From bea46a77956bb6f304a4b69648a55189bbb06b51 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 25 Sep 2020 15:09:17 +0200 Subject: [PATCH 1/4] Attach ESTree AST to all Nodes and do not expose internal AST --- src/ExternalModule.ts | 2 ++ src/Module.ts | 23 ++++++++++------------- src/ModuleLoader.ts | 5 +++-- src/ast/nodes/shared/Node.ts | 7 +++++-- src/rollup/types.d.ts | 4 ++++ src/utils/collapseSourcemaps.ts | 2 -- 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index b6db075d3e7..9fdd86c4990 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -9,6 +9,8 @@ import { isAbsolute, normalize, relative } from './utils/path'; export default class ExternalModule { chunk: void; + // TODO Lukas get from resolution + custom: CustomPluginOptions = {}; declarations: { [name: string]: ExternalVariable }; defaultVariableName = ''; dynamicImporters: string[] = []; diff --git a/src/Module.ts b/src/Module.ts index 082a5cb5ab2..ef6112ce1cc 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -113,7 +113,11 @@ export interface AstContext { warn: (warning: RollupWarning, pos: number) => void; } -function tryParse(module: Module, Parser: typeof acorn.Parser, acornOptions: acorn.Options) { +function tryParse( + module: Module, + Parser: typeof acorn.Parser, + acornOptions: acorn.Options +): acorn.Node { try { return Parser.parse(module.code, { ...acornOptions, @@ -225,7 +229,6 @@ export default class Module { private astContext!: AstContext; private readonly context: string; private customTransformCache!: boolean; - private esTreeAst!: acorn.Node; private exportAllModules: (Module | ExternalModule)[] = []; private exportNamesByVariable: Map | null = null; private exportShimVariable: ExportShimVariable = new ExportShimVariable(this); @@ -639,16 +642,14 @@ export default class Module { timeStart('generate ast', 3); this.alwaysRemovedCode = alwaysRemovedCode || []; - if (ast) { - this.esTreeAst = ast; - } else { - this.esTreeAst = tryParse(this, this.graph.acornParser, this.options.acorn as acorn.Options); + if (!ast) { + ast = tryParse(this, this.graph.acornParser, this.options.acorn as acorn.Options); for (const comment of this.comments) { if (!comment.block && SOURCEMAPPING_URL_RE.test(comment.text)) { this.alwaysRemovedCode.push([comment.start, comment.end]); } } - markPureCallExpressions(this.comments, this.esTreeAst); + markPureCallExpressions(this.comments, ast); } timeEnd('generate ast', 3); @@ -699,11 +700,7 @@ export default class Module { this.scope = new ModuleScope(this.graph.scope, this.astContext); this.namespace = new NamespaceVariable(this.astContext, this.syntheticNamedExports); - this.ast = new Program( - this.esTreeAst, - { type: 'Module', context: this.astContext }, - this.scope - ); + this.ast = new Program(ast, { type: 'Module', context: this.astContext }, this.scope); timeEnd('analyse ast', 3); } @@ -711,7 +708,7 @@ export default class Module { toJSON(): ModuleJSON { return { alwaysRemovedCode: this.alwaysRemovedCode, - ast: this.esTreeAst, + ast: this.ast.esTreeNode, code: this.code, customTransformCache: this.customTransformCache, dependencies: Array.from(this.dependencies, getId), diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index 0f3d3b4ced3..e7562abb563 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -266,7 +266,9 @@ export class ModuleLoader { module.dynamicImports.map(async dynamicImport => { const resolvedId = await this.resolveDynamicImport( module, - dynamicImport.argument, + typeof dynamicImport.argument === 'string' + ? dynamicImport.argument + : dynamicImport.argument.esTreeNode, module.id ); if (resolvedId === null) return null; @@ -475,7 +477,6 @@ export class ModuleLoader { specifier: string | acorn.Node, importer: string ): Promise { - // TODO we only should expose the acorn AST here const resolution = await this.pluginDriver.hookFirst('resolveDynamicImport', [ specifier, importer diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 572bc891af0..34040a77653 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -1,3 +1,4 @@ +import * as acorn from 'acorn'; import { locate } from 'locate-character'; import MagicString from 'magic-string'; import { AstContext, CommentDescription } from '../../../Module'; @@ -20,8 +21,7 @@ import * as NodeType from '../NodeType'; import SpreadElement from '../SpreadElement'; import { ExpressionEntity } from './Expression'; -export interface GenericEsTreeNode { - type: string; +export interface GenericEsTreeNode extends acorn.Node { [key: string]: any; } @@ -32,6 +32,7 @@ export interface Node extends Entity { annotations?: CommentDescription[]; context: AstContext; end: number; + esTreeNode: GenericEsTreeNode; included: boolean; keys: string[]; needsBoundaries?: boolean; @@ -93,6 +94,7 @@ export interface ExpressionNode extends ExpressionEntity, Node {} export class NodeBase implements ExpressionNode { context: AstContext; end!: number; + esTreeNode: acorn.Node; included = false; keys: string[]; parent: Node | { context: AstContext; type: string }; @@ -105,6 +107,7 @@ export class NodeBase implements ExpressionNode { parent: Node | { context: AstContext; type: string }, parentScope: ChildScope ) { + this.esTreeNode = esTreeNode; this.keys = keys[esTreeNode.type] || getAndCreateKeys(esTreeNode); this.parent = parent; this.context = parent.context; diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 074dfeff9f5..ae01a09d63a 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -177,6 +177,10 @@ export interface CustomPluginOptions { [plugin: string]: any; } +export interface CustomPluginOptions { + [plugin: string]: any; +} + export interface PluginContext extends MinimalPluginContext { addWatchFile: (id: string) => void; cache: PluginCache; diff --git a/src/utils/collapseSourcemaps.ts b/src/utils/collapseSourcemaps.ts index 3d9eeaa6785..8ae6238f61c 100644 --- a/src/utils/collapseSourcemaps.ts +++ b/src/utils/collapseSourcemaps.ts @@ -185,8 +185,6 @@ function getCollapsedSourcemap( } else { const sources = originalSourcemap.sources; const sourcesContent = originalSourcemap.sourcesContent || []; - - // TODO indiscriminately treating IDs and sources as normal paths is probably bad. const directory = dirname(id) || '.'; const sourceRoot = originalSourcemap.sourceRoot || '.'; From 2e4cc0caaaa90fe4830ede510b6c797237d7c2ff Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 6 Oct 2020 06:26:08 +0200 Subject: [PATCH 2/4] Include code and AST in module info --- src/Graph.ts | 2 + src/Module.ts | 26 +- src/rollup/types.d.ts | 3 + .../_config.js | 94 ++++- .../implicitly-dependent-entry/_config.js | 94 ++++- .../multiple-dependencies/_config.js | 248 +++++++++++- .../single-dependency/_config.js | 94 ++++- .../deprecated/manual-chunks-info/_config.js | 175 +++++++- .../samples/manual-chunks-info/_config.js | 175 +++++++- .../plugin-module-information/_config.js | 372 +++++++++++++++--- 10 files changed, 1185 insertions(+), 98 deletions(-) diff --git a/src/Graph.ts b/src/Graph.ts index 240d242ab13..be0e034e91e 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -137,6 +137,8 @@ export default class Graph { const foundModule = this.modulesById.get(moduleId); if (!foundModule) return null; return { + ast: (foundModule as Module).ast?.esTreeNode || null, + code: foundModule instanceof Module ? foundModule.code : null, get dynamicallyImportedIds() { if (foundModule instanceof Module) { const dynamicallyImportedIds: string[] = []; diff --git a/src/Module.ts b/src/Module.ts index ef6112ce1cc..71354bbec6a 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -119,7 +119,7 @@ function tryParse( acornOptions: acorn.Options ): acorn.Node { try { - return Parser.parse(module.code, { + return Parser.parse(module.code!, { ...acornOptions, onComment: (block: boolean, text: string, start: number, end: number) => module.comments.push({ block, text, start, end }) @@ -184,9 +184,10 @@ function getVariableForExportNameRecursive( } export default class Module { + ast: Program | null = null; chunkFileNames = new Set(); chunkName: string | null = null; - code!: string; + code: string | null = null; comments: CommentDescription[] = []; dependencies = new Set(); dynamicDependencies = new Set(); @@ -225,7 +226,6 @@ export default class Module { private allExportNames: Set | null = null; private alwaysRemovedCode!: [number, number][]; - private ast!: Program; private astContext!: AstContext; private readonly context: string; private customTransformCache!: boolean; @@ -260,7 +260,7 @@ export default class Module { } bindReferences() { - this.ast.bind(); + this.ast!.bind(); } error(props: RollupError, pos: number): never { @@ -527,13 +527,13 @@ export default class Module { hasEffects() { return ( this.moduleSideEffects === 'no-treeshake' || - (this.ast.included && this.ast.hasEffects(createHasEffectsContext())) + (this.ast!.included && this.ast!.hasEffects(createHasEffectsContext())) ); } include(): void { const context = createInclusionContext(); - if (this.ast.shouldBeIncluded(context)) this.ast.include(context, false); + if (this.ast!.shouldBeIncluded(context)) this.ast!.include(context, false); } includeAllExports(includeNamespaceMembers: boolean) { @@ -571,11 +571,11 @@ export default class Module { } includeAllInBundle() { - this.ast.include(createInclusionContext(), true); + this.ast!.include(createInclusionContext(), true); } isIncluded() { - return this.ast.included || this.namespace.included; + return this.ast!.included || this.namespace.included; } linkImports() { @@ -607,7 +607,7 @@ export default class Module { render(options: RenderOptions): MagicString { const magicString = this.magicString.clone(); - this.ast.render(magicString, options); + this.ast!.render(magicString, options); this.usesTopLevelAwait = this.astContext.usesTopLevelAwait; return magicString; } @@ -708,8 +708,8 @@ export default class Module { toJSON(): ModuleJSON { return { alwaysRemovedCode: this.alwaysRemovedCode, - ast: this.ast.esTreeNode, - code: this.code, + ast: this.ast!.esTreeNode, + code: this.code!, customTransformCache: this.customTransformCache, dependencies: Array.from(this.dependencies, getId), id: this.id, @@ -888,7 +888,7 @@ export default class Module { props.id = this.id; props.pos = pos; let code = this.code; - let { column, line } = locate(code, pos, { offsetLine: 1 }); + let { column, line } = locate(code!, pos, { offsetLine: 1 }); try { ({ column, line } = getOriginalLocation(this.sourcemapChain, { column, line })); code = this.originalCode; @@ -905,7 +905,7 @@ export default class Module { pos }); } - augmentCodeLocation(props, { column, line }, code, this.id); + augmentCodeLocation(props, { column, line }, code!, this.id); } private addModulesToImportDescriptions(importDescription: { diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index ae01a09d63a..9291529ebaf 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -158,6 +158,8 @@ export type EmitChunk = (id: string, options?: { name?: string }) => string; export type EmitFile = (emittedFile: EmittedFile) => string; interface ModuleInfo { + ast: AcornNode | null; + code: string | null; dynamicallyImportedIds: readonly string[]; dynamicImporters: readonly string[]; hasModuleSideEffects: boolean | 'no-treeshake'; @@ -347,6 +349,7 @@ export interface PluginHooks extends OutputPluginHooks { buildStart: (this: PluginContext, options: NormalizedInputOptions) => Promise | void; load: LoadHook; options: (this: MinimalPluginContext, options: InputOptions) => InputOptions | null | undefined; + // TODO Lukas parsedModule hook resolveDynamicImport: ResolveDynamicImportHook; resolveId: ResolveIdHook; transform: TransformHook; diff --git a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js index 69222496a6e..41961b92aee 100644 --- a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js @@ -24,7 +24,52 @@ module.exports = { }); }, buildEnd() { - assert.deepStrictEqual(this.getModuleInfo(ID_MAIN), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_MAIN))), { + ast: { + type: 'Program', + start: 0, + end: 51, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 14, + imported: { type: 'Identifier', start: 9, end: 14, name: 'value' }, + local: { type: 'Identifier', start: 9, end: 14, name: 'value' } + } + ], + source: { type: 'Literal', start: 22, end: 29, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExpressionStatement', + start: 31, + end: 50, + expression: { + type: 'CallExpression', + start: 31, + end: 49, + callee: { + type: 'MemberExpression', + start: 31, + end: 42, + object: { type: 'Identifier', start: 31, end: 38, name: 'console' }, + property: { type: 'Identifier', start: 39, end: 42, name: 'log' }, + computed: false, + optional: false + }, + arguments: [{ type: 'Identifier', start: 43, end: 48, name: 'value' }], + optional: false + } + } + ], + sourceType: 'module' + }, + code: "import { value } from './lib';\nconsole.log(value);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, @@ -37,7 +82,52 @@ module.exports = { isExternal: false, meta: {} }); - assert.deepStrictEqual(this.getModuleInfo(ID_DEP), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_DEP))), { + ast: { + type: 'Program', + start: 0, + end: 51, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 14, + imported: { type: 'Identifier', start: 9, end: 14, name: 'value' }, + local: { type: 'Identifier', start: 9, end: 14, name: 'value' } + } + ], + source: { type: 'Literal', start: 22, end: 29, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExpressionStatement', + start: 31, + end: 50, + expression: { + type: 'CallExpression', + start: 31, + end: 49, + callee: { + type: 'MemberExpression', + start: 31, + end: 42, + object: { type: 'Identifier', start: 31, end: 38, name: 'console' }, + property: { type: 'Identifier', start: 39, end: 42, name: 'log' }, + computed: false, + optional: false + }, + arguments: [{ type: 'Identifier', start: 43, end: 48, name: 'value' }], + optional: false + } + } + ], + sourceType: 'module' + }, + code: "import { value } from './lib';\nconsole.log(value);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, diff --git a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js index fe07db0fae5..c3646c4f721 100644 --- a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js @@ -20,7 +20,52 @@ module.exports = { }); }, buildEnd() { - assert.deepStrictEqual(this.getModuleInfo(ID_MAIN), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_MAIN))), { + ast: { + type: 'Program', + start: 0, + end: 51, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 14, + imported: { type: 'Identifier', start: 9, end: 14, name: 'value' }, + local: { type: 'Identifier', start: 9, end: 14, name: 'value' } + } + ], + source: { type: 'Literal', start: 22, end: 29, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExpressionStatement', + start: 31, + end: 50, + expression: { + type: 'CallExpression', + start: 31, + end: 49, + callee: { + type: 'MemberExpression', + start: 31, + end: 42, + object: { type: 'Identifier', start: 31, end: 38, name: 'console' }, + property: { type: 'Identifier', start: 39, end: 42, name: 'log' }, + computed: false, + optional: false + }, + arguments: [{ type: 'Identifier', start: 43, end: 48, name: 'value' }], + optional: false + } + } + ], + sourceType: 'module' + }, + code: "import { value } from './lib';\nconsole.log(value);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, @@ -33,7 +78,52 @@ module.exports = { isExternal: false, meta: {} }); - assert.deepStrictEqual(this.getModuleInfo(ID_DEP), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_DEP))), { + ast: { + type: 'Program', + start: 0, + end: 51, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 14, + imported: { type: 'Identifier', start: 9, end: 14, name: 'value' }, + local: { type: 'Identifier', start: 9, end: 14, name: 'value' } + } + ], + source: { type: 'Literal', start: 22, end: 29, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExpressionStatement', + start: 31, + end: 50, + expression: { + type: 'CallExpression', + start: 31, + end: 49, + callee: { + type: 'MemberExpression', + start: 31, + end: 42, + object: { type: 'Identifier', start: 31, end: 38, name: 'console' }, + property: { type: 'Identifier', start: 39, end: 42, name: 'log' }, + computed: false, + optional: false + }, + arguments: [{ type: 'Identifier', start: 43, end: 48, name: 'value' }], + optional: false + } + } + ], + sourceType: 'module' + }, + code: "import { value } from './lib';\nconsole.log(value);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, diff --git a/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js b/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js index 5ce417b9889..8cb1f3e1b3b 100644 --- a/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js @@ -33,7 +33,88 @@ module.exports = { }); }, buildEnd() { - assert.deepStrictEqual(this.getModuleInfo(ID_MAIN1), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_MAIN1))), { + ast: { + type: 'Program', + start: 0, + end: 137, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 13, + imported: { type: 'Identifier', start: 9, end: 13, name: 'lib1' }, + local: { type: 'Identifier', start: 9, end: 13, name: 'lib1' } + } + ], + source: { type: 'Literal', start: 21, end: 29, value: './lib1', raw: "'./lib1'" } + }, + { + type: 'ImportDeclaration', + start: 31, + end: 63, + specifiers: [ + { + type: 'ImportSpecifier', + start: 40, + end: 45, + imported: { type: 'Identifier', start: 40, end: 45, name: 'lib1b' }, + local: { type: 'Identifier', start: 40, end: 45, name: 'lib1b' } + } + ], + source: { type: 'Literal', start: 53, end: 62, value: './lib1b', raw: "'./lib1b'" } + }, + { + type: 'ImportDeclaration', + start: 64, + end: 94, + specifiers: [ + { + type: 'ImportSpecifier', + start: 73, + end: 77, + imported: { type: 'Identifier', start: 73, end: 77, name: 'lib2' }, + local: { type: 'Identifier', start: 73, end: 77, name: 'lib2' } + } + ], + source: { type: 'Literal', start: 85, end: 93, value: './lib2', raw: "'./lib2'" } + }, + { + type: 'ExpressionStatement', + start: 95, + end: 136, + expression: { + type: 'CallExpression', + start: 95, + end: 135, + callee: { + type: 'MemberExpression', + start: 95, + end: 106, + object: { type: 'Identifier', start: 95, end: 102, name: 'console' }, + property: { type: 'Identifier', start: 103, end: 106, name: 'log' }, + computed: false, + optional: false + }, + arguments: [ + { type: 'Literal', start: 107, end: 114, value: 'main1', raw: "'main1'" }, + { type: 'Identifier', start: 116, end: 120, name: 'lib1' }, + { type: 'Identifier', start: 123, end: 128, name: 'lib1b' }, + { type: 'Identifier', start: 130, end: 134, name: 'lib2' } + ], + optional: false + } + } + ], + sourceType: 'module' + }, + code: + "import { lib1 } from './lib1';\nimport { lib1b } from './lib1b';\nimport { lib2 } from './lib2';\nconsole.log('main1', lib1, lib1b, lib2);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, @@ -46,7 +127,88 @@ module.exports = { isExternal: false, meta: {} }); - assert.deepStrictEqual(this.getModuleInfo(ID_MAIN2), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_MAIN2))), { + ast: { + type: 'Program', + start: 0, + end: 136, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 13, + imported: { type: 'Identifier', start: 9, end: 13, name: 'lib1' }, + local: { type: 'Identifier', start: 9, end: 13, name: 'lib1' } + } + ], + source: { type: 'Literal', start: 21, end: 29, value: './lib1', raw: "'./lib1'" } + }, + { + type: 'ImportDeclaration', + start: 31, + end: 63, + specifiers: [ + { + type: 'ImportSpecifier', + start: 40, + end: 45, + imported: { type: 'Identifier', start: 40, end: 45, name: 'lib1b' }, + local: { type: 'Identifier', start: 40, end: 45, name: 'lib1b' } + } + ], + source: { type: 'Literal', start: 53, end: 62, value: './lib1b', raw: "'./lib1b'" } + }, + { + type: 'ImportDeclaration', + start: 64, + end: 94, + specifiers: [ + { + type: 'ImportSpecifier', + start: 73, + end: 77, + imported: { type: 'Identifier', start: 73, end: 77, name: 'lib3' }, + local: { type: 'Identifier', start: 73, end: 77, name: 'lib3' } + } + ], + source: { type: 'Literal', start: 85, end: 93, value: './lib3', raw: "'./lib3'" } + }, + { + type: 'ExpressionStatement', + start: 95, + end: 135, + expression: { + type: 'CallExpression', + start: 95, + end: 134, + callee: { + type: 'MemberExpression', + start: 95, + end: 106, + object: { type: 'Identifier', start: 95, end: 102, name: 'console' }, + property: { type: 'Identifier', start: 103, end: 106, name: 'log' }, + computed: false, + optional: false + }, + arguments: [ + { type: 'Literal', start: 107, end: 114, value: 'main2', raw: "'main2'" }, + { type: 'Identifier', start: 116, end: 120, name: 'lib1' }, + { type: 'Identifier', start: 122, end: 127, name: 'lib1b' }, + { type: 'Identifier', start: 129, end: 133, name: 'lib3' } + ], + optional: false + } + } + ], + sourceType: 'module' + }, + code: + "import { lib1 } from './lib1';\nimport { lib1b } from './lib1b';\nimport { lib3 } from './lib3';\nconsole.log('main2', lib1, lib1b, lib3);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, @@ -59,7 +221,87 @@ module.exports = { isExternal: false, meta: {} }); - assert.deepStrictEqual(this.getModuleInfo(ID_DEP), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_DEP))), { + ast: { + type: 'Program', + start: 0, + end: 124, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 13, + imported: { type: 'Identifier', start: 9, end: 13, name: 'lib1' }, + local: { type: 'Identifier', start: 9, end: 13, name: 'lib1' } + } + ], + source: { type: 'Literal', start: 21, end: 29, value: './lib1', raw: "'./lib1'" } + }, + { + type: 'ImportDeclaration', + start: 31, + end: 61, + specifiers: [ + { + type: 'ImportSpecifier', + start: 40, + end: 44, + imported: { type: 'Identifier', start: 40, end: 44, name: 'lib2' }, + local: { type: 'Identifier', start: 40, end: 44, name: 'lib2' } + } + ], + source: { type: 'Literal', start: 52, end: 60, value: './lib2', raw: "'./lib2'" } + }, + { + type: 'ImportDeclaration', + start: 62, + end: 92, + specifiers: [ + { + type: 'ImportSpecifier', + start: 71, + end: 75, + imported: { type: 'Identifier', start: 71, end: 75, name: 'lib3' }, + local: { type: 'Identifier', start: 71, end: 75, name: 'lib3' } + } + ], + source: { type: 'Literal', start: 83, end: 91, value: './lib3', raw: "'./lib3'" } + }, + { + type: 'ExpressionStatement', + start: 93, + end: 123, + expression: { + type: 'CallExpression', + start: 93, + end: 122, + callee: { + type: 'MemberExpression', + start: 93, + end: 104, + object: { type: 'Identifier', start: 93, end: 100, name: 'console' }, + property: { type: 'Identifier', start: 101, end: 104, name: 'log' }, + computed: false, + optional: false + }, + arguments: [ + { type: 'Identifier', start: 105, end: 109, name: 'lib1' }, + { type: 'Identifier', start: 111, end: 115, name: 'lib2' }, + { type: 'Identifier', start: 117, end: 121, name: 'lib3' } + ], + optional: false + } + } + ], + sourceType: 'module' + }, + code: + "import { lib1 } from './lib1';\nimport { lib2 } from './lib2';\nimport { lib3 } from './lib3';\nconsole.log(lib1, lib2, lib3);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, diff --git a/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js b/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js index b1ea9e7e230..1cf66ad7870 100644 --- a/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js @@ -19,7 +19,52 @@ module.exports = { }); }, buildEnd() { - assert.deepStrictEqual(this.getModuleInfo(ID_MAIN), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_MAIN))), { + ast: { + type: 'Program', + start: 0, + end: 51, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 14, + imported: { type: 'Identifier', start: 9, end: 14, name: 'value' }, + local: { type: 'Identifier', start: 9, end: 14, name: 'value' } + } + ], + source: { type: 'Literal', start: 22, end: 29, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExpressionStatement', + start: 31, + end: 50, + expression: { + type: 'CallExpression', + start: 31, + end: 49, + callee: { + type: 'MemberExpression', + start: 31, + end: 42, + object: { type: 'Identifier', start: 31, end: 38, name: 'console' }, + property: { type: 'Identifier', start: 39, end: 42, name: 'log' }, + computed: false, + optional: false + }, + arguments: [{ type: 'Identifier', start: 43, end: 48, name: 'value' }], + optional: false + } + } + ], + sourceType: 'module' + }, + code: "import { value } from './lib';\nconsole.log(value);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, @@ -32,7 +77,52 @@ module.exports = { isExternal: false, meta: {} }); - assert.deepStrictEqual(this.getModuleInfo(ID_DEP), { + assert.deepStrictEqual(JSON.parse(JSON.stringify(this.getModuleInfo(ID_DEP))), { + ast: { + type: 'Program', + start: 0, + end: 51, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 30, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 14, + imported: { type: 'Identifier', start: 9, end: 14, name: 'value' }, + local: { type: 'Identifier', start: 9, end: 14, name: 'value' } + } + ], + source: { type: 'Literal', start: 22, end: 29, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExpressionStatement', + start: 31, + end: 50, + expression: { + type: 'CallExpression', + start: 31, + end: 49, + callee: { + type: 'MemberExpression', + start: 31, + end: 42, + object: { type: 'Identifier', start: 31, end: 38, name: 'console' }, + property: { type: 'Identifier', start: 39, end: 42, name: 'log' }, + computed: false, + optional: false + }, + arguments: [{ type: 'Identifier', start: 43, end: 48, name: 'value' }], + optional: false + } + } + ], + sourceType: 'module' + }, + code: "import { value } from './lib';\nconsole.log(value);\n", dynamicallyImportedIds: [], dynamicImporters: [], hasModuleSideEffects: true, diff --git a/test/function/samples/deprecated/manual-chunks-info/_config.js b/test/function/samples/deprecated/manual-chunks-info/_config.js index 8ffa3c9cb02..2b1476b517b 100644 --- a/test/function/samples/deprecated/manual-chunks-info/_config.js +++ b/test/function/samples/deprecated/manual-chunks-info/_config.js @@ -17,56 +17,213 @@ module.exports = { [getId('main'), 'external', getId('lib'), getId('dynamic')] ); assert.deepStrictEqual( - [...getModuleIds()].map(id => getModuleInfo(id)), + JSON.parse(JSON.stringify([...getModuleIds()].map(id => getModuleInfo(id)))), [ { - dynamicImporters: [], + ast: { + type: 'Program', + start: 0, + end: 123, + body: [ + { + type: 'ExportNamedDeclaration', + start: 0, + end: 43, + declaration: { + type: 'VariableDeclaration', + start: 7, + end: 43, + declarations: [ + { + type: 'VariableDeclarator', + start: 13, + end: 42, + id: { type: 'Identifier', start: 13, end: 20, name: 'promise' }, + init: { + type: 'ImportExpression', + start: 23, + end: 42, + source: { + type: 'Literal', + start: 30, + end: 41, + value: './dynamic', + raw: "'./dynamic'" + } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + }, + { + type: 'ExportNamedDeclaration', + start: 44, + end: 85, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 53, + end: 69, + local: { type: 'Identifier', start: 53, end: 60, name: 'default' }, + exported: { type: 'Identifier', start: 64, end: 69, name: 'value' } + } + ], + source: { type: 'Literal', start: 77, end: 84, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExportNamedDeclaration', + start: 86, + end: 122, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 95, + end: 103, + local: { type: 'Identifier', start: 95, end: 103, name: 'external' }, + exported: { type: 'Identifier', start: 95, end: 103, name: 'external' } + } + ], + source: { + type: 'Literal', + start: 111, + end: 121, + value: 'external', + raw: "'external'" + } + } + ], + sourceType: 'module' + }, + code: + "export const promise = import('./dynamic');\nexport { default as value } from './lib';\nexport { external } from 'external';\n", dynamicallyImportedIds: [getId('dynamic')], + dynamicImporters: [], hasModuleSideEffects: true, id: getId('main'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [], importedIds: [getId('lib'), 'external'], + importers: [], isEntry: true, isExternal: false, meta: {} }, { - dynamicImporters: [getId('dynamic')], + ast: null, + code: null, dynamicallyImportedIds: [], + dynamicImporters: [getId('dynamic')], hasModuleSideEffects: true, id: 'external', implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [getId('main')], importedIds: [], + importers: [getId('main')], isEntry: false, isExternal: true, meta: {} }, { - dynamicImporters: [], + ast: { + type: 'Program', + start: 0, + end: 19, + body: [ + { + type: 'ExportDefaultDeclaration', + start: 0, + end: 18, + declaration: { type: 'Literal', start: 15, end: 17, value: 42, raw: '42' } + } + ], + sourceType: 'module' + }, + code: 'export default 42;\n', dynamicallyImportedIds: [], + dynamicImporters: [], hasModuleSideEffects: true, id: getId('lib'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [getId('dynamic'), getId('main')], importedIds: [], + importers: [getId('dynamic'), getId('main')], isEntry: false, isExternal: false, meta: {} }, { - dynamicImporters: [getId('main')], + ast: { + type: 'Program', + start: 0, + end: 88, + body: [ + { + type: 'ExportNamedDeclaration', + start: 0, + end: 42, + declaration: { + type: 'VariableDeclaration', + start: 7, + end: 42, + declarations: [ + { + type: 'VariableDeclarator', + start: 13, + end: 41, + id: { type: 'Identifier', start: 13, end: 20, name: 'promise' }, + init: { + type: 'ImportExpression', + start: 23, + end: 41, + source: { + type: 'Literal', + start: 30, + end: 40, + value: 'external', + raw: "'external'" + } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + }, + { + type: 'ExportNamedDeclaration', + start: 43, + end: 87, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 52, + end: 71, + local: { type: 'Identifier', start: 52, end: 59, name: 'default' }, + exported: { type: 'Identifier', start: 63, end: 71, name: 'internal' } + } + ], + source: { type: 'Literal', start: 79, end: 86, value: './lib', raw: "'./lib'" } + } + ], + sourceType: 'module' + }, + code: + "export const promise = import('external');\nexport { default as internal } from './lib';\n", dynamicallyImportedIds: ['external'], + dynamicImporters: [getId('main')], hasModuleSideEffects: true, id: getId('dynamic'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [], importedIds: [getId('lib')], + importers: [], isEntry: false, isExternal: false, meta: {} diff --git a/test/function/samples/manual-chunks-info/_config.js b/test/function/samples/manual-chunks-info/_config.js index 7f5aeedff62..7f56a4a17ec 100644 --- a/test/function/samples/manual-chunks-info/_config.js +++ b/test/function/samples/manual-chunks-info/_config.js @@ -16,56 +16,213 @@ module.exports = { [getId('main'), 'external', getId('lib'), getId('dynamic')] ); assert.deepStrictEqual( - [...getModuleIds()].map(id => getModuleInfo(id)), + JSON.parse(JSON.stringify([...getModuleIds()].map(id => getModuleInfo(id)))), [ { - dynamicImporters: [], + ast: { + type: 'Program', + start: 0, + end: 123, + body: [ + { + type: 'ExportNamedDeclaration', + start: 0, + end: 43, + declaration: { + type: 'VariableDeclaration', + start: 7, + end: 43, + declarations: [ + { + type: 'VariableDeclarator', + start: 13, + end: 42, + id: { type: 'Identifier', start: 13, end: 20, name: 'promise' }, + init: { + type: 'ImportExpression', + start: 23, + end: 42, + source: { + type: 'Literal', + start: 30, + end: 41, + value: './dynamic', + raw: "'./dynamic'" + } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + }, + { + type: 'ExportNamedDeclaration', + start: 44, + end: 85, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 53, + end: 69, + local: { type: 'Identifier', start: 53, end: 60, name: 'default' }, + exported: { type: 'Identifier', start: 64, end: 69, name: 'value' } + } + ], + source: { type: 'Literal', start: 77, end: 84, value: './lib', raw: "'./lib'" } + }, + { + type: 'ExportNamedDeclaration', + start: 86, + end: 122, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 95, + end: 103, + local: { type: 'Identifier', start: 95, end: 103, name: 'external' }, + exported: { type: 'Identifier', start: 95, end: 103, name: 'external' } + } + ], + source: { + type: 'Literal', + start: 111, + end: 121, + value: 'external', + raw: "'external'" + } + } + ], + sourceType: 'module' + }, + code: + "export const promise = import('./dynamic');\nexport { default as value } from './lib';\nexport { external } from 'external';\n", dynamicallyImportedIds: [getId('dynamic')], + dynamicImporters: [], hasModuleSideEffects: true, id: getId('main'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [], importedIds: [getId('lib'), 'external'], + importers: [], isEntry: true, isExternal: false, meta: {} }, { - dynamicImporters: [getId('dynamic')], + ast: null, + code: null, dynamicallyImportedIds: [], + dynamicImporters: [getId('dynamic')], hasModuleSideEffects: true, id: 'external', implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [getId('main')], importedIds: [], + importers: [getId('main')], isEntry: false, isExternal: true, meta: {} }, { - dynamicImporters: [], + ast: { + type: 'Program', + start: 0, + end: 19, + body: [ + { + type: 'ExportDefaultDeclaration', + start: 0, + end: 18, + declaration: { type: 'Literal', start: 15, end: 17, value: 42, raw: '42' } + } + ], + sourceType: 'module' + }, + code: 'export default 42;\n', dynamicallyImportedIds: [], + dynamicImporters: [], hasModuleSideEffects: true, id: getId('lib'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [getId('dynamic'), getId('main')], importedIds: [], + importers: [getId('dynamic'), getId('main')], isEntry: false, isExternal: false, meta: {} }, { - dynamicImporters: [getId('main')], + ast: { + type: 'Program', + start: 0, + end: 88, + body: [ + { + type: 'ExportNamedDeclaration', + start: 0, + end: 42, + declaration: { + type: 'VariableDeclaration', + start: 7, + end: 42, + declarations: [ + { + type: 'VariableDeclarator', + start: 13, + end: 41, + id: { type: 'Identifier', start: 13, end: 20, name: 'promise' }, + init: { + type: 'ImportExpression', + start: 23, + end: 41, + source: { + type: 'Literal', + start: 30, + end: 40, + value: 'external', + raw: "'external'" + } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + }, + { + type: 'ExportNamedDeclaration', + start: 43, + end: 87, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 52, + end: 71, + local: { type: 'Identifier', start: 52, end: 59, name: 'default' }, + exported: { type: 'Identifier', start: 63, end: 71, name: 'internal' } + } + ], + source: { type: 'Literal', start: 79, end: 86, value: './lib', raw: "'./lib'" } + } + ], + sourceType: 'module' + }, + code: + "export const promise = import('external');\nexport { default as internal } from './lib';\n", dynamicallyImportedIds: ['external'], + dynamicImporters: [getId('main')], hasModuleSideEffects: true, id: getId('dynamic'), implicitlyLoadedAfterOneOf: [], implicitlyLoadedBefore: [], - importers: [], importedIds: [getId('lib')], + importers: [], isEntry: false, isExternal: false, meta: {} diff --git a/test/function/samples/plugin-module-information/_config.js b/test/function/samples/plugin-module-information/_config.js index 18a4a24189b..24e8e472dfc 100644 --- a/test/function/samples/plugin-module-information/_config.js +++ b/test/function/samples/plugin-module-information/_config.js @@ -15,6 +15,8 @@ module.exports = { plugins: { load(id) { assert.deepStrictEqual(this.getModuleInfo(id), { + ast: null, + code: null, dynamicImporters: [], dynamicallyImportedIds: [], hasModuleSideEffects: true, @@ -30,64 +32,318 @@ module.exports = { }, renderStart() { rendered = true; - assert.deepStrictEqual(Array.from(this.getModuleIds()), [ - ID_MAIN, - ID_FOO, - ID_PATH, - ID_NESTED - ]); - assert.deepStrictEqual(this.getModuleInfo(ID_MAIN), { - dynamicImporters: [], - dynamicallyImportedIds: [ID_NESTED, ID_PATH], - hasModuleSideEffects: true, - id: ID_MAIN, - implicitlyLoadedAfterOneOf: [], - implicitlyLoadedBefore: [], - importedIds: [ID_FOO], - importers: [], - isEntry: true, - isExternal: false, - meta: {} - }); - assert.deepStrictEqual(this.getModuleInfo(ID_FOO), { - dynamicImporters: [], - dynamicallyImportedIds: [], - hasModuleSideEffects: true, - id: ID_FOO, - implicitlyLoadedAfterOneOf: [], - implicitlyLoadedBefore: [], - importedIds: [ID_PATH], - importers: [ID_MAIN, ID_NESTED], - isEntry: false, - isExternal: false, - meta: {} - }); - assert.deepStrictEqual(this.getModuleInfo(ID_NESTED), { - dynamicImporters: [ID_MAIN], - dynamicallyImportedIds: [], - hasModuleSideEffects: true, - id: ID_NESTED, - implicitlyLoadedAfterOneOf: [], - implicitlyLoadedBefore: [], - importedIds: [ID_FOO], - importers: [], - isEntry: false, - isExternal: false, - meta: {} - }); - assert.deepStrictEqual(this.getModuleInfo(ID_PATH), { - dynamicImporters: [ID_MAIN], - dynamicallyImportedIds: [], - hasModuleSideEffects: true, - id: ID_PATH, - implicitlyLoadedAfterOneOf: [], - implicitlyLoadedBefore: [], - importedIds: [], - importers: [ID_FOO], - isEntry: false, - isExternal: true, - meta: {} - }); + assert.deepStrictEqual([...this.getModuleIds()], [ID_MAIN, ID_FOO, ID_PATH, ID_NESTED]); + assert.deepStrictEqual( + JSON.parse(JSON.stringify([...this.getModuleIds()].map(id => this.getModuleInfo(id)))), + [ + { + ast: { + type: 'Program', + start: 0, + end: 159, + body: [ + { + type: 'ExportNamedDeclaration', + start: 0, + end: 31, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 9, + end: 12, + local: { type: 'Identifier', start: 9, end: 12, name: 'foo' }, + exported: { type: 'Identifier', start: 9, end: 12, name: 'foo' } + } + ], + source: { + type: 'Literal', + start: 20, + end: 30, + value: './foo.js', + raw: "'./foo.js'" + } + }, + { + type: 'ExportNamedDeclaration', + start: 32, + end: 80, + declaration: { + type: 'VariableDeclaration', + start: 39, + end: 80, + declarations: [ + { + type: 'VariableDeclarator', + start: 45, + end: 79, + id: { type: 'Identifier', start: 45, end: 51, name: 'nested' }, + init: { + type: 'ImportExpression', + start: 54, + end: 79, + source: { + type: 'Literal', + start: 61, + end: 78, + value: './nested/nested', + raw: "'./nested/nested'" + } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + }, + { + type: 'ExportNamedDeclaration', + start: 81, + end: 116, + declaration: { + type: 'VariableDeclaration', + start: 88, + end: 116, + declarations: [ + { + type: 'VariableDeclarator', + start: 94, + end: 115, + id: { type: 'Identifier', start: 94, end: 98, name: 'path' }, + init: { + type: 'ImportExpression', + start: 101, + end: 115, + source: { + type: 'Literal', + start: 108, + end: 114, + value: 'path', + raw: "'path'" + } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + }, + { + type: 'ExportNamedDeclaration', + start: 117, + end: 158, + declaration: { + type: 'VariableDeclaration', + start: 124, + end: 158, + declarations: [ + { + type: 'VariableDeclarator', + start: 130, + end: 157, + id: { type: 'Identifier', start: 130, end: 139, name: 'pathAgain' }, + init: { + type: 'ImportExpression', + start: 142, + end: 157, + source: { type: 'Identifier', start: 149, end: 156, name: 'thePath' } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + } + ], + sourceType: 'module' + }, + code: + "export { foo } from './foo.js';\nexport const nested = import('./nested/nested');\nexport const path = import('path');\nexport const pathAgain = import(thePath);\n", + dynamicallyImportedIds: [ID_NESTED, ID_PATH], + dynamicImporters: [], + hasModuleSideEffects: true, + id: ID_MAIN, + implicitlyLoadedAfterOneOf: [], + implicitlyLoadedBefore: [], + importedIds: [ID_FOO], + importers: [], + isEntry: true, + isExternal: false, + meta: {} + }, + { + ast: { + type: 'Program', + start: 0, + end: 66, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 24, + specifiers: [ + { + type: 'ImportDefaultSpecifier', + start: 7, + end: 11, + local: { type: 'Identifier', start: 7, end: 11, name: 'path' } + } + ], + source: { type: 'Literal', start: 17, end: 23, value: 'path', raw: "'path'" } + }, + { + type: 'ExportNamedDeclaration', + start: 26, + end: 65, + declaration: { + type: 'VariableDeclaration', + start: 33, + end: 65, + declarations: [ + { + type: 'VariableDeclarator', + start: 39, + end: 64, + id: { type: 'Identifier', start: 39, end: 42, name: 'foo' }, + init: { + type: 'CallExpression', + start: 45, + end: 64, + callee: { + type: 'MemberExpression', + start: 45, + end: 57, + object: { type: 'Identifier', start: 45, end: 49, name: 'path' }, + property: { type: 'Identifier', start: 50, end: 57, name: 'resolve' }, + computed: false, + optional: false + }, + arguments: [ + { type: 'Literal', start: 58, end: 63, value: 'foo', raw: "'foo'" } + ], + optional: false + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + } + ], + sourceType: 'module' + }, + code: "import path from 'path';\n\nexport const foo = path.resolve('foo');\n", + dynamicallyImportedIds: [], + dynamicImporters: [], + hasModuleSideEffects: true, + id: ID_FOO, + implicitlyLoadedAfterOneOf: [], + implicitlyLoadedBefore: [], + importedIds: [ID_PATH], + importers: [ID_MAIN, ID_NESTED], + isEntry: false, + isExternal: false, + meta: {} + }, + { + ast: null, + code: null, + dynamicallyImportedIds: [], + dynamicImporters: [ID_MAIN], + hasModuleSideEffects: true, + id: ID_PATH, + implicitlyLoadedAfterOneOf: [], + implicitlyLoadedBefore: [], + importedIds: [], + importers: [ID_FOO], + isEntry: false, + isExternal: true, + meta: {} + }, + { + ast: { + type: 'Program', + start: 0, + end: 72, + body: [ + { + type: 'ImportDeclaration', + start: 0, + end: 32, + specifiers: [ + { + type: 'ImportSpecifier', + start: 9, + end: 12, + imported: { type: 'Identifier', start: 9, end: 12, name: 'foo' }, + local: { type: 'Identifier', start: 9, end: 12, name: 'foo' } + } + ], + source: { + type: 'Literal', + start: 20, + end: 31, + value: '../foo.js', + raw: "'../foo.js'" + } + }, + { + type: 'ExportNamedDeclaration', + start: 34, + end: 71, + declaration: { + type: 'VariableDeclaration', + start: 41, + end: 71, + declarations: [ + { + type: 'VariableDeclarator', + start: 47, + end: 70, + id: { type: 'Identifier', start: 47, end: 53, name: 'nested' }, + init: { + type: 'BinaryExpression', + start: 56, + end: 70, + left: { + type: 'Literal', + start: 56, + end: 64, + value: 'nested', + raw: "'nested'" + }, + operator: '+', + right: { type: 'Identifier', start: 67, end: 70, name: 'foo' } + } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + } + ], + sourceType: 'module' + }, + code: "import { foo } from '../foo.js';\n\nexport const nested = 'nested' + foo;\n", + dynamicallyImportedIds: [], + dynamicImporters: [ID_MAIN], + hasModuleSideEffects: true, + id: ID_NESTED, + implicitlyLoadedAfterOneOf: [], + implicitlyLoadedBefore: [], + importedIds: [ID_FOO], + importers: [], + isEntry: false, + isExternal: false, + meta: {} + } + ] + ); } } }, From e08e4cd0b02960a0aa024dd06a7fa0e9e64aaab0 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Wed, 7 Oct 2020 06:52:32 +0200 Subject: [PATCH 3/4] Add moduleParsed hook --- cli/help.md | 6 +- docs/01-command-line-reference.md | 6 +- docs/05-plugin-development.md | 7 +- src/Bundle.ts | 2 +- src/Chunk.ts | 12 +- src/ExternalModule.ts | 37 ++++-- src/Graph.ts | 50 +------- src/Module.ts | 70 ++++++++--- src/ModuleLoader.ts | 5 +- src/rollup/types.d.ts | 10 +- src/utils/PluginDriver.ts | 1 + src/utils/transform.ts | 2 +- src/utils/traverseStaticDependencies.ts | 2 +- .../samples/module-parsed-hook/_config.js | 111 ++++++++++++++++++ .../samples/module-parsed-hook/dep.js | 1 + .../samples/module-parsed-hook/main.js | 1 + test/incremental/index.js | 72 +++++++----- 17 files changed, 267 insertions(+), 128 deletions(-) create mode 100644 test/function/samples/module-parsed-hook/_config.js create mode 100644 test/function/samples/module-parsed-hook/dep.js create mode 100644 test/function/samples/module-parsed-hook/main.js diff --git a/cli/help.md b/cli/help.md index 396ad70bc59..40e3d3e7911 100644 --- a/cli/help.md +++ b/cli/help.md @@ -32,6 +32,7 @@ Basic options: --exports Specify export mode (auto, default, named, none) --extend Extend global variable defined by --name --no-externalLiveBindings Do not generate code to support live bindings +--failAfterWarnings Exit with an error if the build produced warnings --footer Code to insert at end of bundle (outside wrapper) --no-freeze Do not freeze namespace objects --no-hoistTransitiveImports Do not hoist transitive imports into entry chunks @@ -46,14 +47,13 @@ Basic options: --preferConst Use `const` instead of `var` for exports --no-preserveEntrySignatures Avoid facade chunks for entry points --preserveModules Preserve module structure ---preserveModulesRoot Preserved modules under this path are rooted in output `dir` +--preserveModulesRoot Put preserved modules under this path at root level --preserveSymlinks Do not follow symlinks when resolving files --shimMissingExports Create shim variables for missing exports --silent Don't print warnings ---failAfterWarnings Exit with an error code if there was a warning during the build --sourcemapExcludeSources Do not include source code in source maps --sourcemapFile Specify bundle position for source maps ---stdin=ext Specify file extension used for stdin input - default is none +--stdin=ext Specify file extension used for stdin input --no-stdin Do not read "-" from stdin --no-strict Don't emit `"use strict";` in the generated modules --strictDeprecations Throw errors for deprecated features diff --git a/docs/01-command-line-reference.md b/docs/01-command-line-reference.md index 7ea215bf3e0..56c02cc81f6 100755 --- a/docs/01-command-line-reference.md +++ b/docs/01-command-line-reference.md @@ -290,6 +290,7 @@ Many options have command line equivalents. In those cases, any arguments passed --exports Specify export mode (auto, default, named, none) --extend Extend global variable defined by --name --no-externalLiveBindings Do not generate code to support live bindings +--failAfterWarnings Exit with an error if the build produced warnings --footer Code to insert at end of bundle (outside wrapper) --no-freeze Do not freeze namespace objects --no-hoistTransitiveImports Do not hoist transitive imports into entry chunks @@ -304,14 +305,13 @@ Many options have command line equivalents. In those cases, any arguments passed --preferConst Use `const` instead of `var` for exports --no-preserveEntrySignatures Avoid facade chunks for entry points --preserveModules Preserve module structure ---preserveModulesRoot Preserved modules under this path are rooted in output `dir` +--preserveModulesRoot Put preserved modules under this path at root level --preserveSymlinks Do not follow symlinks when resolving files --shimMissingExports Create shim variables for missing exports --silent Don't print warnings ---failAfterWarnings Exit with an error code if there was a warning during the build --sourcemapExcludeSources Do not include source code in source maps --sourcemapFile Specify bundle position for source maps ---stdin=ext Specify file extension used for stdin input - default is none +--stdin=ext Specify file extension used for stdin input --no-stdin Do not read "-" from stdin --no-strict Don't emit `"use strict";` in the generated modules --strictDeprecations Throw errors for deprecated features diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md index 10c07d74f4d..7b65a36f376 100644 --- a/docs/05-plugin-development.md +++ b/docs/05-plugin-development.md @@ -624,6 +624,8 @@ Returns additional information about the module in question in the form ``` { id: string, // the id of the module, for convenience + code: string | null, // the source code of the module, `null` if external or not yet available + ast: ESTree.Program, // the parsed abstract syntax tree if available isEntry: boolean, // is this a user- or plugin-defined entry point isExternal: boolean, // for external modules that are referenced but not included in the graph importedIds: string[], // the module ids statically imported by this module @@ -636,7 +638,10 @@ Returns additional information about the module in question in the form } ``` -This utility function returns `null` if the module id cannot be found. +During the build, this object represents currently available information about the module. Before the [`buildEnd`](guide/en/#buildend) hook, this information may be incomplete as e.g. + the `importedIds` are not yet resolved or additional `importers` are discovered. + +Returns `null` if the module id cannot be found. #### `this.meta: {rollupVersion: string, watchMode: boolean}` diff --git a/src/Bundle.ts b/src/Bundle.ts index 5cb835f4fbe..622293b6a38 100644 --- a/src/Bundle.ts +++ b/src/Bundle.ts @@ -267,7 +267,7 @@ function getIncludedModules(modulesById: Map): return [...modulesById.values()].filter( module => module instanceof Module && - (module.isIncluded() || module.isEntryPoint || module.includedDynamicImporters.length > 0) + (module.isIncluded() || module.info.isEntry || module.includedDynamicImporters.length > 0) ) as Module[]; } diff --git a/src/Chunk.ts b/src/Chunk.ts index 72a2a8a0b5c..f354792a265 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -162,7 +162,7 @@ export default class Chunk { } if ( !chunk.dependencies.has(chunkByModule.get(facadedModule)!) && - facadedModule.moduleSideEffects && + facadedModule.info.hasModuleSideEffects && facadedModule.hasEffects() ) { chunk.dependencies.add(chunkByModule.get(facadedModule)!); @@ -234,7 +234,7 @@ export default class Chunk { if (this.isEmpty && module.isIncluded()) { this.isEmpty = false; } - if (module.isEntryPoint || outputOptions.preserveModules) { + if (module.info.isEntry || outputOptions.preserveModules) { this.entryModules.push(module); } for (const importer of module.includedDynamicImporters) { @@ -307,7 +307,7 @@ export default class Chunk { } else { assignExportsToNames(remainingExports, this.exportsByName, this.exportNamesByVariable); } - if (this.outputOptions.preserveModules || (this.facadeModule && this.facadeModule.isEntryPoint)) + if (this.outputOptions.preserveModules || (this.facadeModule && this.facadeModule.info.isEntry)) this.exportMode = getExportMode( this, this.outputOptions, @@ -473,7 +473,7 @@ export default class Chunk { exports: this.getExportNames(), facadeModuleId: facadeModule && facadeModule.id, isDynamicEntry: this.dynamicEntryModules.length > 0, - isEntry: facadeModule !== null && facadeModule.isEntryPoint, + isEntry: facadeModule !== null && facadeModule.info.isEntry, isImplicitEntry: this.implicitEntryModules.length > 0, modules: this.renderedModules, get name() { @@ -718,7 +718,7 @@ export default class Chunk { intro: addons.intro!, isEntryModuleFacade: this.outputOptions.preserveModules || - (this.facadeModule !== null && this.facadeModule.isEntryPoint), + (this.facadeModule !== null && this.facadeModule.info.isEntry), namedExportsMode: this.exportMode !== 'default', outro: addons.outro!, usesTopLevelAwait, @@ -1307,7 +1307,7 @@ export default class Chunk { } if ( this.includedNamespaces.has(module) || - (module.isEntryPoint && module.preserveSignature !== false) || + (module.info.isEntry && module.preserveSignature !== false) || module.includedDynamicImporters.some(importer => this.chunkByModule.get(importer) !== this) ) { this.ensureReexportsAreAvailableForModule(module); diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index 9fdd86c4990..aef2796394f 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -1,22 +1,23 @@ import ExternalVariable from './ast/variables/ExternalVariable'; import { CustomPluginOptions, + ModuleInfo, NormalizedInputOptions, NormalizedOutputOptions } from './rollup/types'; +import { EMPTY_ARRAY } from './utils/blank'; import { makeLegal } from './utils/identifierHelpers'; import { isAbsolute, normalize, relative } from './utils/path'; export default class ExternalModule { chunk: void; - // TODO Lukas get from resolution - custom: CustomPluginOptions = {}; declarations: { [name: string]: ExternalVariable }; defaultVariableName = ''; dynamicImporters: string[] = []; execIndex: number; exportedVariables: Map; importers: string[] = []; + info: ModuleInfo; mostCommonSuggestion = 0; namespaceVariableName = ''; nameSuggestions: { [name: string]: number }; @@ -30,19 +31,35 @@ export default class ExternalModule { constructor( private readonly options: NormalizedInputOptions, public readonly id: string, - public moduleSideEffects: boolean | 'no-treeshake', - public meta: CustomPluginOptions + hasModuleSideEffects: boolean | 'no-treeshake', + meta: CustomPluginOptions ) { - this.id = id; this.execIndex = Infinity; - this.moduleSideEffects = moduleSideEffects; - - const parts = id.split(/[\\/]/); - this.suggestedVariableName = makeLegal(parts.pop()!); - + this.suggestedVariableName = makeLegal(id.split(/[\\/]/).pop()!); this.nameSuggestions = Object.create(null); this.declarations = Object.create(null); this.exportedVariables = new Map(); + + const module = this; + this.info = { + ast: null, + code: null, + dynamicallyImportedIds: EMPTY_ARRAY, + get dynamicImporters() { + return module.dynamicImporters.sort(); + }, + hasModuleSideEffects, + id, + implicitlyLoadedAfterOneOf: EMPTY_ARRAY, + implicitlyLoadedBefore: EMPTY_ARRAY, + importedIds: EMPTY_ARRAY, + get importers() { + return module.importers.sort(); + }, + isEntry: false, + isExternal: true, + meta + }; } getVariableForExportName(name: string): ExternalVariable { diff --git a/src/Graph.ts b/src/Graph.ts index be0e034e91e..ebbc12b6f8f 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -12,11 +12,9 @@ import { RollupWatcher, SerializablePluginCache } from './rollup/types'; -import { EMPTY_ARRAY } from './utils/blank'; import { BuildPhase } from './utils/buildPhase'; import { errImplicitDependantIsNotIncluded, error } from './utils/error'; import { analyseModuleExecution } from './utils/executionOrder'; -import { getId } from './utils/getId'; import { PluginDriver } from './utils/PluginDriver'; import relativeId from './utils/relativeId'; import { timeEnd, timeStart } from './utils/timers'; @@ -136,49 +134,7 @@ export default class Graph { getModuleInfo = (moduleId: string): ModuleInfo | null => { const foundModule = this.modulesById.get(moduleId); if (!foundModule) return null; - return { - ast: (foundModule as Module).ast?.esTreeNode || null, - code: foundModule instanceof Module ? foundModule.code : null, - get dynamicallyImportedIds() { - if (foundModule instanceof Module) { - const dynamicallyImportedIds: string[] = []; - for (const { resolution } of foundModule.dynamicImports) { - if (resolution instanceof Module || resolution instanceof ExternalModule) { - dynamicallyImportedIds.push(resolution.id); - } - } - return dynamicallyImportedIds; - } - return EMPTY_ARRAY; - }, - get dynamicImporters() { - return foundModule!.dynamicImporters.sort(); - }, - hasModuleSideEffects: foundModule.moduleSideEffects, - id: foundModule.id, - get implicitlyLoadedAfterOneOf() { - return foundModule instanceof Module - ? Array.from(foundModule.implicitlyLoadedAfter, getId) - : EMPTY_ARRAY; - }, - get implicitlyLoadedBefore() { - return foundModule instanceof Module - ? Array.from(foundModule.implicitlyLoadedBefore, getId) - : []; - }, - get importedIds() { - if (foundModule instanceof Module) { - return Array.from(foundModule.sources, source => foundModule.resolvedIds[source].id); - } - return EMPTY_ARRAY; - }, - get importers() { - return foundModule!.importers.sort(); - }, - isEntry: foundModule instanceof Module && foundModule.isEntryPoint, - isExternal: foundModule instanceof ExternalModule, - meta: foundModule.meta - }; + return foundModule.info; }; private async generateModuleGraph(): Promise { @@ -213,7 +169,7 @@ export default class Graph { this.needsTreeshakingPass = false; for (const module of this.modules) { if (module.isExecuted) { - if (module.moduleSideEffects === 'no-treeshake') { + if (module.info.hasModuleSideEffects === 'no-treeshake') { module.includeAllInBundle(); } else { module.include(); @@ -228,7 +184,7 @@ export default class Graph { for (const externalModule of this.externalModules) externalModule.warnUnusedImports(); for (const module of this.implicitEntryModules) { for (const dependant of module.implicitlyLoadedAfter) { - if (!(dependant.isEntryPoint || dependant.isIncluded())) { + if (!(dependant.info.isEntry || dependant.isIncluded())) { error(errImplicitDependantIsNotIncluded(dependant)); } } diff --git a/src/Module.ts b/src/Module.ts index 71354bbec6a..3d42a0227f0 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -33,6 +33,7 @@ import { DecodedSourceMapOrMissing, EmittedFile, ExistingDecodedSourceMap, + ModuleInfo, ModuleJSON, ModuleOptions, NormalizedInputOptions, @@ -119,7 +120,7 @@ function tryParse( acornOptions: acorn.Options ): acorn.Node { try { - return Parser.parse(module.code!, { + return Parser.parse(module.info.code!, { ...acornOptions, onComment: (block: boolean, text: string, start: number, end: number) => module.comments.push({ block, text, start, end }) @@ -187,7 +188,6 @@ export default class Module { ast: Program | null = null; chunkFileNames = new Set(); chunkName: string | null = null; - code: string | null = null; comments: CommentDescription[] = []; dependencies = new Set(); dynamicDependencies = new Set(); @@ -209,6 +209,7 @@ export default class Module { importMetas: MetaProperty[] = []; imports = new Set(); includedDynamicImporters: Module[] = []; + info: ModuleInfo; isExecuted = false; isUserDefinedEntryPoint = false; namespace!: NamespaceVariable; @@ -243,13 +244,48 @@ export default class Module { private readonly graph: Graph, public readonly id: string, private readonly options: NormalizedInputOptions, - public isEntryPoint: boolean, - public moduleSideEffects: boolean | 'no-treeshake', + isEntry: boolean, + hasModuleSideEffects: boolean | 'no-treeshake', public syntheticNamedExports: boolean | string, - public meta: CustomPluginOptions + meta: CustomPluginOptions ) { this.excludeFromSourcemap = /\0/.test(id); this.context = options.moduleContext(id); + + const module = this; + this.info = { + ast: null, + code: null, + get dynamicallyImportedIds() { + const dynamicallyImportedIds: string[] = []; + for (const { resolution } of module.dynamicImports) { + if (resolution instanceof Module || resolution instanceof ExternalModule) { + dynamicallyImportedIds.push(resolution.id); + } + } + return dynamicallyImportedIds; + }, + get dynamicImporters() { + return module.dynamicImporters.sort(); + }, + hasModuleSideEffects, + id, + get implicitlyLoadedAfterOneOf() { + return Array.from(module.implicitlyLoadedAfter, getId); + }, + get implicitlyLoadedBefore() { + return Array.from(module.implicitlyLoadedBefore, getId); + }, + get importedIds() { + return Array.from(module.sources, source => module.resolvedIds[source].id); + }, + get importers() { + return module.importers.sort(); + }, + isEntry, + isExternal: false, + meta + }; } basename() { @@ -300,7 +336,7 @@ export default class Module { const possibleDependencies = new Set(this.dependencies); let dependencyVariables = this.imports; if ( - this.isEntryPoint || + this.info.isEntry || this.includedDynamicImporters.length > 0 || this.namespace.included || this.implicitlyLoadedAfter.size > 0 @@ -323,11 +359,12 @@ export default class Module { } relevantDependencies.add(variable.module!); } - if (this.options.treeshake && this.moduleSideEffects !== 'no-treeshake') { + if (this.options.treeshake && this.info.hasModuleSideEffects !== 'no-treeshake') { for (const dependency of possibleDependencies) { if ( !( - dependency.moduleSideEffects || additionalSideEffectModules.has(dependency as Module) + dependency.info.hasModuleSideEffects || + additionalSideEffectModules.has(dependency as Module) ) || relevantDependencies.has(dependency) ) { @@ -526,7 +563,7 @@ export default class Module { hasEffects() { return ( - this.moduleSideEffects === 'no-treeshake' || + this.info.hasModuleSideEffects === 'no-treeshake' || (this.ast!.included && this.ast!.hasEffects(createHasEffectsContext())) ); } @@ -628,7 +665,7 @@ export default class Module { alwaysRemovedCode?: [number, number][]; transformFiles?: EmittedFile[] | undefined; }) { - this.code = code; + this.info.code = code; this.originalCode = originalCode; this.originalSourcemap = originalSourcemap; this.sourcemapChain = sourcemapChain; @@ -701,6 +738,7 @@ export default class Module { this.scope = new ModuleScope(this.graph.scope, this.astContext); this.namespace = new NamespaceVariable(this.astContext, this.syntheticNamedExports); this.ast = new Program(ast, { type: 'Module', context: this.astContext }, this.scope); + this.info.ast = ast; timeEnd('analyse ast', 3); } @@ -709,12 +747,12 @@ export default class Module { return { alwaysRemovedCode: this.alwaysRemovedCode, ast: this.ast!.esTreeNode, - code: this.code!, + code: this.info.code!, customTransformCache: this.customTransformCache, dependencies: Array.from(this.dependencies, getId), id: this.id, - meta: this.meta, - moduleSideEffects: this.moduleSideEffects, + meta: this.info.meta, + moduleSideEffects: this.info.hasModuleSideEffects, originalCode: this.originalCode, originalSourcemap: this.originalSourcemap, resolvedIds: this.resolvedIds, @@ -762,13 +800,13 @@ export default class Module { syntheticNamedExports }: Partial>) { if (moduleSideEffects != null) { - this.moduleSideEffects = moduleSideEffects; + this.info.hasModuleSideEffects = moduleSideEffects; } if (syntheticNamedExports != null) { this.syntheticNamedExports = syntheticNamedExports; } if (meta != null) { - this.meta = { ...this.meta, ...meta }; + this.info.meta = { ...this.info.meta, ...meta }; } } @@ -887,7 +925,7 @@ export default class Module { private addLocationToLogProps(props: RollupLogProps, pos: number): void { props.id = this.id; props.pos = pos; - let code = this.code; + let code = this.info.code; let { column, line } = locate(code!, pos, { offsetLine: 1 }); try { ({ column, line } = getOriginalLocation(this.sourcemapChain, { column, line })); diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index e7562abb563..d7e89bb88af 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -183,7 +183,7 @@ export class ModuleLoader { this.loadEntryModule(unresolvedModule.id, false, unresolvedModule.importer, null).then( async entryModule => { addChunkNamesToModule(entryModule, unresolvedModule, false); - if (!entryModule.isEntryPoint) { + if (!entryModule.info.isEntry) { this.implicitEntryModules.add(entryModule); const implicitlyLoadedAfterModules = await Promise.all( implicitlyLoadedAfter.map(id => @@ -299,7 +299,7 @@ export class ModuleLoader { const existingModule = this.modulesById.get(id); if (existingModule instanceof Module) { if (isEntry) { - existingModule.isEntryPoint = true; + existingModule.info.isEntry = true; this.implicitEntryModules.delete(existingModule); for (const dependant of existingModule.implicitlyLoadedAfter) { dependant.implicitlyLoadedBefore.delete(existingModule); @@ -321,6 +321,7 @@ export class ModuleLoader { this.modulesById.set(id, module); this.graph.watchFiles[id] = true; await this.addModuleSource(id, importer, module); + await this.pluginDriver.hookParallel('moduleParsed', [module.info]); await Promise.all([ this.fetchStaticDependencies(module), this.fetchDynamicDependencies(module) diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 9291529ebaf..7366eeac9de 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -179,10 +179,6 @@ export interface CustomPluginOptions { [plugin: string]: any; } -export interface CustomPluginOptions { - [plugin: string]: any; -} - export interface PluginContext extends MinimalPluginContext { addWatchFile: (id: string) => void; cache: PluginCache; @@ -269,6 +265,8 @@ export type TransformHook = ( id: string ) => Promise | TransformResult; +export type ModuleParsedHook = (this: PluginContext, info: ModuleInfo) => Promise | void; + export type RenderChunkHook = ( this: PluginContext, code: string, @@ -348,8 +346,8 @@ export interface PluginHooks extends OutputPluginHooks { buildEnd: (this: PluginContext, err?: Error) => Promise | void; buildStart: (this: PluginContext, options: NormalizedInputOptions) => Promise | void; load: LoadHook; + moduleParsed: ModuleParsedHook; options: (this: MinimalPluginContext, options: InputOptions) => InputOptions | null | undefined; - // TODO Lukas parsedModule hook resolveDynamicImport: ResolveDynamicImportHook; resolveId: ResolveIdHook; transform: TransformHook; @@ -397,6 +395,7 @@ export type AsyncPluginHooks = | 'buildStart' | 'generateBundle' | 'load' + | 'moduleParsed' | 'renderChunk' | 'renderError' | 'renderStart' @@ -433,6 +432,7 @@ export type ParallelPluginHooks = | 'buildStart' | 'footer' | 'intro' + | 'moduleParsed' | 'outro' | 'renderError' | 'renderStart' diff --git a/src/utils/PluginDriver.ts b/src/utils/PluginDriver.ts index 863a7a91ac1..b688a556295 100644 --- a/src/utils/PluginDriver.ts +++ b/src/utils/PluginDriver.ts @@ -48,6 +48,7 @@ const inputHookNames: { buildEnd: 1, buildStart: 1, load: 1, + moduleParsed: 1, options: 1, resolveDynamicImport: 1, resolveId: 1, diff --git a/src/utils/transform.ts b/src/utils/transform.ts index 7905097b669..dc26ba75b13 100644 --- a/src/utils/transform.ts +++ b/src/utils/transform.ts @@ -161,7 +161,7 @@ export default function transform( ast, code, customTransformCache, - meta: module.meta, + meta: module.info.meta, originalCode, originalSourcemap, sourcemapChain, diff --git a/src/utils/traverseStaticDependencies.ts b/src/utils/traverseStaticDependencies.ts index c32a516b7f6..e2828eb83bf 100644 --- a/src/utils/traverseStaticDependencies.ts +++ b/src/utils/traverseStaticDependencies.ts @@ -10,7 +10,7 @@ export function markModuleAndImpureDependenciesAsExecuted(baseModule: Module) { if ( !(dependency instanceof ExternalModule) && !dependency.isExecuted && - (dependency.moduleSideEffects || module.implicitlyLoadedBefore.has(dependency)) && + (dependency.info.hasModuleSideEffects || module.implicitlyLoadedBefore.has(dependency)) && !visitedModules.has(dependency.id) ) { dependency.isExecuted = true; diff --git a/test/function/samples/module-parsed-hook/_config.js b/test/function/samples/module-parsed-hook/_config.js new file mode 100644 index 00000000000..4b5c11e0d90 --- /dev/null +++ b/test/function/samples/module-parsed-hook/_config.js @@ -0,0 +1,111 @@ +const assert = require('assert'); +const path = require('path'); + +const parsedModules = []; + +const ID_MAIN = path.join(__dirname, 'main.js'); +const ID_DEP = path.join(__dirname, 'dep.js'); + +module.exports = { + description: 'calls the moduleParsedHook once a module is parsed', + options: { + plugins: { + name: 'test-plugin', + moduleParsed(moduleInfo) { + parsedModules.push(moduleInfo); + }, + buildEnd() { + assert.deepStrictEqual(JSON.parse(JSON.stringify(parsedModules)), [ + { + ast: { + type: 'Program', + start: 0, + end: 34, + body: [ + { + type: 'ExportNamedDeclaration', + start: 0, + end: 33, + declaration: null, + specifiers: [ + { + type: 'ExportSpecifier', + start: 9, + end: 14, + local: { type: 'Identifier', start: 9, end: 14, name: 'value' }, + exported: { type: 'Identifier', start: 9, end: 14, name: 'value' } + } + ], + source: { + type: 'Literal', + start: 22, + end: 32, + value: './dep.js', + raw: "'./dep.js'" + } + } + ], + sourceType: 'module' + }, + code: "export { value } from './dep.js';\n", + dynamicallyImportedIds: [], + dynamicImporters: [], + hasModuleSideEffects: true, + id: ID_MAIN, + implicitlyLoadedAfterOneOf: [], + implicitlyLoadedBefore: [], + importedIds: [ID_DEP], + importers: [], + isEntry: true, + isExternal: false, + meta: {} + }, + { + ast: { + type: 'Program', + start: 0, + end: 25, + body: [ + { + type: 'ExportNamedDeclaration', + start: 0, + end: 24, + declaration: { + type: 'VariableDeclaration', + start: 7, + end: 24, + declarations: [ + { + type: 'VariableDeclarator', + start: 13, + end: 23, + id: { type: 'Identifier', start: 13, end: 18, name: 'value' }, + init: { type: 'Literal', start: 21, end: 23, value: 42, raw: '42' } + } + ], + kind: 'const' + }, + specifiers: [], + source: null + } + ], + sourceType: 'module' + }, + code: 'export const value = 42;\n', + dynamicallyImportedIds: [], + dynamicImporters: [], + hasModuleSideEffects: true, + id: ID_DEP, + implicitlyLoadedAfterOneOf: [], + implicitlyLoadedBefore: [], + importedIds: [], + importers: [ID_MAIN], + isEntry: false, + isExternal: false, + meta: {} + } + ]); + } + } + } +}; diff --git a/test/function/samples/module-parsed-hook/dep.js b/test/function/samples/module-parsed-hook/dep.js new file mode 100644 index 00000000000..46d3ca8c61f --- /dev/null +++ b/test/function/samples/module-parsed-hook/dep.js @@ -0,0 +1 @@ +export const value = 42; diff --git a/test/function/samples/module-parsed-hook/main.js b/test/function/samples/module-parsed-hook/main.js new file mode 100644 index 00000000000..b46fed2e121 --- /dev/null +++ b/test/function/samples/module-parsed-hook/main.js @@ -0,0 +1 @@ +export { value } from './dep.js'; diff --git a/test/incremental/index.js b/test/incremental/index.js index 185e4045eff..55375856b59 100644 --- a/test/incremental/index.js +++ b/test/incremental/index.js @@ -10,7 +10,7 @@ describe('incremental', () => { const plugin = { resolveId: id => { - resolveIdCalls += 1; + resolveIdCalls++; return id === 'external' ? false : id; }, @@ -19,7 +19,7 @@ describe('incremental', () => { }, transform: code => { - transformCalls += 1; + transformCalls++; return code; } }; @@ -40,19 +40,19 @@ describe('incremental', () => { input: 'entry', plugins: [plugin] }); - assert.equal(resolveIdCalls, 2); - assert.equal(transformCalls, 2); + assert.strictEqual(resolveIdCalls, 2); + assert.strictEqual(transformCalls, 2); const secondBundle = await rollup.rollup({ input: 'entry', plugins: [plugin], cache: firstBundle }); - assert.equal(resolveIdCalls, 3); // +1 for entry point which is resolved every time - assert.equal(transformCalls, 2); + assert.strictEqual(resolveIdCalls, 3); // +1 for entry point which is resolved every time + assert.strictEqual(transformCalls, 2); const result = await executeBundle(secondBundle); - assert.equal(result, 42); + assert.strictEqual(result, 42); }); it('does not resolve dynamic ids and transforms in the second time', () => { @@ -66,8 +66,8 @@ describe('incremental', () => { plugins: [plugin] }) .then(bundle => { - assert.equal(resolveIdCalls, 2); - assert.equal(transformCalls, 2); + assert.strictEqual(resolveIdCalls, 2); + assert.strictEqual(transformCalls, 2); return rollup.rollup({ input: 'entry', plugins: [plugin], @@ -75,8 +75,8 @@ describe('incremental', () => { }); }) .then(bundle => { - assert.equal(resolveIdCalls, 3); // +1 for entry point which is resolved every time - assert.equal(transformCalls, 2); + assert.strictEqual(resolveIdCalls, 3); // +1 for entry point which is resolved every time + assert.strictEqual(transformCalls, 2); }); }); @@ -89,10 +89,10 @@ describe('incremental', () => { plugins: [plugin] }) .then(bundle => { - assert.equal(transformCalls, 2); + assert.strictEqual(transformCalls, 2); return executeBundle(bundle).then(result => { - assert.equal(result, 42); + assert.strictEqual(result, 42); modules.foo = `export default 43`; cache = bundle.cache; @@ -106,12 +106,12 @@ describe('incremental', () => { }); }) .then(bundle => { - assert.equal(transformCalls, 3); + assert.strictEqual(transformCalls, 3); return executeBundle(bundle); }) .then(result => { - assert.equal(result, 43); + assert.strictEqual(result, 43); }); }); @@ -124,10 +124,10 @@ describe('incremental', () => { plugins: [plugin] }) .then(bundle => { - assert.equal(resolveIdCalls, 2); + assert.strictEqual(resolveIdCalls, 2); return executeBundle(bundle).then(result => { - assert.equal(result, 42); + assert.strictEqual(result, 42); modules.entry = `import bar from 'bar'; export default bar;`; cache = bundle.cache; @@ -141,12 +141,12 @@ describe('incremental', () => { }); }) .then(bundle => { - assert.equal(resolveIdCalls, 4); + assert.strictEqual(resolveIdCalls, 4); return executeBundle(bundle); }) .then(result => { - assert.equal(result, 21); + assert.strictEqual(result, 21); }); }); @@ -162,10 +162,10 @@ describe('incremental', () => { plugins: [plugin] }) .then(bundle => { - assert.equal(resolveIdCalls, 3); + assert.strictEqual(resolveIdCalls, 3); return executeBundle(bundle, require).then(result => { - assert.equal(result, 43); + assert.strictEqual(result, 43); cache = bundle.cache; }); }) @@ -177,12 +177,12 @@ describe('incremental', () => { }); }) .then(bundle => { - assert.equal(resolveIdCalls, 4); + assert.strictEqual(resolveIdCalls, 4); return executeBundle(bundle, require); }) .then(result => { - assert.equal(result, 43); + assert.strictEqual(result, 43); }); }); @@ -243,7 +243,7 @@ describe('incremental', () => { return executeBundle(bundle); }) .then(result => { - assert.equal(result, 63); + assert.strictEqual(result, 63); }); }); }); @@ -261,8 +261,8 @@ describe('incremental', () => { plugins: [plugin] }) .then(bundle => { - assert.equal(bundle.cache.modules[0].id, 'foo'); - assert.equal(bundle.cache.modules[1].id, 'entry'); + assert.strictEqual(bundle.cache.modules[0].id, 'foo'); + assert.strictEqual(bundle.cache.modules[1].id, 'entry'); assert.deepEqual(bundle.cache.modules[1].resolvedIds, { foo: { @@ -284,10 +284,11 @@ describe('incremental', () => { }); it('restores module options from cache', async () => { + let moduleParsedCalls = 0; const plugin = { name: 'test', resolveId(id) { - resolveIdCalls += 1; + resolveIdCalls++; return { id, meta: { test: { resolved: id } } }; }, @@ -297,11 +298,16 @@ describe('incremental', () => { }, transform(code, id) { - transformCalls += 1; + transformCalls++; assert.deepStrictEqual(this.getModuleInfo(id).meta, { test: { loaded: id } }); return { code, meta: { test: { transformed: id } } }; }, + moduleParsed({ id, meta }) { + assert.deepStrictEqual(meta, { test: { transformed: id } }); + moduleParsedCalls++; + }, + buildEnd() { assert.deepStrictEqual( [...this.getModuleIds()].map(id => ({ id, meta: this.getModuleInfo(id).meta })), @@ -317,15 +323,17 @@ describe('incremental', () => { input: 'entry', plugins: [plugin] }); - assert.equal(resolveIdCalls, 2); - assert.equal(transformCalls, 2); + assert.strictEqual(resolveIdCalls, 2); + assert.strictEqual(transformCalls, 2); + assert.strictEqual(moduleParsedCalls, 2); await rollup.rollup({ input: 'entry', plugins: [plugin], cache: bundle }); - assert.equal(resolveIdCalls, 3); // +1 for entry point which is resolved every time - assert.equal(transformCalls, 2); + assert.strictEqual(resolveIdCalls, 3); // +1 for entry point which is resolved every time + assert.strictEqual(transformCalls, 2); + assert.strictEqual(moduleParsedCalls, 4); // should not be cached }); }); From 0ac41a0c0214373a9015f1319b841a33effdb66a Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Thu, 8 Oct 2020 07:12:22 +0200 Subject: [PATCH 4/4] Add documentation --- docs/05-plugin-development.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md index 7b65a36f376..76fb911c02d 100644 --- a/docs/05-plugin-development.md +++ b/docs/05-plugin-development.md @@ -79,7 +79,7 @@ See [Output Generation Hooks](guide/en/#output-generation-hooks) for hooks that #### `buildEnd` Type: `(error?: Error) => void`
Kind: `async, parallel`
-Previous Hook: [`transform`](guide/en/#transform), [`resolveId`](guide/en/#resolveid) or [`resolveDynamicImport`](guide/en/#resolvedynamicimport).
+Previous Hook: [`moduleParsed`](guide/en/#moduleparsed), [`resolveId`](guide/en/#resolveid) or [`resolveDynamicImport`](guide/en/#resolvedynamicimport).
Next Hook: [`outputOptions`](guide/en/#outputoptions) in the output generation phase as this is the last hook of the build phase. Called when rollup has finished bundling, but before `generate` or `write` is called; you can also return a Promise. If an error occurred during the build, it is passed on to this hook. @@ -108,6 +108,18 @@ See [custom module meta-data](guide/en/#custom-module-meta-data) for how to use You can use [`this.getModuleInfo`](guide/en/#thisgetmoduleinfomoduleid-string--moduleinfo--null) to find out the previous values of `moduleSideEffects`, `syntheticNamedExports` and `meta` inside this hook. +#### `moduleParsed` +Type: `(moduleInfo: ModuleInfo) => void`
+Kind: `async, parallel`
+Previous Hook: [`transform`](guide/en/#transform) where the currently handled file was transformed.
+NextHook: [`resolveId`](guide/en/#resolveid) and [`resolveDynamicImport`](guide/en/#resolvedynamicimport) to resolve all discovered static and dynamic imports in parallel if present, otherwise [`buildEnd`](guide/en/#buildend). + +This hook is called each time a module has been fully parsed by Rollup. See [`this.getModuleInfo`](guide/en/#thisgetmoduleinfomoduleid-string--moduleinfo--null) for what information is passed to this hook. + +In contrast to the [`transform`](guide/en/#transform) hook, this hook is never cached and can be used to get information about both cached and other modules, including the final shape of the `meta` property, the `code` and the `ast`. + +Note that information about imported modules is not yet available in this hook, and information about importing modules may be incomplete as additional importers could be discovered later. If you need this information, use the [`buildEnd`](guide/en/#buildend) hook. + #### `options` Type: `(options: InputOptions) => InputOptions | null`
Kind: `async, sequential`
@@ -121,7 +133,7 @@ This is the only hook that does not have access to most [plugin context](guide/e #### `resolveDynamicImport` Type: `(specifier: string | ESTree.Node, importer: string) => string | false | null | {id: string, external?: boolean}`
Kind: `async, first`
-Previous Hook: [`transform`](guide/en/#transform) where the importing file was transformed.
+Previous Hook: [`moduleParsed`](guide/en/#moduleparsed) for the importing file.
Next Hook: [`load`](guide/en/#load) if the hook resolved with an id that has not yet been loaded, [`resolveId`](guide/en/#resolveid) if the dynamic import contains a string and was not resolved by the hook, otherwise [`buildEnd`](guide/en/#buildend). Defines a custom resolver for dynamic imports. Returning `false` signals that the import should be kept as it is and not be passed to other resolvers thus making it external. Similar to the [`resolveId`](guide/en/#resolveid) hook, you can also return an object to resolve the import to a different id while marking it as external at the same time. @@ -138,7 +150,7 @@ Note that the return value of this hook will not be passed to `resolveId` afterw #### `resolveId` Type: `(source: string, importer: string | undefined, options: {custom?: {[plugin: string]: any}) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}`
Kind: `async, first`
-Previous Hook: [`buildStart`](guide/en/#buildstart) if we are resolving an entry point, [`transform`](guide/en/#transform) if we are resolving an import, or as fallback for [`resolveDynamicImport`](guide/en/#resolvedynamicimport). Additionally this hook can be triggered during the build phase from plugin hooks by calling [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string) to emit an entry point or at any time by calling [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean-custom-plugin-string-any--promiseid-string-external-boolean-modulesideeffects-boolean--no-treeshake-syntheticnamedexports-boolean--string-custom-plugin-string-any--null) to manually resolve an id.
+Previous Hook: [`buildStart`](guide/en/#buildstart) if we are resolving an entry point, [`moduleParsed`](guide/en/#moduleparsed) if we are resolving an import, or as fallback for [`resolveDynamicImport`](guide/en/#resolvedynamicimport). Additionally this hook can be triggered during the build phase from plugin hooks by calling [`this.emitFile`](guide/en/#thisemitfileemittedfile-emittedchunk--emittedasset--string) to emit an entry point or at any time by calling [`this.resolve`](guide/en/#thisresolvesource-string-importer-string-options-skipself-boolean-custom-plugin-string-any--promiseid-string-external-boolean-modulesideeffects-boolean--no-treeshake-syntheticnamedexports-boolean--string-custom-plugin-string-any--null) to manually resolve an id.
Next Hook: [`load`](guide/en/#load) if the resolved id that has not yet been loaded, otherwise [`buildEnd`](guide/en/#buildend). Defines a custom resolver. A resolver can be useful for e.g. locating third-party dependencies. Here `source` is the importee exactly as it is written in the import statement, i.e. for @@ -201,7 +213,7 @@ When triggering this hook from a plugin via [`this.resolve(source, importer, opt Type: `(code: string, id: string) => string | null | {code?: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | "no-treeshake" | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}`
Kind: `async, sequential`
Previous Hook: [`load`](guide/en/#load) where the currently handled file was loaded.
-NextHook: [`resolveId`](guide/en/#resolveid) and [`resolveDynamicImport`](guide/en/#resolvedynamicimport) to resolve all discovered static and dynamic imports in parallel if present, otherwise [`buildEnd`](guide/en/#buildend). +NextHook: [`moduleParsed`](guide/en/#moduleparsed) once the file has been processed and parsed. Can be used to transform individual modules. To prevent additional parsing overhead in case e.g. this hook already used `this.parse` to generate an AST for some reason, this hook can optionally return a `{ code, ast, map }` object. The `ast` must be a standard ESTree AST with `start` and `end` properties for each node. If the transformation does not move code, you can preserve existing sourcemaps by setting `map` to `null`. Otherwise you might need to generate the source map. See [the section on source code transformations](#source-code-transformations).