From 22289f9cdb63169dc914d3c6e0bb8cc4de99c6cc Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Tue, 13 Oct 2020 19:58:42 +0200 Subject: [PATCH] Add moduleParsed plugin hook (#3813) * Attach ESTree AST to all Nodes and do not expose internal AST * Include code and AST in module info * Add moduleParsed hook * Add documentation --- cli/help.md | 6 +- docs/01-command-line-reference.md | 6 +- docs/05-plugin-development.md | 27 +- src/Bundle.ts | 2 +- src/Chunk.ts | 12 +- src/ExternalModule.ts | 35 +- src/Graph.ts | 48 +-- src/Module.ts | 111 ++++-- src/ModuleLoader.ts | 10 +- src/ast/nodes/shared/Node.ts | 7 +- src/rollup/types.d.ts | 7 + src/utils/PluginDriver.ts | 1 + src/utils/collapseSourcemaps.ts | 2 - src/utils/transform.ts | 2 +- src/utils/traverseStaticDependencies.ts | 2 +- .../_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 +++++++- .../samples/module-parsed-hook/_config.js | 111 ++++++ .../samples/module-parsed-hook/dep.js | 1 + .../samples/module-parsed-hook/main.js | 1 + .../plugin-module-information/_config.js | 372 +++++++++++++++--- test/incremental/index.js | 72 ++-- 26 files changed, 1479 insertions(+), 236 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..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). @@ -624,6 +636,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 +650,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 b6db075d3e7..aef2796394f 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -1,9 +1,11 @@ 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'; @@ -15,6 +17,7 @@ export default class ExternalModule { execIndex: number; exportedVariables: Map; importers: string[] = []; + info: ModuleInfo; mostCommonSuggestion = 0; namespaceVariableName = ''; nameSuggestions: { [name: string]: number }; @@ -28,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 240d242ab13..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,47 +134,7 @@ export default class Graph { getModuleInfo = (moduleId: string): ModuleInfo | null => { const foundModule = this.modulesById.get(moduleId); if (!foundModule) return null; - return { - 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 { @@ -211,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(); @@ -226,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 082a5cb5ab2..3d42a0227f0 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -33,6 +33,7 @@ import { DecodedSourceMapOrMissing, EmittedFile, ExistingDecodedSourceMap, + ModuleInfo, ModuleJSON, ModuleOptions, NormalizedInputOptions, @@ -113,9 +114,13 @@ 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, { + return Parser.parse(module.info.code!, { ...acornOptions, onComment: (block: boolean, text: string, start: number, end: number) => module.comments.push({ block, text, start, end }) @@ -180,9 +185,9 @@ function getVariableForExportNameRecursive( } export default class Module { + ast: Program | null = null; chunkFileNames = new Set(); chunkName: string | null = null; - code!: string; comments: CommentDescription[] = []; dependencies = new Set(); dynamicDependencies = new Set(); @@ -204,6 +209,7 @@ export default class Module { importMetas: MetaProperty[] = []; imports = new Set(); includedDynamicImporters: Module[] = []; + info: ModuleInfo; isExecuted = false; isUserDefinedEntryPoint = false; namespace!: NamespaceVariable; @@ -221,11 +227,9 @@ 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; - private esTreeAst!: acorn.Node; private exportAllModules: (Module | ExternalModule)[] = []; private exportNamesByVariable: Map | null = null; private exportShimVariable: ExportShimVariable = new ExportShimVariable(this); @@ -240,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() { @@ -257,7 +296,7 @@ export default class Module { } bindReferences() { - this.ast.bind(); + this.ast!.bind(); } error(props: RollupError, pos: number): never { @@ -297,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 @@ -320,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) ) { @@ -523,14 +563,14 @@ export default class Module { hasEffects() { return ( - this.moduleSideEffects === 'no-treeshake' || - (this.ast.included && this.ast.hasEffects(createHasEffectsContext())) + this.info.hasModuleSideEffects === 'no-treeshake' || + (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) { @@ -568,11 +608,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() { @@ -604,7 +644,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; } @@ -625,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; @@ -639,16 +679,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 +737,8 @@ 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); + this.info.ast = ast; timeEnd('analyse ast', 3); } @@ -711,13 +746,13 @@ export default class Module { toJSON(): ModuleJSON { return { alwaysRemovedCode: this.alwaysRemovedCode, - ast: this.esTreeAst, - code: this.code, + ast: this.ast!.esTreeNode, + 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, @@ -765,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 }; } } @@ -890,8 +925,8 @@ export default class Module { private addLocationToLogProps(props: RollupLogProps, pos: number): void { props.id = this.id; props.pos = pos; - let code = this.code; - let { column, line } = locate(code, pos, { offsetLine: 1 }); + let code = this.info.code; + let { column, line } = locate(code!, pos, { offsetLine: 1 }); try { ({ column, line } = getOriginalLocation(this.sourcemapChain, { column, line })); code = this.originalCode; @@ -908,7 +943,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/ModuleLoader.ts b/src/ModuleLoader.ts index 0f3d3b4ced3..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 => @@ -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; @@ -297,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); @@ -319,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) @@ -475,7 +478,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..7366eeac9de 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'; @@ -263,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, @@ -342,6 +346,7 @@ 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; resolveDynamicImport: ResolveDynamicImportHook; resolveId: ResolveIdHook; @@ -390,6 +395,7 @@ export type AsyncPluginHooks = | 'buildStart' | 'generateBundle' | 'load' + | 'moduleParsed' | 'renderChunk' | 'renderError' | 'renderStart' @@ -426,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/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 || '.'; 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/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/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/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: {} + } + ] + ); } } }, 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 }); });