From c98761e8c34500d72be0c1ace4f5eb890f82de87 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Mon, 13 Aug 2018 17:05:50 +0200 Subject: [PATCH] renderChunk hook to replace transformChunk --- src/Chunk.ts | 63 +++++++++++++++++------------- src/rollup/index.ts | 19 +++------ src/rollup/types.d.ts | 21 ++++------ src/utils/renderChunk.ts | 69 +++++++++++++++++++++++++++++++++ src/utils/transformChunk.ts | 77 ------------------------------------- test/hooks/index.js | 36 +++++++++++++++++ 6 files changed, 156 insertions(+), 129 deletions(-) create mode 100644 src/utils/renderChunk.ts delete mode 100644 src/utils/transformChunk.ts diff --git a/src/Chunk.ts b/src/Chunk.ts index d90be4e6f20..42ad910973f 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -12,7 +12,13 @@ import ExternalModule from './ExternalModule'; import finalisers from './finalisers/index'; import Graph from './Graph'; import Module from './Module'; -import { GlobalsOption, OutputOptions, RawSourceMap, RenderedModule } from './rollup/types'; +import { + GlobalsOption, + OutputOptions, + RawSourceMap, + RenderChunk, + RenderedModule +} from './rollup/types'; import { Addons } from './utils/addons'; import { toBase64 } from './utils/base64'; import collapseSourcemaps from './utils/collapseSourcemaps'; @@ -20,10 +26,10 @@ import error from './utils/error'; import getIndentString from './utils/getIndentString'; import { makeLegal } from './utils/identifierHelpers'; import { basename, dirname, normalize, relative, resolve } from './utils/path'; +import renderChunk from './utils/renderChunk'; import { RenderOptions } from './utils/renderHelpers'; import { makeUnique, renderNamePattern } from './utils/renderNamePattern'; import { timeEnd, timeStart } from './utils/timers'; -import transformChunk from './utils/transformChunk'; export interface ModuleDeclarations { exports: ChunkExports; @@ -1027,7 +1033,7 @@ export default class Chunk { this.id = outName; } - render(options: OutputOptions, addons: Addons) { + render(options: OutputOptions, addons: Addons, outputChunk: RenderChunk) { timeStart('render format', 3); if (!this.renderedSource) @@ -1105,32 +1111,37 @@ export default class Chunk { let map: SourceMap = null; const chunkSourcemapChain: RawSourceMap[] = []; - return transformChunk(this.graph, this, prevCode, chunkSourcemapChain, options).then( - (code: string) => { - if (options.sourcemap) { - timeStart('sourcemap', 3); - - let file: string; - if (options.file) file = resolve(options.sourcemapFile || options.file); - else if (options.dir) file = resolve(options.dir, this.id); - else file = resolve(this.id); - - if (this.graph.pluginDriver.hasLoadersOrTransforms) { - const decodedMap = magicString.generateDecodedMap({}); - map = collapseSourcemaps(this, file, decodedMap, this.usedModules, chunkSourcemapChain); - } else { - map = magicString.generateMap({ file, includeContent: true }); - } - - map.sources = map.sources.map(normalize); - - timeEnd('sourcemap', 3); + return renderChunk({ + graph: this.graph, + chunk: this, + renderChunk: outputChunk, + code: prevCode, + sourcemapChain: chunkSourcemapChain, + options + }).then((code: string) => { + if (options.sourcemap) { + timeStart('sourcemap', 3); + + let file: string; + if (options.file) file = resolve(options.sourcemapFile || options.file); + else if (options.dir) file = resolve(options.dir, this.id); + else file = resolve(this.id); + + if (this.graph.pluginDriver.hasLoadersOrTransforms) { + const decodedMap = magicString.generateDecodedMap({}); + map = collapseSourcemaps(this, file, decodedMap, this.usedModules, chunkSourcemapChain); + } else { + map = magicString.generateMap({ file, includeContent: true }); } - if (options.compact !== true && code[code.length - 1] !== '\n') code += '\n'; + map.sources = map.sources.map(normalize); - return { code, map }; + timeEnd('sourcemap', 3); } - ); + + if (options.compact !== true && code[code.length - 1] !== '\n') code += '\n'; + + return { code, map }; + }); } } diff --git a/src/rollup/index.ts b/src/rollup/index.ts index ec666c40d11..7c61f6cb346 100644 --- a/src/rollup/index.ts +++ b/src/rollup/index.ts @@ -274,7 +274,7 @@ export default function rollup( optimized = true; } - // then name all chunks + // name all chunks for (let i = 0; i < chunks.length; i++) { const chunk = chunks[i]; const imports = chunk.getImportIds(); @@ -288,14 +288,6 @@ export default function rollup( ? inputOptions.input[0] : inputOptions.input) ); - const outputChunk: OutputChunk = { - imports, - exports, - modules, - code: undefined, - map: undefined - }; - outputBundle[singleChunk.id] = outputChunk; } else if (inputOptions.experimentalPreserveModules) { chunk.generateIdPreserveModules(inputBase); } else { @@ -309,7 +301,10 @@ export default function rollup( } chunk.generateId(pattern, patternName, addons, outputOptions, outputBundle); } + outputBundle[chunk.id] = { + fileName: chunk.id, + isEntry: chunk.entryModule !== undefined, imports, exports, modules, @@ -318,12 +313,10 @@ export default function rollup( }; } - // render chunk import statements and finalizer wrappers given known names return Promise.all( chunks.map(chunk => { - const chunkId = chunk.id; - return chunk.render(outputOptions, addons).then(rendered => { - const outputChunk = outputBundle[chunkId]; + const outputChunk = outputBundle[chunk.id]; + return chunk.render(outputOptions, addons, outputChunk).then(rendered => { outputChunk.code = rendered.code; outputChunk.map = rendered.map; diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 16e4bc36bc3..600009cc212 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -129,25 +129,15 @@ export type TransformHook = ( | void; export type TransformChunkHook = ( + this: PluginContext, code: string, - options: OutputOptions, - chunk: OutputChunk + options: OutputOptions ) => | Promise<{ code: string; map: RawSourceMap } | void> | { code: string; map: RawSourceMap } | void | null; -export type TransformChunkHookBound = ( - this: PluginContext, - code: string, - options: OutputOptions, - chunk: OutputChunk -) => - | Promise<{ code: string; map: RawSourceMap } | void> - | { code: string; map: RawSourceMap } - | void; - export type ResolveDynamicImportHook = ( this: PluginContext, specifier: string | ESTree.Node, @@ -354,12 +344,17 @@ export interface RenderedModule { originalLength: number; } -export interface OutputChunk { +export interface RenderChunk { + fileName: string; + isEntry: boolean; imports: string[]; exports: string[]; modules: { [id: string]: RenderedModule; }; +} + +export interface OutputChunk extends RenderChunk { code: string; map?: SourceMap; } diff --git a/src/utils/renderChunk.ts b/src/utils/renderChunk.ts new file mode 100644 index 00000000000..83faefae4f7 --- /dev/null +++ b/src/utils/renderChunk.ts @@ -0,0 +1,69 @@ +import { decode } from 'sourcemap-codec'; +import Chunk from '../Chunk'; +import Graph from '../Graph'; +import { OutputOptions, Plugin, RawSourceMap, RenderChunk } from '../rollup/types'; +import error from './error'; + +export default function renderChunk({ + graph, + chunk, + renderChunk, + code, + sourcemapChain, + options +}: { + graph: Graph; + chunk: Chunk; + renderChunk: RenderChunk; + code: string; + sourcemapChain: RawSourceMap[]; + options: OutputOptions; +}) { + const renderChunkReducer = (code: string, result: any, plugin: Plugin): string => { + if (result == null) return code; + + if (typeof result === 'string') + result = { + code: result, + map: undefined + }; + + const map = typeof result.map === 'string' ? JSON.parse(result.map) : result.map; + if (map && typeof map.mappings === 'string') map.mappings = decode(map.mappings); + + // strict null check allows 'null' maps to not be pushed to the chain, while 'undefined' gets the missing map warning + if (map !== null) sourcemapChain.push(map || { missing: true, plugin: plugin.name }); + + return result.code; + }; + + let inTransformBundle = false; + let inRenderChunk = true; + return graph.pluginDriver + .hookReduceArg0('renderChunk', [code, renderChunk, options], renderChunkReducer) + .then(code => { + inRenderChunk = false; + return graph.pluginDriver.hookReduceArg0( + 'transformChunk', + [code, options, chunk], + renderChunkReducer + ); + }) + .then(code => { + inTransformBundle = true; + return graph.pluginDriver.hookReduceArg0( + 'transformBundle', + [code, options, chunk], + renderChunkReducer + ); + }) + .catch(err => { + if (inRenderChunk) throw err; + error(err, { + code: inTransformBundle ? 'BAD_BUNDLE_TRANSFORMER' : 'BAD_CHUNK_TRANSFORMER', + message: `Error transforming ${(inTransformBundle ? 'bundle' : 'chunk') + + (err.plugin ? ` with '${err.plugin}' plugin` : '')}: ${err.message}`, + plugin: err.plugin + }); + }); +} diff --git a/src/utils/transformChunk.ts b/src/utils/transformChunk.ts deleted file mode 100644 index e359945b6a6..00000000000 --- a/src/utils/transformChunk.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { decode } from 'sourcemap-codec'; -import Chunk from '../Chunk'; -import Graph from '../Graph'; -import { OutputOptions, Plugin, RawSourceMap } from '../rollup/types'; -import { createAssetPluginHooks } from './assetHooks'; -import error from './error'; - -export default function transformChunk( - graph: Graph, - chunk: Chunk, - code: string, - sourcemapChain: RawSourceMap[], - options: OutputOptions -) { - const transformChunkAssetPluginHooks = createAssetPluginHooks(graph.assetsById, graph.watchFiles); - - const transformChunkReducer = (code: string, result: any, plugin: Plugin): string => { - if (result == null) return code; - - if (typeof result === 'string') { - result = { - code: result, - map: undefined - }; - } else if (!inTransformBundle && !result.map && options.sourcemap) { - throw new Error( - `${ - inTransformBundle ? 'transformBundle' : 'transformChunk' - } must return a "map" sourcemap property when sourcemaps are enabled.` - ); - } - - const map = typeof result.map === 'string' ? JSON.parse(result.map) : result.map; - if (map && typeof map.mappings === 'string') { - map.mappings = decode(map.mappings); - } - - // strict null check allows 'null' maps to not be pushed to the chain, while 'undefined' gets the missing map warning - if (map !== null) { - sourcemapChain.push(map || { missing: true, plugin: plugin.name }); - } - - return result.code; - }; - - let inTransformBundle = false; - return graph.pluginDriver - .hookReduceArg0( - 'transformChunk', - [code, options, chunk], - transformChunkReducer, - pluginContext => ({ - ...pluginContext, - ...transformChunkAssetPluginHooks - }) - ) - .then(code => { - inTransformBundle = true; - return graph.pluginDriver.hookReduceArg0( - 'transformBundle', - [code, options, chunk], - transformChunkReducer, - pluginContext => ({ - ...pluginContext, - ...transformChunkAssetPluginHooks - }) - ); - }) - .catch(err => { - error(err, { - code: inTransformBundle ? 'BAD_BUNDLE_TRANSFORMER' : 'BAD_CHUNK_TRANSFORMER', - message: `Error transforming ${(inTransformBundle ? 'bundle' : 'chunk') + - (err.plugin ? ` with '${err.plugin}' plugin` : '')}: ${err.message}`, - plugin: err.plugin - }); - }); -} diff --git a/test/hooks/index.js b/test/hooks/index.js index aca01a31768..5aff369ae1f 100644 --- a/test/hooks/index.js +++ b/test/hooks/index.js @@ -619,6 +619,42 @@ module.exports = input; }); }); + it('supports renderChunk in place of transformBundle and transformChunk', () => { + let calledHook = false; + return rollup + .rollup({ + input: 'input', + experimentalCodeSplitting: true, + plugins: [ + loader({ input: `alert('hello')` }), + { + renderChunk (code, chunk, options) { + calledHook = true; + assert.equal(chunk.fileName, 'input.js'); + assert.equal(chunk.isEntry, true); + assert.equal(chunk.exports.length, 0); + assert.ok(chunk.modules['input']); + try { + this.emitAsset('test.ext', 'hello world'); + } + catch (e) { + assert.equal(e.code, 'ASSETS_ALREADY_FINALISED'); + } + } + } + ] + }) + .then(bundle => { + return bundle.generate({ + format: 'es', + assetFileNames: '[name][extname]' + }); + }) + .then(() => { + assert.equal(calledHook, true); + }); + }); + it('passes bundle object to generateBundle hook', () => { return rollup .rollup({