From 0161f8bacc5c1a28fb98ff0972ded943d4cec982 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Wed, 7 Oct 2020 06:52:32 +0200 Subject: [PATCH] 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 539ca8cc189..699d3fd738a 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 }); });