diff --git a/docs/02-javascript-api.md b/docs/02-javascript-api.md index ed03f0a12aa..61db48a09f7 100755 --- a/docs/02-javascript-api.md +++ b/docs/02-javascript-api.md @@ -44,9 +44,11 @@ async function build() { // exports: string[], // exported variable names // facadeModuleId: string | null, // the id of a module that this chunk corresponds to // fileName: string, // the chunk file name + // implicitlyLoadedBefore: string[]; // entries that should only be loaded after this chunk // imports: string[], // external modules imported statically by the chunk // isDynamicEntry: boolean, // is this chunk a dynamic entry point // isEntry: boolean, // is this chunk a static entry point + // isImplicitEntry: boolean, // should this chunk only be loaded after other chunks // map: string | null, // sourcemaps if present // modules: { // information about the modules in this chunk // [id: string]: { @@ -57,6 +59,7 @@ async function build() { // }; // }, // name: string // the name of this chunk as used in naming patterns + // referencedFiles: string[] // files referenced via import.meta.ROLLUP_FILE_URL_ // type: 'chunk', // signifies that this is a chunk // } console.log('Chunk', chunkOrAsset.modules); diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md index a7b5da7cfde..8b2c60abdc3 100644 --- a/docs/05-plugin-development.md +++ b/docs/05-plugin-development.md @@ -309,6 +309,7 @@ Called at the end of `bundle.generate()` or immediately before the files are wri }, }, name: string, + referencedFiles: string[], type: 'chunk', } ``` diff --git a/src/Bundle.ts b/src/Bundle.ts index 62e77270e01..d139e9627c4 100644 --- a/src/Bundle.ts +++ b/src/Bundle.ts @@ -82,20 +82,12 @@ export default class Bundle { ): Promise { this.assignChunkIds(chunks, inputBase, addons, outputBundle); for (const chunk of chunks) { - const chunkDescription = (outputBundle[ - chunk.id! - ] = chunk.getChunkInfoWithFileNames() as OutputChunk); - chunkDescription.fileName = chunk.id!; + outputBundle[chunk.id!] = chunk.getChunkInfoWithFileNames() as OutputChunk; } await Promise.all( - chunks.map(chunk => { + chunks.map(async chunk => { const outputChunk = outputBundle[chunk.id!] as OutputChunk; - return chunk - .render(this.outputOptions, addons, outputChunk, this.pluginDriver) - .then(rendered => { - outputChunk.code = rendered.code; - outputChunk.map = rendered.map; - }); + Object.assign(outputChunk, await chunk.render(this.outputOptions, addons, outputChunk)); }) ); } @@ -146,7 +138,7 @@ export default class Bundle { this.unsetOptions ); } else { - chunk.id = chunk.generateId(addons, this.outputOptions, bundle, true, this.pluginDriver); + chunk.id = chunk.generateId(addons, this.outputOptions, bundle, true); } bundle[chunk.id] = FILE_PLACEHOLDER; } @@ -206,6 +198,7 @@ export default class Bundle { this.inputOptions, this.outputOptions, this.unsetOptions, + this.pluginDriver, this.graph.modulesById, chunkByModule, this.facadeChunkByModule, @@ -231,7 +224,7 @@ export default class Bundle { chunk.generateExports(); } for (const chunk of chunks) { - chunk.preRender(this.outputOptions, inputBase, this.pluginDriver); + chunk.preRender(this.outputOptions, inputBase); } } } diff --git a/src/Chunk.ts b/src/Chunk.ts index 05142fbe7be..9fd9e1a5bd5 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -118,6 +118,7 @@ export default class Chunk { inputOptions: NormalizedInputOptions, outputOptions: NormalizedOutputOptions, unsetOptions: Set, + pluginDriver: PluginDriver, modulesById: Map, chunkByModule: Map, facadeChunkByModule: Map, @@ -129,6 +130,7 @@ export default class Chunk { inputOptions, outputOptions, unsetOptions, + pluginDriver, modulesById, chunkByModule, facadeChunkByModule, @@ -197,6 +199,7 @@ export default class Chunk { private readonly inputOptions: NormalizedInputOptions, private readonly outputOptions: NormalizedOutputOptions, private readonly unsetOptions: Set, + private readonly pluginDriver: PluginDriver, private readonly modulesById: Map, private readonly chunkByModule: Map, private readonly facadeChunkByModule: Map, @@ -342,6 +345,7 @@ export default class Chunk { this.inputOptions, this.outputOptions, this.unsetOptions, + this.pluginDriver, this.modulesById, this.chunkByModule, this.facadeChunkByModule, @@ -376,8 +380,7 @@ export default class Chunk { addons: Addons, options: NormalizedOutputOptions, existingNames: Record, - includeHash: boolean, - outputPluginDriver: PluginDriver + includeHash: boolean ): string { if (this.fileName !== null) { return this.fileName; @@ -394,12 +397,7 @@ export default class Chunk { format: () => options.format, hash: () => includeHash - ? this.computeContentHashWithDependencies( - addons, - options, - existingNames, - outputPluginDriver - ) + ? this.computeContentHashWithDependencies(addons, options, existingNames) : '[hash]', name: () => this.getChunkName() }, @@ -469,7 +467,8 @@ export default class Chunk { fileName: this.id!, implicitlyLoadedBefore: Array.from(this.implicitlyLoadedBefore, getId), imports: Array.from(this.dependencies, getId), - map: undefined + map: undefined, + referencedFiles: this.getReferencedFiles() }); } @@ -483,10 +482,10 @@ export default class Chunk { ); } - getRenderedHash(outputPluginDriver: PluginDriver): string { + getRenderedHash(): string { if (this.renderedHash) return this.renderedHash; const hash = createHash(); - const hashAugmentation = outputPluginDriver.hookReduceValueSync( + const hashAugmentation = this.pluginDriver.hookReduceValueSync( 'augmentChunkHash', '', [this.getChunkInfo()], @@ -529,7 +528,7 @@ export default class Chunk { } // prerender allows chunk hashes and names to be generated before finalizing - preRender(options: NormalizedOutputOptions, inputBase: string, outputPluginDriver: PluginDriver) { + preRender(options: NormalizedOutputOptions, inputBase: string) { const magicString = new MagicStringBundle({ separator: options.compact ? '' : '\n\n' }); this.usedModules = []; this.indentString = getIndentString(this.orderedModules, options); @@ -545,7 +544,7 @@ export default class Chunk { freeze: options.freeze, indent: this.indentString, namespaceToStringTag: options.namespaceToStringTag, - outputPluginDriver, + outputPluginDriver: this.pluginDriver, varOrConst: options.preferConst ? 'const' : 'var' }; @@ -627,12 +626,7 @@ export default class Chunk { this.exportMode === 'none' ? [] : this.getChunkExportDeclarations(options.format); } - async render( - options: NormalizedOutputOptions, - addons: Addons, - outputChunk: RenderedChunk, - outputPluginDriver: PluginDriver - ) { + async render(options: NormalizedOutputOptions, addons: Addons, outputChunk: RenderedChunk) { timeStart('render format', 2); const format = options.format; @@ -660,7 +654,7 @@ export default class Chunk { } this.finaliseDynamicImports(options); - this.finaliseImportMetas(format, outputPluginDriver); + this.finaliseImportMetas(format); const hasExports = this.renderedExports!.length !== 0 || @@ -723,7 +717,7 @@ export default class Chunk { let code = await renderChunk({ code: prevCode, options, - outputPluginDriver, + outputPluginDriver: this.pluginDriver, renderChunk: outputChunk, sourcemapChain: chunkSourcemapChain }); @@ -797,8 +791,7 @@ export default class Chunk { private computeContentHashWithDependencies( addons: Addons, options: NormalizedOutputOptions, - existingNames: Record, - outputPluginDriver: PluginDriver + existingNames: Record ): string { const hash = createHash(); hash.update( @@ -810,8 +803,8 @@ export default class Chunk { if (current instanceof ExternalModule) { hash.update(':' + current.renderPath); } else { - hash.update(current.getRenderedHash(outputPluginDriver)); - hash.update(current.generateId(addons, options, existingNames, false, outputPluginDriver)); + hash.update(current.getRenderedHash()); + hash.update(current.generateId(addons, options, existingNames, false)); } if (current instanceof ExternalModule) continue; for (const dependency of [...current.dependencies, ...current.dynamicDependencies]) { @@ -874,13 +867,10 @@ export default class Chunk { } } - private finaliseImportMetas( - format: InternalModuleFormat, - outputPluginDriver: PluginDriver - ): void { + private finaliseImportMetas(format: InternalModuleFormat): void { for (const [module, code] of this.renderedModuleSources) { for (const importMeta of module.importMetas) { - importMeta.renderFinalMechanism(code, this.id!, format, outputPluginDriver); + importMeta.renderFinalMechanism(code, this.id!, format, this.pluginDriver); } } } @@ -1048,6 +1038,19 @@ export default class Chunk { return getAliasName(this.orderedModules[this.orderedModules.length - 1].id); } + private getReferencedFiles(): string[] { + const referencedFiles: string[] = []; + for (const module of this.orderedModules) { + for (const meta of module.importMetas) { + const fileName = meta.getReferencedFileName(this.pluginDriver); + if (fileName) { + referencedFiles.push(fileName); + } + } + } + return referencedFiles; + } + private getRelativePath(targetPath: string, stripJsExtension: boolean): string { let relativePath = normalize(relative(dirname(this.id!), targetPath)); if (stripJsExtension && relativePath.endsWith('.js')) { diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index a90cbf0b7f8..76cd2e3c3c9 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -20,6 +20,14 @@ export default class MetaProperty extends NodeBase { private metaProperty?: string | null; + getReferencedFileName(outputPluginDriver: PluginDriver): string | null { + const metaProperty = this.metaProperty as string | null; + if (metaProperty && metaProperty.startsWith(FILE_PREFIX)) { + return outputPluginDriver.getFileName(metaProperty.substr(FILE_PREFIX.length)); + } + return null; + } + hasEffects(): boolean { return false; } @@ -31,37 +39,33 @@ export default class MetaProperty extends NodeBase { include() { if (!this.included) { this.included = true; - const parent = this.parent; - const metaProperty = (this.metaProperty = - parent instanceof MemberExpression && typeof parent.propertyKey === 'string' - ? parent.propertyKey - : null); - if ( - metaProperty && - (metaProperty.startsWith(FILE_PREFIX) || - metaProperty.startsWith(ASSET_PREFIX) || - metaProperty.startsWith(CHUNK_PREFIX)) - ) { - this.scope.addAccessedGlobalsByFormat(accessedFileUrlGlobals); - } else { - this.scope.addAccessedGlobalsByFormat(accessedMetaUrlGlobals); + if (this.meta.name === 'import') { + this.context.addImportMeta(this); + const parent = this.parent; + const metaProperty = (this.metaProperty = + parent instanceof MemberExpression && typeof parent.propertyKey === 'string' + ? parent.propertyKey + : null); + if ( + metaProperty && + (metaProperty.startsWith(FILE_PREFIX) || + metaProperty.startsWith(ASSET_PREFIX) || + metaProperty.startsWith(CHUNK_PREFIX)) + ) { + this.scope.addAccessedGlobalsByFormat(accessedFileUrlGlobals); + } else { + this.scope.addAccessedGlobalsByFormat(accessedMetaUrlGlobals); + } } } } - initialise() { - if (this.meta.name === 'import') { - this.context.addImportMeta(this); - } - } - renderFinalMechanism( code: MagicString, chunkId: string, format: InternalModuleFormat, outputPluginDriver: PluginDriver ): void { - if (!this.included) return; const parent = this.parent; const metaProperty = this.metaProperty as string | null; diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index d89121a1f14..9082127ad80 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -681,6 +681,7 @@ export interface RenderedChunk extends PreRenderedChunk { implicitlyLoadedBefore: string[]; imports: string[]; map?: SourceMap; + referencedFiles: string[]; } export interface OutputChunk extends RenderedChunk { diff --git a/test/function/samples/emit-file/emit-from-output-options/buildStart1.js b/test/function/samples/emit-file/emit-from-output-options/buildStart1.js deleted file mode 100644 index 4c326cae1bc..00000000000 --- a/test/function/samples/emit-file/emit-from-output-options/buildStart1.js +++ /dev/null @@ -1 +0,0 @@ -console.log('buildStart1'); diff --git a/test/function/samples/emit-file/emit-from-output-options/buildStart2.js b/test/function/samples/emit-file/emit-from-output-options/buildStart2.js deleted file mode 100644 index a74fcff5302..00000000000 --- a/test/function/samples/emit-file/emit-from-output-options/buildStart2.js +++ /dev/null @@ -1 +0,0 @@ -console.log('buildStart2'); diff --git a/test/function/samples/emit-file/file-references-in-bundle/_config.js b/test/function/samples/emit-file/file-references-in-bundle/_config.js new file mode 100644 index 00000000000..2d245fd526e --- /dev/null +++ b/test/function/samples/emit-file/file-references-in-bundle/_config.js @@ -0,0 +1,39 @@ +const assert = require('assert'); + +module.exports = { + description: 'lists referenced files in the bundle', + options: { + input: 'main', + plugins: { + transform() { + return `export const asset = import.meta.ROLLUP_FILE_URL_${this.emitFile({ + type: 'asset', + name: 'asset.txt', + source: 'asset' + })};\nexport const chunk = import.meta.ROLLUP_FILE_URL_${this.emitFile({ + type: 'chunk', + id: 'ref.js' + })}`; + }, + generateBundle(options, bundle) { + assert.deepStrictEqual(bundle['main.js'].referencedFiles, [ + 'assets/asset.txt', + 'chunks/ref.js' + ]); + } + }, + output: { + assetFileNames: 'assets/[name][extname]', + chunkFileNames: 'chunks/[name].js' + } + }, + context: { + __dirname: 'dir' + }, + exports(exports) { + assert.deepStrictEqual(exports, { + asset: 'file:///dir/assets/asset.txt', + chunk: 'file:///dir/chunks/ref.js' + }); + } +}; diff --git a/test/function/samples/emit-file/file-references-in-bundle/main.js b/test/function/samples/emit-file/file-references-in-bundle/main.js new file mode 100644 index 00000000000..7e37f689f8e --- /dev/null +++ b/test/function/samples/emit-file/file-references-in-bundle/main.js @@ -0,0 +1 @@ +throw new Error('not executed'); diff --git a/test/function/samples/emit-file/file-references-in-bundle/ref.js b/test/function/samples/emit-file/file-references-in-bundle/ref.js new file mode 100644 index 00000000000..a730e61cb7a --- /dev/null +++ b/test/function/samples/emit-file/file-references-in-bundle/ref.js @@ -0,0 +1 @@ +console.log('Hello'); diff --git a/test/watch/index.js b/test/watch/index.js index 8c005b160ca..a3dbb22d9c9 100644 --- a/test/watch/index.js +++ b/test/watch/index.js @@ -1243,7 +1243,7 @@ describe('rollup.watch', () => { 'END', () => { assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - assert.strictEqual(watchChangeIds.size, 0); + assert.deepStrictEqual([...watchChangeIds], []); for (const file of watchFiles) sander.writeFileSync(file, 'changed'); }, 'START',