diff --git a/bin/src/run/batchWarnings.ts b/bin/src/run/batchWarnings.ts index 41a2e88437a..bfc0e525de7 100644 --- a/bin/src/run/batchWarnings.ts +++ b/bin/src/run/batchWarnings.ts @@ -4,8 +4,8 @@ import relativeId from '../../../src/utils/relativeId'; import { stderr } from '../logging'; export interface BatchWarnings { - readonly count: number; add: (warning: string | RollupWarning) => void; + readonly count: number; flush: () => void; } @@ -117,22 +117,21 @@ const immediateHandlers: { // TODO select sensible priorities const deferredHandlers: { [code: string]: { - priority: number; fn: (warnings: RollupWarning[]) => void; + priority: number; }; } = { UNUSED_EXTERNAL_IMPORT: { - priority: 1, fn: warnings => { title('Unused external imports'); warnings.forEach(warning => { stderr(`${warning.names} imported from external module '${warning.source}' but never used`); }); - } + }, + priority: 1 }, UNRESOLVED_IMPORT: { - priority: 1, fn: warnings => { title('Unresolved dependencies'); info('https://rollupjs.org/guide/en#warning-treating-module-as-external-dependency'); @@ -147,11 +146,11 @@ const deferredHandlers: { const importers = dependencies.get(dependency); stderr(`${tc.bold(dependency)} (imported by ${importers.join(', ')})`); }); - } + }, + priority: 1 }, MISSING_EXPORT: { - priority: 1, fn: warnings => { title('Missing exports'); info('https://rollupjs.org/guide/en#error-name-is-not-exported-by-module-'); @@ -161,37 +160,37 @@ const deferredHandlers: { stderr(`${warning.missing} is not exported by ${warning.exporter}`); stderr(tc.gray(warning.frame)); }); - } + }, + priority: 1 }, THIS_IS_UNDEFINED: { - priority: 1, fn: warnings => { title('`this` has been rewritten to `undefined`'); info('https://rollupjs.org/guide/en#error-this-is-undefined'); showTruncatedWarnings(warnings); - } + }, + priority: 1 }, EVAL: { - priority: 1, fn: warnings => { title('Use of eval is strongly discouraged'); info('https://rollupjs.org/guide/en#avoiding-eval'); showTruncatedWarnings(warnings); - } + }, + priority: 1 }, NON_EXISTENT_EXPORT: { - priority: 1, fn: warnings => { title(`Import of non-existent ${warnings.length > 1 ? 'exports' : 'export'}`); showTruncatedWarnings(warnings); - } + }, + priority: 1 }, NAMESPACE_CONFLICT: { - priority: 1, fn: warnings => { title(`Conflicting re-exports`); warnings.forEach(warning => { @@ -203,11 +202,11 @@ const deferredHandlers: { )} (will be ignored)` ); }); - } + }, + priority: 1 }, MISSING_GLOBAL_NAME: { - priority: 1, fn: warnings => { title(`Missing global variable ${warnings.length > 1 ? 'names' : 'name'}`); stderr( @@ -216,11 +215,11 @@ const deferredHandlers: { warnings.forEach(warning => { stderr(`${tc.bold(warning.source)} (guessing '${warning.guess}')`); }); - } + }, + priority: 1 }, SOURCEMAP_BROKEN: { - priority: 1, fn: warnings => { title(`Broken sourcemap`); info('https://rollupjs.org/guide/en#warning-sourcemap-is-likely-to-be-incorrect'); @@ -230,18 +229,18 @@ const deferredHandlers: { plugins.length === 0 ? '' : plugins.length > 1 - ? ` (such as ${plugins - .slice(0, -1) - .map(p => `'${p}'`) - .join(', ')} and '${plugins.slice(-1)}')` - : ` (such as '${plugins[0]}')`; + ? ` (such as ${plugins + .slice(0, -1) + .map(p => `'${p}'`) + .join(', ')} and '${plugins.slice(-1)}')` + : ` (such as '${plugins[0]}')`; stderr(`Plugins that transform code${detail} should generate accompanying sourcemaps`); - } + }, + priority: 1 }, PLUGIN_WARNING: { - priority: 1, fn: warnings => { const nestedByPlugin = nest(warnings, 'plugin'); @@ -266,7 +265,8 @@ const deferredHandlers: { }); }); }); - } + }, + priority: 1 } }; @@ -279,15 +279,15 @@ function info(url: string) { } function nest(array: T[], prop: string) { - const nested: { key: string; items: T[] }[] = []; - const lookup = new Map(); + const nested: { items: T[]; key: string }[] = []; + const lookup = new Map(); array.forEach(item => { const key = (item)[prop]; if (!lookup.has(key)) { lookup.set(key, { - key, - items: [] + items: [], + key }); nested.push(lookup.get(key)); diff --git a/bin/src/run/index.ts b/bin/src/run/index.ts index ba44423deea..31a6f2ddd29 100644 --- a/bin/src/run/index.ts +++ b/bin/src/run/index.ts @@ -102,8 +102,8 @@ function execute(configFile: string, configs: InputOptions[], command: any) { promise = promise.then(() => { const warnings = batchWarnings(); const { inputOptions, outputOptions, optionError } = mergeOptions({ - config, command, + config, defaultOnWarnHandler: warnings.add }); diff --git a/bin/src/run/loadConfigFile.ts b/bin/src/run/loadConfigFile.ts index 53d1adbc1a6..5dea8f008f4 100644 --- a/bin/src/run/loadConfigFile.ts +++ b/bin/src/run/loadConfigFile.ts @@ -23,12 +23,12 @@ export default function loadConfigFile( return rollup .rollup({ - input: configFile, - treeshake: false, external: (id: string) => { return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json'; }, - onwarn: warnings.add + input: configFile, + onwarn: warnings.add, + treeshake: false }) .then((bundle: RollupBuild) => { if (!silent && warnings.count > 0) { diff --git a/bin/src/run/watch.ts b/bin/src/run/watch.ts index 5c7b3f44036..d09ab82bff2 100644 --- a/bin/src/run/watch.ts +++ b/bin/src/run/watch.ts @@ -20,16 +20,16 @@ import { printTimings } from './timings'; interface WatchEvent { code?: string; + duration?: number; error?: RollupError | Error; input?: InputOption; output?: string[]; - duration?: number; result?: RollupBuild; } interface Watcher { - on: (event: string, fn: (event: WatchEvent) => void) => void; close: () => void; + on: (event: string, fn: (event: WatchEvent) => void) => void; } export default function watch( @@ -57,8 +57,8 @@ export default function watch( function processConfigs(configs: RollupWatchOptions[]): RollupWatchOptions[] { return configs.map(options => { const merged = mergeOptions({ - config: options, command, + config: options, defaultOnWarnHandler: warnings.add }); @@ -71,8 +71,8 @@ export default function watch( if (merged.optionError) merged.inputOptions.onwarn({ - message: merged.optionError, - code: 'UNKNOWN_OPTION' + code: 'UNKNOWN_OPTION', + message: merged.optionError }); if ( @@ -117,8 +117,8 @@ export default function watch( input = Array.isArray(input) ? input.join(', ') : Object.keys(input) - .map(key => (>input)[key]) - .join(', '); + .map(key => (>input)[key]) + .join(', '); } stderr( tc.cyan( diff --git a/src/Chunk.ts b/src/Chunk.ts index b9ee6619f57..b0438056141 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -38,40 +38,40 @@ import { timeEnd, timeStart } from './utils/timers'; import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames'; export interface ModuleDeclarations { - exports: ChunkExports; dependencies: ModuleDeclarationDependency[]; + exports: ChunkExports; } export interface ModuleDeclarationDependency { - id: string; - namedExportsMode: boolean; - name: string; - globalName: string; - isChunk: boolean; // these used as interop signifiers exportsDefault: boolean; exportsNames: boolean; - reexports?: ReexportSpecifier[]; + globalName: string; + id: string; imports?: ImportSpecifier[]; + isChunk: boolean; + name: string; + namedExportsMode: boolean; + reexports?: ReexportSpecifier[]; } export type ChunkDependencies = ModuleDeclarationDependency[]; export type ChunkExports = { - local: string; exported: string; hoisted: boolean; + local: string; uninitialized: boolean; }[]; export interface ReexportSpecifier { - reexported: string; imported: string; + reexported: string; } export interface ImportSpecifier { - local: string; imported: string; + local: string; } function getGlobalName( @@ -94,19 +94,19 @@ function getGlobalName( if (hasExports) { graph.warn({ code: 'MISSING_GLOBAL_NAME', - source: module.id, guess: module.variableName, message: `No name was provided for external module '${ module.id - }' in output.globals – guessing '${module.variableName}'` + }' in output.globals – guessing '${module.variableName}'`, + source: module.id }); return module.variableName; } } export default class Chunk { - execIndex: number; entryModules: Module[] = []; + execIndex: number; exportMode: string = 'named'; facadeModule: Module | null = null; graph: Graph; @@ -170,109 +170,10 @@ export default class Chunk { } else this.variableName = '__chunk_' + ++graph.curChunkIndex; } - getImportIds(): string[] { - return this.dependencies.map(chunk => chunk.id); - } - - getDynamicImportIds(): string[] { - return this.dynamicDependencies.map(chunk => chunk.id).filter(Boolean); - } - - getExportNames(): string[] { - return Object.keys(this.exportNames); - } - - private inlineChunkDependencies(chunk: Chunk, deep: boolean) { - for (const dep of chunk.dependencies) { - if (dep instanceof ExternalModule) { - if (this.dependencies.indexOf(dep) === -1) this.dependencies.push(dep); - } else { - if (dep === this || this.dependencies.indexOf(dep) !== -1) continue; - if (!dep.isEmpty) this.dependencies.push(dep); - if (deep) this.inlineChunkDependencies(dep, true); - } - } - } - - turnIntoFacade(facadedModule: Module) { - this.dependencies = [facadedModule.chunk]; - this.dynamicDependencies = []; - this.facadeModule = facadedModule; - facadedModule.facadeChunk = this; - for (const exportName of facadedModule.getAllExports()) { - const tracedVariable = facadedModule.getVariableForExportName(exportName); - this.exports.add(tracedVariable); - this.exportNames[exportName] = tracedVariable; - } - } - - link() { - const dependencies: Set = new Set(); - const dynamicDependencies: Set = new Set(); - for (const module of this.orderedModules) { - this.addChunksFromDependencies(module.dependencies, dependencies); - this.addChunksFromDependencies(module.dynamicDependencies, dynamicDependencies); - this.setUpModuleImports(module); - } - this.dependencies = Array.from(dependencies); - this.dynamicDependencies = Array.from(dynamicDependencies); - } - - private addChunksFromDependencies( - moduleDependencies: (Module | ExternalModule)[], - chunkDependencies: Set - ) { - for (const depModule of moduleDependencies) { - if (depModule.chunk === this) { - continue; - } - let dependency: Chunk | ExternalModule; - if (depModule instanceof Module) { - dependency = depModule.chunk; - } else { - if (!depModule.used && this.graph.isPureExternalModule(depModule.id)) { - continue; - } - dependency = depModule; - } - chunkDependencies.add(dependency); - } - } - - private setUpModuleImports(module: Module) { - for (const variable of Array.from(module.imports)) { - if (variable.module.chunk !== this) { - this.imports.add(variable); - if (variable.module instanceof Module) { - variable.module.chunk.exports.add(variable); - } - } - } - if (module.getOrCreateNamespace().included) { - for (const reexportName of Object.keys(module.reexports)) { - const reexport = module.reexports[reexportName]; - const variable = reexport.module.getVariableForExportName(reexport.localName); - if (variable.module.chunk !== this) { - this.imports.add(variable); - if (variable.module instanceof Module) { - variable.module.chunk.exports.add(variable); - } - } - } - } - for (const { node, resolution } of module.dynamicImports) { - if (node.included) { - this.hasDynamicImport = true; - if (resolution instanceof Module && resolution.chunk === this) - resolution.getOrCreateNamespace().include(); - } - } - } - generateEntryExportsOrMarkAsTainted() { const exportVariableMaps = this.entryModules.map(module => ({ - module, - map: this.getVariableExportNamesForModule(module) + map: this.getVariableExportNamesForModule(module), + module })); for (const { map } of exportVariableMaps) { for (const exposedVariable of Array.from(map.keys())) { @@ -298,34 +199,41 @@ export default class Chunk { } } - private getVariableExportNamesForModule(module: Module) { - const exportNamesByVariable: Map = new Map(); - for (const exportName of module.getAllExports()) { - const tracedVariable = module.getVariableForExportName(exportName); - if (!tracedVariable || !(tracedVariable.included || tracedVariable.isExternal)) { - continue; - } - const existingExportNames = exportNamesByVariable.get(tracedVariable); - if (existingExportNames) { - existingExportNames.push(exportName); - } else { - exportNamesByVariable.set(tracedVariable, [exportName]); - } - const exportingModule = tracedVariable.module; - if (exportingModule && exportingModule.chunk && exportingModule.chunk !== this) { - exportingModule.chunk.exports.add(tracedVariable); - } - } - return exportNamesByVariable; + generateId( + pattern: string, + patternName: string, + addons: Addons, + options: OutputOptions, + existingNames: Record + ) { + this.id = makeUnique( + renderNamePattern(pattern, patternName, type => { + switch (type) { + case 'format': + return options.format === 'es' ? 'esm' : options.format; + case 'hash': + return this.computeContentHashWithDependencies(addons, options); + case 'name': + return this.getChunkName(); + } + }), + existingNames + ); } - getVariableExportName(variable: Variable) { - if (this.graph.preserveModules && variable instanceof NamespaceVariable) { - return '*'; - } - for (const exportName of Object.keys(this.exportNames)) { - if (this.exportNames[exportName] === variable) return exportName; - } + generateIdPreserveModules( + preserveModulesRelativeDir: string, + existingNames: Record + ) { + const sanitizedId = sanitizeFileName(this.orderedModules[0].id); + this.id = makeUnique( + normalize( + isAbsolute(this.orderedModules[0].id) + ? relative(preserveModulesRelativeDir, sanitizedId) + : '_virtual/' + basename(sanitizedId) + ), + existingNames + ); } generateInternalExports(options: OutputOptions) { @@ -357,310 +265,182 @@ export default class Chunk { } } - private prepareDynamicImports() { - for (const module of this.orderedModules) { - for (const { node, resolution } of module.dynamicImports) { - if (!resolution) continue; - if (resolution instanceof Module) { - if (resolution.chunk === this) { - const namespace = resolution.getOrCreateNamespace(); - node.setResolution(false, namespace.getName()); - } else { - node.setResolution(false); - } - } else if (resolution instanceof ExternalModule) { - node.setResolution(true); - } else { - node.setResolution(false); - } - } - } + getChunkName(): string { + return this.chunkName || (this.chunkName = this.computeChunkName()); } - private finaliseDynamicImports() { - for (let i = 0; i < this.orderedModules.length; i++) { - const module = this.orderedModules[i]; - const code = this.renderedModuleSources[i]; - for (const { node, resolution } of module.dynamicImports) { - if (!resolution) continue; - if (resolution instanceof Module) { - if (!resolution.chunk.isEmpty && resolution.chunk !== this) { - const resolutionChunk = resolution.facadeChunk || resolution.chunk; - let relPath = normalize(relative(dirname(this.id), resolutionChunk.id)); - if (!relPath.startsWith('../')) relPath = './' + relPath; - node.renderFinalResolution(code, `'${relPath}'`); - } - } else if (resolution instanceof ExternalModule) { - node.renderFinalResolution(code, `'${resolution.id}'`); - } else { - node.renderFinalResolution(code, resolution); - } - } - } + getDynamicImportIds(): string[] { + return this.dynamicDependencies.map(chunk => chunk.id).filter(Boolean); } - private finaliseImportMetas(options: OutputOptions): boolean { - let usesMechanism = false; - for (let i = 0; i < this.orderedModules.length; i++) { - const module = this.orderedModules[i]; - const code = this.renderedModuleSources[i]; - for (const importMeta of module.importMetas) { - if (importMeta.renderFinalMechanism(code, this.id, options.format, options.compact)) - usesMechanism = true; - } - } - return usesMechanism; + getExportNames(): string[] { + return Object.keys(this.exportNames); } - private setIdentifierRenderResolutions(options: OutputOptions) { - for (const exportName of Object.keys(this.exportNames)) { - const exportVariable = this.exportNames[exportName]; - if (exportVariable) { - if (exportVariable instanceof ExportShimVariable) { - this.needsExportsShim = true; - } - exportVariable.exportName = exportName; - if ( - options.format !== 'es' && - options.format !== 'system' && - exportVariable.isReassigned && - !exportVariable.isId && - (!isExportDefaultVariable(exportVariable) || !exportVariable.hasId) - ) { - exportVariable.setRenderNames('exports', exportName); - } else { - exportVariable.setRenderNames(null, null); - } - } - } + getImportIds(): string[] { + return this.dependencies.map(chunk => chunk.id); + } - const usedNames = Object.create(null); - if (this.needsExportsShim) { - usedNames[MISSING_EXPORT_SHIM_VARIABLE] = true; + getRenderedHash(): string { + if (this.renderedHash) return this.renderedHash; + if (!this.renderedSource) return ''; + const hash = sha256(); + hash.update(this.renderedSource.toString()); + hash.update(Object.keys(this.exportNames).join(',')); + return (this.renderedHash = hash.digest('hex')); + } + + getRenderedSourceLength() { + if (this.renderedSourceLength !== undefined) return this.renderedSourceLength; + return (this.renderedSourceLength = this.renderedSource.length()); + } + + getVariableExportName(variable: Variable) { + if (this.graph.preserveModules && variable instanceof NamespaceVariable) { + return '*'; + } + for (const exportName of Object.keys(this.exportNames)) { + if (this.exportNames[exportName] === variable) return exportName; } + } - deconflictChunk( - this.orderedModules, - this.dependencies, - this.imports, - usedNames, - options.format, - options.interop !== false, - this.graph.preserveModules - ); + link() { + const dependencies: Set = new Set(); + const dynamicDependencies: Set = new Set(); + for (const module of this.orderedModules) { + this.addChunksFromDependencies(module.dependencies, dependencies); + this.addChunksFromDependencies(module.dynamicDependencies, dynamicDependencies); + this.setUpModuleImports(module); + } + this.dependencies = Array.from(dependencies); + this.dynamicDependencies = Array.from(dynamicDependencies); } - private getChunkDependencyDeclarations( - options: OutputOptions, - inputBase: string - ): ChunkDependencies { - const reexportDeclarations = new Map(); + /* + * Performs a full merge of another chunk into this chunk + * chunkList allows updating references in other chunks for the merged chunk to this chunk + * A new facade will be added to chunkList if tainting exports of either as an entry point + */ + merge(chunk: Chunk, chunkList: Chunk[], options: OutputOptions, inputBase: string) { + if (this.facadeModule !== null || chunk.facadeModule !== null) + throw new Error('Internal error: Code splitting chunk merges not supported for facades'); - for (let exportName of Object.keys(this.exportNames)) { - let exportModule: Chunk | ExternalModule; - let importName: string; - if (exportName[0] === '*') { - exportModule = this.graph.moduleById.get(exportName.substr(1)); - importName = exportName = '*'; - } else { - const variable = this.exportNames[exportName]; - const module = variable.module; - // skip local exports - if (!module || module.chunk === this) continue; - if (module instanceof Module) { - exportModule = module.chunk; - importName = module.chunk.getVariableExportName(variable); - } else { - exportModule = module; - importName = variable.name; - } + for (const module of chunk.orderedModules) { + module.chunk = this; + this.orderedModules.push(module); + } + + for (const variable of Array.from(chunk.imports)) { + if (!this.imports.has(variable) && variable.module.chunk !== this) { + this.imports.add(variable); } - let exportDeclaration = reexportDeclarations.get(exportModule); - if (!exportDeclaration) reexportDeclarations.set(exportModule, (exportDeclaration = [])); - exportDeclaration.push({ imported: importName, reexported: exportName }); } - const importsAsArray = Array.from(this.imports); - const dependencies: ChunkDependencies = []; + // NB detect when exported variables are orphaned by the merge itself + // (involves reverse tracing dependents) + for (const variable of Array.from(chunk.exports)) { + if (!this.exports.has(variable)) { + this.exports.add(variable); + } + } - for (const dep of this.dependencies) { - const imports: ImportSpecifier[] = []; - for (const variable of importsAsArray) { - if ( - (variable.module instanceof Module - ? variable.module.chunk === dep - : variable.module === dep) && - !( - variable instanceof ExportDefaultVariable && - variable.referencesOriginal() && - this.imports.has(variable.getOriginalVariable()) - ) - ) { - const local = variable.getName(); - const imported = - variable.module instanceof ExternalModule - ? variable.name - : variable.module.chunk.getVariableExportName(variable); - imports.push({ local, imported }); + const thisOldExportNames = this.exportNames; + + // regenerate internal names + this.generateInternalExports(options); + + const updateRenderedDeclaration = ( + dep: ModuleDeclarationDependency, + oldExportNames: Record + ) => { + if (dep.imports) { + for (const impt of dep.imports) { + impt.imported = this.getVariableExportName(oldExportNames[impt.imported]); } } + if (dep.reexports) { + for (const reexport of dep.reexports) { + reexport.imported = this.getVariableExportName(oldExportNames[reexport.imported]); + } + } + }; - const reexports = reexportDeclarations.get(dep); - let exportsNames: boolean, exportsDefault: boolean; - let namedExportsMode = true; - if (dep instanceof ExternalModule) { - exportsNames = dep.exportsNames || dep.exportsNamespace; - exportsDefault = 'default' in dep.declarations; - } else { - exportsNames = true; - // we don't want any interop patterns to trigger - exportsDefault = false; - namedExportsMode = dep.exportMode !== 'default'; + const mergeRenderedDeclaration = ( + into: ModuleDeclarationDependency, + from: ModuleDeclarationDependency + ) => { + if (from.imports) { + if (!into.imports) { + into.imports = from.imports; + } else { + into.imports = into.imports.concat(from.imports); + } + } + if (from.reexports) { + if (!into.reexports) { + into.reexports = from.reexports; + } else { + into.reexports = into.reexports.concat(from.reexports); + } + } + if (!into.exportsNames && from.exportsNames) { + into.exportsNames = true; + } + if (!into.exportsDefault && from.exportsDefault) { + into.exportsDefault = true; } + into.name = this.variableName; + }; - let id: string; - let globalName: string; - if (dep instanceof ExternalModule) { - id = dep.setRenderPath(options, inputBase); - if (options.format === 'umd' || options.format === 'iife') { - globalName = getGlobalName( - dep, - options.globals, - this.graph, - exportsNames || exportsDefault + // go through the other chunks and update their dependencies + // also update their import and reexport names in the process + for (const c of chunkList) { + let includedDeclaration: ModuleDeclarationDependency; + for (let i = 0; i < c.dependencies.length; i++) { + const dep = c.dependencies[i]; + if ((dep === chunk || dep === this) && includedDeclaration) { + const duplicateDeclaration = c.renderedDeclarations.dependencies[i]; + updateRenderedDeclaration( + duplicateDeclaration, + dep === chunk ? chunk.exportNames : thisOldExportNames ); + mergeRenderedDeclaration(includedDeclaration, duplicateDeclaration); + c.renderedDeclarations.dependencies.splice(i, 1); + c.dependencies.splice(i--, 1); + } else if (dep === chunk) { + c.dependencies[i] = this; + includedDeclaration = c.renderedDeclarations.dependencies[i]; + updateRenderedDeclaration(includedDeclaration, chunk.exportNames); + } else if (dep === this) { + includedDeclaration = c.renderedDeclarations.dependencies[i]; + updateRenderedDeclaration(includedDeclaration, thisOldExportNames); } } - - dependencies.push({ - id, // chunk id updated on render - namedExportsMode, - globalName, - name: dep.variableName, - isChunk: !(dep).isExternal, - exportsNames, - exportsDefault, - reexports, - imports: imports.length > 0 ? imports : null - }); } - return dependencies; + // re-render the merged chunk + this.preRender(options, inputBase); } - private getChunkExportDeclarations(): ChunkExports { - const exports: ChunkExports = []; - for (const exportName of Object.keys(this.exportNames)) { - if (exportName[0] === '*') continue; + // prerender allows chunk hashes and names to be generated before finalizing + preRender(options: OutputOptions, inputBase: string) { + timeStart('render modules', 3); - const variable = this.exportNames[exportName]; - const module = variable.module; + const magicString = new MagicStringBundle({ separator: options.compact ? '' : '\n\n' }); + this.usedModules = []; + this.indentString = options.compact ? '' : getIndentString(this.orderedModules, options); - if (module && module.chunk !== this) continue; - let hoisted = false; - let uninitialized = false; - if (variable instanceof LocalVariable) { - if (variable.init === UNDEFINED_EXPRESSION) { - uninitialized = true; - } - variable.declarations.forEach(decl => { - if (decl.type === NodeType.ExportDefaultDeclaration) { - if (decl.declaration.type === NodeType.FunctionDeclaration) hoisted = true; - } else if (decl.parent.type === NodeType.FunctionDeclaration) { - hoisted = true; - } - }); - } else if (variable instanceof GlobalVariable) { - hoisted = true; - } + const n = options.compact ? '' : '\n'; + const _ = options.compact ? '' : ' '; - const localName = variable.getName(); - - exports.push({ - local: localName, - exported: exportName === '*' ? localName : exportName, - hoisted, - uninitialized - }); - } - return exports; - } - - getRenderedHash(): string { - if (this.renderedHash) return this.renderedHash; - if (!this.renderedSource) return ''; - const hash = sha256(); - hash.update(this.renderedSource.toString()); - hash.update(Object.keys(this.exportNames).join(',')); - return (this.renderedHash = hash.digest('hex')); - } - - visitStaticDependenciesUntilCondition( - isConditionSatisfied: (dep: Chunk | ExternalModule) => any - ): boolean { - const seen = new Set(); - function visitDep(dep: Chunk | ExternalModule): boolean { - if (seen.has(dep)) return; - seen.add(dep); - if (dep instanceof Chunk) { - for (const subDep of dep.dependencies) { - if (visitDep(subDep)) return true; - } - } - return isConditionSatisfied(dep) === true; - } - return visitDep(this); - } - - visitDependencies(handleDependency: (dependency: Chunk | ExternalModule) => void) { - const toBeVisited: (Chunk | ExternalModule)[] = [this]; - const visited: Set = new Set(); - for (const current of toBeVisited) { - handleDependency(current); - if (current instanceof ExternalModule) continue; - for (const dependency of current.dependencies.concat(current.dynamicDependencies)) { - if (!visited.has(dependency)) { - visited.add(dependency); - toBeVisited.push(dependency); - } - } - } - } - - private computeContentHashWithDependencies(addons: Addons, options: OutputOptions): string { - const hash = sha256(); - - hash.update( - [addons.intro, addons.outro, addons.banner, addons.footer].map(addon => addon || '').join(':') - ); - hash.update(options.format); - this.visitDependencies(dep => { - if (dep instanceof ExternalModule) hash.update(':' + dep.renderPath); - else hash.update(dep.getRenderedHash()); - }); - - return hash.digest('hex').substr(0, 8); - } - - // prerender allows chunk hashes and names to be generated before finalizing - preRender(options: OutputOptions, inputBase: string) { - timeStart('render modules', 3); - - const magicString = new MagicStringBundle({ separator: options.compact ? '' : '\n\n' }); - this.usedModules = []; - this.indentString = options.compact ? '' : getIndentString(this.orderedModules, options); - - const n = options.compact ? '' : '\n'; - const _ = options.compact ? '' : ' '; - - const renderOptions: RenderOptions = { - compact: options.compact, - format: options.format, - freeze: options.freeze !== false, - indent: this.indentString, - namespaceToStringTag: options.namespaceToStringTag === true, - varOrConst: options.preferConst ? 'const' : 'var' - }; + const renderOptions: RenderOptions = { + compact: options.compact, + format: options.format, + freeze: options.freeze !== false, + indent: this.indentString, + namespaceToStringTag: options.namespaceToStringTag === true, + varOrConst: options.preferConst ? 'const' : 'var' + }; // Make sure the direct dependencies of a chunk are present to maintain execution order for (const { module } of Array.from(this.imports)) { @@ -702,10 +482,10 @@ export default class Chunk { const { renderedExports, removedExports } = module.getRenderedExports(); this.renderedModules[module.id] = { - renderedExports, + originalLength: module.originalCode.length, removedExports, - renderedLength: source.length(), - originalLength: module.originalCode.length + renderedExports, + renderedLength: source.length() }; const namespace = module.getOrCreateNamespace(); @@ -753,168 +533,6 @@ export default class Chunk { timeEnd('render modules', 3); } - getRenderedSourceLength() { - if (this.renderedSourceLength !== undefined) return this.renderedSourceLength; - return (this.renderedSourceLength = this.renderedSource.length()); - } - - /* - * Performs a full merge of another chunk into this chunk - * chunkList allows updating references in other chunks for the merged chunk to this chunk - * A new facade will be added to chunkList if tainting exports of either as an entry point - */ - merge(chunk: Chunk, chunkList: Chunk[], options: OutputOptions, inputBase: string) { - if (this.facadeModule !== null || chunk.facadeModule !== null) - throw new Error('Internal error: Code splitting chunk merges not supported for facades'); - - for (const module of chunk.orderedModules) { - module.chunk = this; - this.orderedModules.push(module); - } - - for (const variable of Array.from(chunk.imports)) { - if (!this.imports.has(variable) && variable.module.chunk !== this) { - this.imports.add(variable); - } - } - - // NB detect when exported variables are orphaned by the merge itself - // (involves reverse tracing dependents) - for (const variable of Array.from(chunk.exports)) { - if (!this.exports.has(variable)) { - this.exports.add(variable); - } - } - - const thisOldExportNames = this.exportNames; - - // regenerate internal names - this.generateInternalExports(options); - - const updateRenderedDeclaration = ( - dep: ModuleDeclarationDependency, - oldExportNames: Record - ) => { - if (dep.imports) { - for (const impt of dep.imports) { - impt.imported = this.getVariableExportName(oldExportNames[impt.imported]); - } - } - if (dep.reexports) { - for (const reexport of dep.reexports) { - reexport.imported = this.getVariableExportName(oldExportNames[reexport.imported]); - } - } - }; - - const mergeRenderedDeclaration = ( - into: ModuleDeclarationDependency, - from: ModuleDeclarationDependency - ) => { - if (from.imports) { - if (!into.imports) { - into.imports = from.imports; - } else { - into.imports = into.imports.concat(from.imports); - } - } - if (from.reexports) { - if (!into.reexports) { - into.reexports = from.reexports; - } else { - into.reexports = into.reexports.concat(from.reexports); - } - } - if (!into.exportsNames && from.exportsNames) { - into.exportsNames = true; - } - if (!into.exportsDefault && from.exportsDefault) { - into.exportsDefault = true; - } - into.name = this.variableName; - }; - - // go through the other chunks and update their dependencies - // also update their import and reexport names in the process - for (const c of chunkList) { - let includedDeclaration: ModuleDeclarationDependency; - for (let i = 0; i < c.dependencies.length; i++) { - const dep = c.dependencies[i]; - if ((dep === chunk || dep === this) && includedDeclaration) { - const duplicateDeclaration = c.renderedDeclarations.dependencies[i]; - updateRenderedDeclaration( - duplicateDeclaration, - dep === chunk ? chunk.exportNames : thisOldExportNames - ); - mergeRenderedDeclaration(includedDeclaration, duplicateDeclaration); - c.renderedDeclarations.dependencies.splice(i, 1); - c.dependencies.splice(i--, 1); - } else if (dep === chunk) { - c.dependencies[i] = this; - includedDeclaration = c.renderedDeclarations.dependencies[i]; - updateRenderedDeclaration(includedDeclaration, chunk.exportNames); - } else if (dep === this) { - includedDeclaration = c.renderedDeclarations.dependencies[i]; - updateRenderedDeclaration(includedDeclaration, thisOldExportNames); - } - } - } - - // re-render the merged chunk - this.preRender(options, inputBase); - } - - generateIdPreserveModules( - preserveModulesRelativeDir: string, - existingNames: Record - ) { - const sanitizedId = sanitizeFileName(this.orderedModules[0].id); - this.id = makeUnique( - normalize( - isAbsolute(this.orderedModules[0].id) - ? relative(preserveModulesRelativeDir, sanitizedId) - : '_virtual/' + basename(sanitizedId) - ), - existingNames - ); - } - - generateId( - pattern: string, - patternName: string, - addons: Addons, - options: OutputOptions, - existingNames: Record - ) { - this.id = makeUnique( - renderNamePattern(pattern, patternName, type => { - switch (type) { - case 'format': - return options.format === 'es' ? 'esm' : options.format; - case 'hash': - return this.computeContentHashWithDependencies(addons, options); - case 'name': - return this.getChunkName(); - } - }), - existingNames - ); - } - - getChunkName(): string { - return this.chunkName || (this.chunkName = this.computeChunkName()); - } - - private computeChunkName(): string { - if (this.facadeModule !== null && this.facadeModule.chunkAlias) { - return sanitizeFileName(this.facadeModule.chunkAlias); - } - for (const module of this.orderedModules) { - if (module.chunkAlias) return sanitizeFileName(module.chunkAlias); - } - return 'chunk'; - } - render(options: OutputOptions, addons: Addons, outputChunk: RenderedChunk) { timeStart('render format', 3); @@ -995,12 +613,12 @@ export default class Chunk { const chunkSourcemapChain: RawSourceMap[] = []; return renderChunk({ - graph: this.graph, chunk: this, - renderChunk: outputChunk, code: prevCode, - sourcemapChain: chunkSourcemapChain, - options + graph: this.graph, + options, + renderChunk: outputChunk, + sourcemapChain: chunkSourcemapChain }).then((code: string) => { if (options.sourcemap) { timeStart('sourcemap', 3); @@ -1038,4 +656,386 @@ export default class Chunk { return { code, map }; }); } + + turnIntoFacade(facadedModule: Module) { + this.dependencies = [facadedModule.chunk]; + this.dynamicDependencies = []; + this.facadeModule = facadedModule; + facadedModule.facadeChunk = this; + for (const exportName of facadedModule.getAllExports()) { + const tracedVariable = facadedModule.getVariableForExportName(exportName); + this.exports.add(tracedVariable); + this.exportNames[exportName] = tracedVariable; + } + } + + visitDependencies(handleDependency: (dependency: Chunk | ExternalModule) => void) { + const toBeVisited: (Chunk | ExternalModule)[] = [this]; + const visited: Set = new Set(); + for (const current of toBeVisited) { + handleDependency(current); + if (current instanceof ExternalModule) continue; + for (const dependency of current.dependencies.concat(current.dynamicDependencies)) { + if (!visited.has(dependency)) { + visited.add(dependency); + toBeVisited.push(dependency); + } + } + } + } + + visitStaticDependenciesUntilCondition( + isConditionSatisfied: (dep: Chunk | ExternalModule) => any + ): boolean { + const seen = new Set(); + function visitDep(dep: Chunk | ExternalModule): boolean { + if (seen.has(dep)) return; + seen.add(dep); + if (dep instanceof Chunk) { + for (const subDep of dep.dependencies) { + if (visitDep(subDep)) return true; + } + } + return isConditionSatisfied(dep) === true; + } + return visitDep(this); + } + + private addChunksFromDependencies( + moduleDependencies: (Module | ExternalModule)[], + chunkDependencies: Set + ) { + for (const depModule of moduleDependencies) { + if (depModule.chunk === this) { + continue; + } + let dependency: Chunk | ExternalModule; + if (depModule instanceof Module) { + dependency = depModule.chunk; + } else { + if (!depModule.used && this.graph.isPureExternalModule(depModule.id)) { + continue; + } + dependency = depModule; + } + chunkDependencies.add(dependency); + } + } + + private computeChunkName(): string { + if (this.facadeModule !== null && this.facadeModule.chunkAlias) { + return sanitizeFileName(this.facadeModule.chunkAlias); + } + for (const module of this.orderedModules) { + if (module.chunkAlias) return sanitizeFileName(module.chunkAlias); + } + return 'chunk'; + } + + private computeContentHashWithDependencies(addons: Addons, options: OutputOptions): string { + const hash = sha256(); + + hash.update( + [addons.intro, addons.outro, addons.banner, addons.footer].map(addon => addon || '').join(':') + ); + hash.update(options.format); + this.visitDependencies(dep => { + if (dep instanceof ExternalModule) hash.update(':' + dep.renderPath); + else hash.update(dep.getRenderedHash()); + }); + + return hash.digest('hex').substr(0, 8); + } + + private finaliseDynamicImports() { + for (let i = 0; i < this.orderedModules.length; i++) { + const module = this.orderedModules[i]; + const code = this.renderedModuleSources[i]; + for (const { node, resolution } of module.dynamicImports) { + if (!resolution) continue; + if (resolution instanceof Module) { + if (!resolution.chunk.isEmpty && resolution.chunk !== this) { + const resolutionChunk = resolution.facadeChunk || resolution.chunk; + let relPath = normalize(relative(dirname(this.id), resolutionChunk.id)); + if (!relPath.startsWith('../')) relPath = './' + relPath; + node.renderFinalResolution(code, `'${relPath}'`); + } + } else if (resolution instanceof ExternalModule) { + node.renderFinalResolution(code, `'${resolution.id}'`); + } else { + node.renderFinalResolution(code, resolution); + } + } + } + } + + private finaliseImportMetas(options: OutputOptions): boolean { + let usesMechanism = false; + for (let i = 0; i < this.orderedModules.length; i++) { + const module = this.orderedModules[i]; + const code = this.renderedModuleSources[i]; + for (const importMeta of module.importMetas) { + if (importMeta.renderFinalMechanism(code, this.id, options.format, options.compact)) + usesMechanism = true; + } + } + return usesMechanism; + } + + private getChunkDependencyDeclarations( + options: OutputOptions, + inputBase: string + ): ChunkDependencies { + const reexportDeclarations = new Map(); + + for (let exportName of Object.keys(this.exportNames)) { + let exportModule: Chunk | ExternalModule; + let importName: string; + if (exportName[0] === '*') { + exportModule = this.graph.moduleById.get(exportName.substr(1)); + importName = exportName = '*'; + } else { + const variable = this.exportNames[exportName]; + const module = variable.module; + // skip local exports + if (!module || module.chunk === this) continue; + if (module instanceof Module) { + exportModule = module.chunk; + importName = module.chunk.getVariableExportName(variable); + } else { + exportModule = module; + importName = variable.name; + } + } + let exportDeclaration = reexportDeclarations.get(exportModule); + if (!exportDeclaration) reexportDeclarations.set(exportModule, (exportDeclaration = [])); + exportDeclaration.push({ imported: importName, reexported: exportName }); + } + + const importsAsArray = Array.from(this.imports); + const dependencies: ChunkDependencies = []; + + for (const dep of this.dependencies) { + const imports: ImportSpecifier[] = []; + for (const variable of importsAsArray) { + if ( + (variable.module instanceof Module + ? variable.module.chunk === dep + : variable.module === dep) && + !( + variable instanceof ExportDefaultVariable && + variable.referencesOriginal() && + this.imports.has(variable.getOriginalVariable()) + ) + ) { + const local = variable.getName(); + const imported = + variable.module instanceof ExternalModule + ? variable.name + : variable.module.chunk.getVariableExportName(variable); + imports.push({ local, imported }); + } + } + + const reexports = reexportDeclarations.get(dep); + let exportsNames: boolean, exportsDefault: boolean; + let namedExportsMode = true; + if (dep instanceof ExternalModule) { + exportsNames = dep.exportsNames || dep.exportsNamespace; + exportsDefault = 'default' in dep.declarations; + } else { + exportsNames = true; + // we don't want any interop patterns to trigger + exportsDefault = false; + namedExportsMode = dep.exportMode !== 'default'; + } + + let id: string; + let globalName: string; + if (dep instanceof ExternalModule) { + id = dep.setRenderPath(options, inputBase); + if (options.format === 'umd' || options.format === 'iife') { + globalName = getGlobalName( + dep, + options.globals, + this.graph, + exportsNames || exportsDefault + ); + } + } + + dependencies.push({ + exportsDefault, + exportsNames, + globalName, + id, // chunk id updated on render + imports: imports.length > 0 ? imports : null, + isChunk: !(dep).isExternal, + name: dep.variableName, + namedExportsMode, + reexports + }); + } + + return dependencies; + } + + private getChunkExportDeclarations(): ChunkExports { + const exports: ChunkExports = []; + for (const exportName of Object.keys(this.exportNames)) { + if (exportName[0] === '*') continue; + + const variable = this.exportNames[exportName]; + const module = variable.module; + + if (module && module.chunk !== this) continue; + let hoisted = false; + let uninitialized = false; + if (variable instanceof LocalVariable) { + if (variable.init === UNDEFINED_EXPRESSION) { + uninitialized = true; + } + variable.declarations.forEach(decl => { + if (decl.type === NodeType.ExportDefaultDeclaration) { + if (decl.declaration.type === NodeType.FunctionDeclaration) hoisted = true; + } else if (decl.parent.type === NodeType.FunctionDeclaration) { + hoisted = true; + } + }); + } else if (variable instanceof GlobalVariable) { + hoisted = true; + } + + const localName = variable.getName(); + + exports.push({ + exported: exportName === '*' ? localName : exportName, + hoisted, + local: localName, + uninitialized + }); + } + return exports; + } + + private getVariableExportNamesForModule(module: Module) { + const exportNamesByVariable: Map = new Map(); + for (const exportName of module.getAllExports()) { + const tracedVariable = module.getVariableForExportName(exportName); + if (!tracedVariable || !(tracedVariable.included || tracedVariable.isExternal)) { + continue; + } + const existingExportNames = exportNamesByVariable.get(tracedVariable); + if (existingExportNames) { + existingExportNames.push(exportName); + } else { + exportNamesByVariable.set(tracedVariable, [exportName]); + } + const exportingModule = tracedVariable.module; + if (exportingModule && exportingModule.chunk && exportingModule.chunk !== this) { + exportingModule.chunk.exports.add(tracedVariable); + } + } + return exportNamesByVariable; + } + + private inlineChunkDependencies(chunk: Chunk, deep: boolean) { + for (const dep of chunk.dependencies) { + if (dep instanceof ExternalModule) { + if (this.dependencies.indexOf(dep) === -1) this.dependencies.push(dep); + } else { + if (dep === this || this.dependencies.indexOf(dep) !== -1) continue; + if (!dep.isEmpty) this.dependencies.push(dep); + if (deep) this.inlineChunkDependencies(dep, true); + } + } + } + + private prepareDynamicImports() { + for (const module of this.orderedModules) { + for (const { node, resolution } of module.dynamicImports) { + if (!resolution) continue; + if (resolution instanceof Module) { + if (resolution.chunk === this) { + const namespace = resolution.getOrCreateNamespace(); + node.setResolution(false, namespace.getName()); + } else { + node.setResolution(false); + } + } else if (resolution instanceof ExternalModule) { + node.setResolution(true); + } else { + node.setResolution(false); + } + } + } + } + + private setIdentifierRenderResolutions(options: OutputOptions) { + for (const exportName of Object.keys(this.exportNames)) { + const exportVariable = this.exportNames[exportName]; + if (exportVariable) { + if (exportVariable instanceof ExportShimVariable) { + this.needsExportsShim = true; + } + exportVariable.exportName = exportName; + if ( + options.format !== 'es' && + options.format !== 'system' && + exportVariable.isReassigned && + !exportVariable.isId && + (!isExportDefaultVariable(exportVariable) || !exportVariable.hasId) + ) { + exportVariable.setRenderNames('exports', exportName); + } else { + exportVariable.setRenderNames(null, null); + } + } + } + + const usedNames = Object.create(null); + if (this.needsExportsShim) { + usedNames[MISSING_EXPORT_SHIM_VARIABLE] = true; + } + + deconflictChunk( + this.orderedModules, + this.dependencies, + this.imports, + usedNames, + options.format, + options.interop !== false, + this.graph.preserveModules + ); + } + + private setUpModuleImports(module: Module) { + for (const variable of Array.from(module.imports)) { + if (variable.module.chunk !== this) { + this.imports.add(variable); + if (variable.module instanceof Module) { + variable.module.chunk.exports.add(variable); + } + } + } + if (module.getOrCreateNamespace().included) { + for (const reexportName of Object.keys(module.reexports)) { + const reexport = module.reexports[reexportName]; + const variable = reexport.module.getVariableForExportName(reexport.localName); + if (variable.module.chunk !== this) { + this.imports.add(variable); + if (variable.module instanceof Module) { + variable.module.chunk.exports.add(variable); + } + } + } + } + for (const { node, resolution } of module.dynamicImports) { + if (node.included) { + this.hasDynamicImport = true; + if (resolution instanceof Module && resolution.chunk === this) + resolution.getOrCreateNamespace().include(); + } + } + } } diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index 87f6a2d92c1..0b5dae5f44c 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -5,23 +5,24 @@ import { makeLegal } from './utils/identifierHelpers'; import { isAbsolute, normalize, relative } from './utils/path'; export default class ExternalModule { - private graph: Graph; chunk: void; declarations: { [name: string]: ExternalVariable }; + execIndex: number; exportedVariables: Map; exportsNames = false; exportsNamespace: boolean = false; id: string; - renderPath: string = undefined; - renormalizeRenderPath = false; - isExternal = true; isEntryPoint = false; - variableName: string; + isExternal = true; mostCommonSuggestion: number = 0; nameSuggestions: { [name: string]: number }; reexported: boolean = false; + renderPath: string = undefined; + renormalizeRenderPath = false; used = false; - execIndex: number; + variableName: string; + + private graph: Graph; constructor({ graph, id }: { graph: Graph; id: string }) { this.graph = graph; @@ -36,6 +37,18 @@ export default class ExternalModule { this.exportedVariables = new Map(); } + getVariableForExportName(name: string, _isExportAllSearch?: boolean): ExternalVariable { + if (name !== 'default' && name !== '*') this.exportsNames = true; + if (name === '*') this.exportsNamespace = true; + + let declaration = this.declarations[name]; + if (declaration) return declaration; + + this.declarations[name] = declaration = new ExternalVariable(this, name); + this.exportedVariables.set(declaration, name); + return declaration; + } + setRenderPath(options: OutputOptions, inputBase: string) { this.renderPath = ''; if (options.paths) { @@ -82,21 +95,9 @@ export default class ExternalModule { this.graph.warn({ code: 'UNUSED_EXTERNAL_IMPORT', - source: this.id, + message: `${names} imported from external module '${this.id}' but never used`, names: unused, - message: `${names} imported from external module '${this.id}' but never used` + source: this.id }); } - - getVariableForExportName(name: string, _isExportAllSearch?: boolean): ExternalVariable { - if (name !== 'default' && name !== '*') this.exportsNames = true; - if (name === '*') this.exportsNamespace = true; - - let declaration = this.declarations[name]; - if (declaration) return declaration; - - this.declarations[name] = declaration = new ExternalVariable(this, name); - this.exportedVariables.set(declaration, name); - return declaration; - } } diff --git a/src/Graph.ts b/src/Graph.ts index 62b2c83a6e3..f713513fbf5 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -44,39 +44,34 @@ function makeOnwarn() { } export default class Graph { - curChunkIndex = 0; acornOptions: acorn.Options; acornParser: typeof acorn.Parser; + assetsById = new Map(); cachedModules: Map; + cacheExpiry: number; context: string; + contextParse: (code: string, acornOptions?: acorn.Options) => ESTree.Program; + curChunkIndex = 0; + deoptimizationTracker: EntityPathTracker; externalModules: ExternalModule[] = []; + // track graph build status as each graph instance is used only once + finished = false; getModuleContext: (id: string) => string; + isExternal: IsExternal; isPureExternalModule: (id: string) => boolean; moduleById = new Map(); - assetsById = new Map(); modules: Module[] = []; needsTreeshakingPass: boolean = false; onwarn: WarningHandler; - deoptimizationTracker: EntityPathTracker; + pluginCache: Record; + pluginDriver: PluginDriver; + preserveModules: boolean; scope: GlobalScope; shimMissingExports: boolean; - treeshakingOptions: TreeshakingOptions; - preserveModules: boolean; - - isExternal: IsExternal; - - contextParse: (code: string, acornOptions?: acorn.Options) => ESTree.Program; - - pluginDriver: PluginDriver; - pluginCache: Record; - watchFiles: Record = Object.create(null); - cacheExpiry: number; - - // track graph build status as each graph instance is used only once - finished = false; - // deprecated treeshake: boolean; + treeshakingOptions: TreeshakingOptions; + watchFiles: Record = Object.create(null); constructor(options: InputOptions, watcher?: RollupWatcher) { this.curChunkIndex = 0; @@ -107,9 +102,9 @@ export default class Graph { if (this.treeshake) { this.treeshakingOptions = options.treeshake ? { + annotations: (options.treeshake).annotations !== false, propertyReadSideEffects: (options.treeshake).propertyReadSideEffects !== false, - annotations: (options.treeshake).annotations !== false, pureExternalModules: (options.treeshake).pureExternalModules } : { propertyReadSideEffects: true, annotations: true, pureExternalModules: false }; @@ -194,159 +189,6 @@ export default class Graph { this.acornParser = acorn.Parser.extend(...acornPluginsToInject); } - getCache(): RollupCache { - // handle plugin cache eviction - for (const name in this.pluginCache) { - const cache = this.pluginCache[name]; - let allDeleted = true; - for (const key of Object.keys(cache)) { - if (cache[key][0] >= this.cacheExpiry) delete cache[key]; - else allDeleted = false; - } - if (allDeleted) delete this.pluginCache[name]; - } - - return { - modules: this.modules.map(module => module.toJSON()), - plugins: this.pluginCache - }; - } - - finaliseAssets(assetFileNames: string) { - const outputBundle: OutputBundle = Object.create(null); - this.assetsById.forEach(asset => { - if (asset.source !== undefined) finaliseAsset(asset, outputBundle, assetFileNames); - }); - return outputBundle; - } - - private loadModule(entryName: string) { - return this.pluginDriver - .hookFirst('resolveId', [entryName, undefined]) - .then(id => { - if (id === false) { - error({ - code: 'UNRESOLVED_ENTRY', - message: `Entry module cannot be external` - }); - } - - if (id == null) { - error({ - code: 'UNRESOLVED_ENTRY', - message: `Could not resolve entry (${entryName})` - }); - } - - return this.fetchModule(id, undefined); - }); - } - - private link() { - for (const module of this.modules) { - module.linkDependencies(); - } - for (const module of this.modules) { - module.bindReferences(); - } - this.warnForMissingExports(); - } - - private warnForMissingExports() { - for (const module of this.modules) { - for (const importName of Object.keys(module.importDescriptions)) { - const importDescription = module.importDescriptions[importName]; - if ( - importDescription.name !== '*' && - !importDescription.module.getVariableForExportName(importDescription.name) - ) { - module.warn( - { - code: 'NON_EXISTENT_EXPORT', - name: importDescription.name, - source: importDescription.module.id, - message: `Non-existent export '${ - importDescription.name - }' is imported from ${relativeId(importDescription.module.id)}` - }, - importDescription.start - ); - } - } - } - } - - includeMarked(modules: Module[]) { - if (this.treeshake) { - let treeshakingPass = 1; - do { - timeStart(`treeshaking pass ${treeshakingPass}`, 3); - this.needsTreeshakingPass = false; - for (const module of modules) { - if (module.isExecuted) module.include(); - } - timeEnd(`treeshaking pass ${treeshakingPass++}`, 3); - } while (this.needsTreeshakingPass); - } else { - // Necessary to properly replace namespace imports - for (const module of modules) module.includeAllInBundle(); - } - } - - private loadEntryModules( - entryModules: string | string[] | Record, - manualChunks: Record | void - ) { - let removeAliasExtensions = false; - let entryModuleIds: string[]; - let entryModuleAliases: string[]; - if (typeof entryModules === 'string') entryModules = [entryModules]; - - if (Array.isArray(entryModules)) { - removeAliasExtensions = true; - entryModuleAliases = entryModules.concat([]); - entryModuleIds = entryModules; - } else { - entryModuleAliases = Object.keys(entryModules); - entryModuleIds = entryModuleAliases.map(name => (>entryModules)[name]); - } - - const entryAndManualChunkIds = entryModuleIds.concat([]); - if (manualChunks) { - Object.keys(manualChunks).forEach(name => { - const manualChunkIds = manualChunks[name]; - manualChunkIds.forEach(id => { - if (entryAndManualChunkIds.indexOf(id) === -1) entryAndManualChunkIds.push(id); - }); - }); - } - - return Promise.all(entryAndManualChunkIds.map(id => this.loadModule(id))).then( - entryAndChunkModules => { - if (removeAliasExtensions) { - for (let i = 0; i < entryModuleAliases.length; i++) - entryModuleAliases[i] = getAliasName(entryAndChunkModules[i].id, entryModuleAliases[i]); - } - - const entryModules = entryAndChunkModules.slice(0, entryModuleIds.length); - - let manualChunkModules: { [chunkName: string]: Module[] }; - if (manualChunks) { - manualChunkModules = {}; - for (const chunkName of Object.keys(manualChunks)) { - const chunk = manualChunks[chunkName]; - manualChunkModules[chunkName] = chunk.map(entryId => { - const entryIndex = entryAndManualChunkIds.indexOf(entryId); - return entryAndChunkModules[entryIndex]; - }); - } - } - - return { entryModules, entryModuleAliases, manualChunkModules }; - } - ); - } - build( entryModules: string | string[] | Record, manualChunks: Record | void, @@ -498,106 +340,62 @@ export default class Graph { ); } - private fetchModule(id: string, importer: string): Promise { - // short-circuit cycles - const existingModule = this.moduleById.get(id); - if (existingModule) { - if (existingModule.isExternal) throw new Error(`Cannot fetch external module ${id}`); - return Promise.resolve(existingModule); + finaliseAssets(assetFileNames: string) { + const outputBundle: OutputBundle = Object.create(null); + this.assetsById.forEach(asset => { + if (asset.source !== undefined) finaliseAsset(asset, outputBundle, assetFileNames); + }); + return outputBundle; + } + + getCache(): RollupCache { + // handle plugin cache eviction + for (const name in this.pluginCache) { + const cache = this.pluginCache[name]; + let allDeleted = true; + for (const key of Object.keys(cache)) { + if (cache[key][0] >= this.cacheExpiry) delete cache[key]; + else allDeleted = false; + } + if (allDeleted) delete this.pluginCache[name]; } - const module: Module = new Module(this, id); - this.moduleById.set(id, module); - this.watchFiles[id] = true; + return { + modules: this.modules.map(module => module.toJSON()), + plugins: this.pluginCache + }; + } - timeStart('load modules', 3); - return Promise.resolve(this.pluginDriver.hookFirst('load', [id])) - .catch((err: Error) => { - timeEnd('load modules', 3); - let msg = `Could not load ${id}`; - if (importer) msg += ` (imported by ${importer})`; + includeMarked(modules: Module[]) { + if (this.treeshake) { + let treeshakingPass = 1; + do { + timeStart(`treeshaking pass ${treeshakingPass}`, 3); + this.needsTreeshakingPass = false; + for (const module of modules) { + if (module.isExecuted) module.include(); + } + timeEnd(`treeshaking pass ${treeshakingPass++}`, 3); + } while (this.needsTreeshakingPass); + } else { + // Necessary to properly replace namespace imports + for (const module of modules) module.includeAllInBundle(); + } + } - msg += `: ${err.message}`; - throw new Error(msg); - }) - .then(source => { - timeEnd('load modules', 3); - if (typeof source === 'string') return source; - if (source && typeof source === 'object' && typeof source.code === 'string') return source; + warn(warning: RollupWarning) { + warning.toString = () => { + let str = ''; - // TODO report which plugin failed - error({ - code: 'BAD_LOADER', - message: `Error loading ${relativeId( - id - )}: plugin load hook should return a string, a { code, map } object, or nothing/null` - }); - }) - .then(source => { - const sourceDescription: SourceDescription = - typeof source === 'string' - ? { - code: source, - ast: null - } - : source; - - const cachedModule = this.cachedModules.get(id); - if ( - cachedModule && - !cachedModule.customTransformCache && - cachedModule.originalCode === sourceDescription.code - ) { - // re-emit transform assets - if (cachedModule.transformAssets) { - for (const asset of cachedModule.transformAssets) - this.pluginDriver.emitAsset(asset.name, asset.source); - } - return cachedModule; - } - - return transform(this, sourceDescription, module); - }) - .then((source: ModuleJSON) => { - module.setSource(source); - - this.modules.push(module); - this.moduleById.set(id, module); + if (warning.plugin) str += `(${warning.plugin} plugin) `; + if (warning.loc) + str += `${relativeId(warning.loc.file)} (${warning.loc.line}:${warning.loc.column}) `; + str += warning.message; - return this.fetchAllDependencies(module).then(() => { - for (const name in module.exports) { - if (name !== 'default') { - module.exportsAll[name] = module.id; - } - } - module.exportAllSources.forEach(source => { - const id = module.resolvedIds[source].id; - const exportAllModule = this.moduleById.get(id); - if (exportAllModule.isExternal) return; + return str; + }; - for (const name in (exportAllModule).exportsAll) { - if (name in module.exportsAll) { - this.warn({ - code: 'NAMESPACE_CONFLICT', - reexporter: module.id, - name, - sources: [module.exportsAll[name], (exportAllModule).exportsAll[name]], - message: `Conflicting namespaces: ${relativeId( - module.id - )} re-exports '${name}' from both ${relativeId( - module.exportsAll[name] - )} and ${relativeId( - (exportAllModule).exportsAll[name] - )} (will be ignored)` - }); - } else { - module.exportsAll[name] = (exportAllModule).exportsAll[name]; - } - } - }); - return module; - }); - }); + this.onwarn(warning); } private fetchAllDependencies(module: Module) { @@ -669,11 +467,11 @@ export default class Graph { if (resolvedId !== false) { this.warn({ code: 'UNRESOLVED_IMPORT', - source, importer: relativeId(module.id), message: `'${source}' is imported by ${relativeId( module.id )}, but could not be resolved – treating it as an external dependency`, + source, url: 'https://rollupjs.org/guide/en#warning-treating-module-as-external-dependency' }); @@ -717,18 +515,215 @@ export default class Graph { ).then(() => fetchDynamicImportsPromise); } - warn(warning: RollupWarning) { - warning.toString = () => { - let str = ''; + private fetchModule(id: string, importer: string): Promise { + // short-circuit cycles + const existingModule = this.moduleById.get(id); + if (existingModule) { + if (existingModule.isExternal) throw new Error(`Cannot fetch external module ${id}`); + return Promise.resolve(existingModule); + } - if (warning.plugin) str += `(${warning.plugin} plugin) `; - if (warning.loc) - str += `${relativeId(warning.loc.file)} (${warning.loc.line}:${warning.loc.column}) `; - str += warning.message; + const module: Module = new Module(this, id); + this.moduleById.set(id, module); + this.watchFiles[id] = true; - return str; - }; + timeStart('load modules', 3); + return Promise.resolve(this.pluginDriver.hookFirst('load', [id])) + .catch((err: Error) => { + timeEnd('load modules', 3); + let msg = `Could not load ${id}`; + if (importer) msg += ` (imported by ${importer})`; - this.onwarn(warning); + msg += `: ${err.message}`; + throw new Error(msg); + }) + .then(source => { + timeEnd('load modules', 3); + if (typeof source === 'string') return source; + if (source && typeof source === 'object' && typeof source.code === 'string') return source; + + // TODO report which plugin failed + error({ + code: 'BAD_LOADER', + message: `Error loading ${relativeId( + id + )}: plugin load hook should return a string, a { code, map } object, or nothing/null` + }); + }) + .then(source => { + const sourceDescription: SourceDescription = + typeof source === 'string' + ? { + ast: null, + code: source + } + : source; + + const cachedModule = this.cachedModules.get(id); + if ( + cachedModule && + !cachedModule.customTransformCache && + cachedModule.originalCode === sourceDescription.code + ) { + // re-emit transform assets + if (cachedModule.transformAssets) { + for (const asset of cachedModule.transformAssets) + this.pluginDriver.emitAsset(asset.name, asset.source); + } + return cachedModule; + } + + return transform(this, sourceDescription, module); + }) + .then((source: ModuleJSON) => { + module.setSource(source); + + this.modules.push(module); + this.moduleById.set(id, module); + + return this.fetchAllDependencies(module).then(() => { + for (const name in module.exports) { + if (name !== 'default') { + module.exportsAll[name] = module.id; + } + } + module.exportAllSources.forEach(source => { + const id = module.resolvedIds[source].id; + const exportAllModule = this.moduleById.get(id); + if (exportAllModule.isExternal) return; + + for (const name in (exportAllModule).exportsAll) { + if (name in module.exportsAll) { + this.warn({ + code: 'NAMESPACE_CONFLICT', + message: `Conflicting namespaces: ${relativeId( + module.id + )} re-exports '${name}' from both ${relativeId( + module.exportsAll[name] + )} and ${relativeId( + (exportAllModule).exportsAll[name] + )} (will be ignored)`, + name, + reexporter: module.id, + sources: [module.exportsAll[name], (exportAllModule).exportsAll[name]] + }); + } else { + module.exportsAll[name] = (exportAllModule).exportsAll[name]; + } + } + }); + return module; + }); + }); + } + + private link() { + for (const module of this.modules) { + module.linkDependencies(); + } + for (const module of this.modules) { + module.bindReferences(); + } + this.warnForMissingExports(); + } + + private loadEntryModules( + entryModules: string | string[] | Record, + manualChunks: Record | void + ) { + let removeAliasExtensions = false; + let entryModuleIds: string[]; + let entryModuleAliases: string[]; + if (typeof entryModules === 'string') entryModules = [entryModules]; + + if (Array.isArray(entryModules)) { + removeAliasExtensions = true; + entryModuleAliases = entryModules.concat([]); + entryModuleIds = entryModules; + } else { + entryModuleAliases = Object.keys(entryModules); + entryModuleIds = entryModuleAliases.map(name => (>entryModules)[name]); + } + + const entryAndManualChunkIds = entryModuleIds.concat([]); + if (manualChunks) { + Object.keys(manualChunks).forEach(name => { + const manualChunkIds = manualChunks[name]; + manualChunkIds.forEach(id => { + if (entryAndManualChunkIds.indexOf(id) === -1) entryAndManualChunkIds.push(id); + }); + }); + } + + return Promise.all(entryAndManualChunkIds.map(id => this.loadModule(id))).then( + entryAndChunkModules => { + if (removeAliasExtensions) { + for (let i = 0; i < entryModuleAliases.length; i++) + entryModuleAliases[i] = getAliasName(entryAndChunkModules[i].id, entryModuleAliases[i]); + } + + const entryModules = entryAndChunkModules.slice(0, entryModuleIds.length); + + let manualChunkModules: { [chunkName: string]: Module[] }; + if (manualChunks) { + manualChunkModules = {}; + for (const chunkName of Object.keys(manualChunks)) { + const chunk = manualChunks[chunkName]; + manualChunkModules[chunkName] = chunk.map(entryId => { + const entryIndex = entryAndManualChunkIds.indexOf(entryId); + return entryAndChunkModules[entryIndex]; + }); + } + } + + return { entryModules, entryModuleAliases, manualChunkModules }; + } + ); + } + + private loadModule(entryName: string) { + return this.pluginDriver + .hookFirst('resolveId', [entryName, undefined]) + .then(id => { + if (id === false) { + error({ + code: 'UNRESOLVED_ENTRY', + message: `Entry module cannot be external` + }); + } + + if (id == null) { + error({ + code: 'UNRESOLVED_ENTRY', + message: `Could not resolve entry (${entryName})` + }); + } + + return this.fetchModule(id, undefined); + }); + } + + private warnForMissingExports() { + for (const module of this.modules) { + for (const importName of Object.keys(module.importDescriptions)) { + const importDescription = module.importDescriptions[importName]; + if ( + importDescription.name !== '*' && + !importDescription.module.getVariableForExportName(importDescription.name) + ) { + module.warn( + { + code: 'NON_EXISTENT_EXPORT', + message: `Non-existent export '${ + importDescription.name + }' is imported from ${relativeId(importDescription.module.id)}`, + name: importDescription.name, + source: importDescription.module.id + }, + importDescription.start + ); + } + } + } } } diff --git a/src/Module.ts b/src/Module.ts index dc369fe36f6..46bed56176a 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -44,29 +44,29 @@ import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames'; export interface CommentDescription { block: boolean; - text: string; - start: number; end: number; + start: number; + text: string; } export interface ImportDescription { + module: Module | ExternalModule | null; + name: string; source: string; start: number; - name: string; - module: Module | ExternalModule | null; } export interface ExportDescription { - localName: string; identifier?: string; + localName: string; node?: Node; } export interface ReexportDescription { localName: string; - start: number; - source: string; module: Module; + source: string; + start: number; } export interface AstContext { @@ -76,6 +76,7 @@ export interface AstContext { ) => void; addImport: (node: ImportDeclaration) => void; addImportMeta: (node: MetaProperty) => void; + annotations: boolean; code: string; deoptimizationTracker: EntityPathTracker; error: (props: RollupError, pos: number) => void; @@ -86,16 +87,15 @@ export interface AstContext { getModuleName: () => string; getReexports: () => string[]; importDescriptions: { [name: string]: ImportDescription }; - isCrossChunkImport: (importDescription: ImportDescription) => boolean; includeDynamicImport: (node: Import) => void; includeVariable: (variable: Variable) => void; + isCrossChunkImport: (importDescription: ImportDescription) => boolean; magicString: MagicString; - moduleContext: string; module: Module; // not to be used for tree-shaking + moduleContext: string; nodeConstructors: { [name: string]: typeof NodeBase }; preserveModules: boolean; propertyReadSideEffects: boolean; - annotations: boolean; traceExport: (name: string) => Variable; traceVariable: (name: string) => Variable; treeshake: boolean; @@ -105,8 +105,8 @@ export interface AstContext { export const defaultAcornOptions: acorn.Options = { ecmaVersion: 2019, - sourceType: 'module', - preserveParens: false + preserveParens: false, + sourceType: 'module' }; function tryParse(module: Module, Parser: typeof acorn.Parser, acornOptions: acorn.Options) { @@ -155,7 +155,6 @@ const MISSING_EXPORT_SHIM_DESCRIPTION: ExportDescription = { }; export default class Module { - type: 'Module'; chunk: Chunk; chunkAlias: string = undefined; code: string; @@ -165,18 +164,18 @@ export default class Module { dynamicallyImportedBy: Module[] = []; dynamicDependencies: (Module | ExternalModule)[] = []; dynamicImports: { - node: Import; alias: string | null; + node: Import; resolution: Module | ExternalModule | string | void; }[] = []; entryPointsHash: Uint8Array = new Uint8Array(10); - exportAllModules: (Module | ExternalModule)[] = null; - exportShimVariable: ExportShimVariable = new ExportShimVariable(this); excludeFromSourcemap: boolean; execIndex: number = Infinity; + exportAllModules: (Module | ExternalModule)[] = null; + exportAllSources: string[] = []; exports: { [name: string]: ExportDescription } = Object.create(null); exportsAll: { [name: string]: string } = Object.create(null); - exportAllSources: string[] = []; + exportShimVariable: ExportShimVariable = new ExportShimVariable(this); facadeChunk: Chunk | null = null; id: string; importDescriptions: { [name: string]: ImportDescription } = Object.create(null); @@ -193,6 +192,7 @@ export default class Module { sourcemapChain: RawSourceMap[]; sources: string[] = []; transformAssets: Asset[]; + type: 'Module'; usesTopLevelAwait: boolean = false; private ast: Program; @@ -211,233 +211,185 @@ export default class Module { this.context = graph.getModuleContext(id); } - setSource({ - code, - originalCode, - originalSourcemap, - ast, - sourcemapChain, - resolvedIds, - transformDependencies, - customTransformCache - }: ModuleJSON) { - this.code = code; - this.originalCode = originalCode; - this.originalSourcemap = originalSourcemap; - this.sourcemapChain = sourcemapChain; - this.transformDependencies = transformDependencies; - this.customTransformCache = customTransformCache; + basename() { + const base = basename(this.id); + const ext = extname(this.id); - timeStart('generate ast', 3); + return makeLegal(ext ? base.slice(0, -ext.length) : base); + } - this.esTreeAst = ( - (ast || tryParse(this, this.graph.acornParser, this.graph.acornOptions)) - ); - markPureCallExpressions(this.comments, this.esTreeAst); + bindReferences() { + this.ast.bind(); + } - timeEnd('generate ast', 3); + error(props: RollupError, pos: number) { + if (pos !== undefined) { + props.pos = pos; - this.resolvedIds = resolvedIds || Object.create(null); + let location = locate(this.code, pos, { offsetLine: 1 }); + try { + location = getOriginalLocation(this.sourcemapChain, location); + } catch (e) { + this.warn( + { + code: 'SOURCEMAP_ERROR', + loc: { + column: location.column, + file: this.id, + line: location.line + }, + message: `Error when using sourcemap for reporting an error: ${e.message}`, + pos + }, + undefined + ); + } - // By default, `id` is the filename. Custom resolvers and loaders - // can change that, but it makes sense to use it for the source filename - const fileName = this.id; + props.loc = { + column: location.column, + file: this.id, + line: location.line + }; + props.frame = getCodeFrame(this.originalCode, location.line, location.column); + } - this.magicString = new MagicString(code, { - filename: this.excludeFromSourcemap ? null : fileName, // don't include plugin helpers in sourcemap - indentExclusionRanges: [] - }); - this.removeExistingSourceMap(); + error(props); + } - timeStart('analyse ast', 3); + getAllExports() { + const allExports = Object.assign(Object.create(null), this.exports, this.reexports); - this.astContext = { - addDynamicImport: this.addDynamicImport.bind(this), - addExport: this.addExport.bind(this), - addImport: this.addImport.bind(this), - addImportMeta: this.addImportMeta.bind(this), - code, // Only needed for debugging - error: this.error.bind(this), - fileName, // Needed for warnings - getAssetFileName: this.graph.pluginDriver.getAssetFileName, - getExports: this.getExports.bind(this), - getReexports: this.getReexports.bind(this), - getModuleExecIndex: () => this.execIndex, - getModuleName: this.basename.bind(this), - importDescriptions: this.importDescriptions, - includeDynamicImport: this.includeDynamicImport.bind(this), - includeVariable: this.includeVariable.bind(this), - isCrossChunkImport: importDescription => importDescription.module.chunk !== this.chunk, - magicString: this.magicString, - module: this, - moduleContext: this.context, - nodeConstructors, - preserveModules: this.graph.preserveModules, - propertyReadSideEffects: - !this.graph.treeshake || this.graph.treeshakingOptions.propertyReadSideEffects, - annotations: this.graph.treeshake && this.graph.treeshakingOptions.annotations, - deoptimizationTracker: this.graph.deoptimizationTracker, - traceExport: this.getVariableForExportName.bind(this), - traceVariable: this.traceVariable.bind(this), - treeshake: this.graph.treeshake, - usesTopLevelAwait: false, - warn: this.warn.bind(this) - }; + this.exportAllModules.forEach(module => { + if (module.isExternal) { + allExports[`*${module.id}`] = true; + return; + } - this.scope = new ModuleScope(this.graph.scope, this.astContext); - this.ast = new Program( - this.esTreeAst, - { type: 'Module', context: this.astContext }, - this.scope - ); + for (const name of (module).getAllExports()) { + if (name !== 'default') allExports[name] = true; + } + }); - timeEnd('analyse ast', 3); + return Object.keys(allExports); } - private removeExistingSourceMap() { - for (const comment of this.comments) { - if (!comment.block && SOURCEMAPPING_URL_RE.test(comment.text)) { - this.magicString.remove(comment.start, comment.end); + getDynamicImportExpressions(): (string | Node)[] { + return this.dynamicImports.map(({ node }) => { + const importArgument = node.parent.arguments[0]; + if (isTemplateLiteral(importArgument)) { + if (importArgument.expressions.length === 0 && importArgument.quasis.length === 1) { + return importArgument.quasis[0].value.cooked; + } + } else if (isLiteral(importArgument)) { + if (typeof importArgument.value === 'string') { + return importArgument.value; + } + } else { + return importArgument; } - } + }); } - private addExport( - node: ExportAllDeclaration | ExportNamedDeclaration | ExportDefaultDeclaration - ) { - const source = (node).source && (node).source.value; + getExports() { + return Object.keys(this.exports); + } - // export { name } from './other' - if (source) { - if (this.sources.indexOf(source) === -1) this.sources.push(source); + getOrCreateNamespace(): NamespaceVariable { + return ( + this.namespaceVariable || (this.namespaceVariable = new NamespaceVariable(this.astContext)) + ); + } - if (node.type === NodeType.ExportAllDeclaration) { - // Store `export * from '...'` statements in an array of delegates. - // When an unknown import is encountered, we see if one of them can satisfy it. - this.exportAllSources.push(source); - } else { - for (const specifier of (node).specifiers) { - const name = specifier.exported.name; + getReexports() { + const reexports = Object.create(null); - if (this.exports[name] || this.reexports[name]) { - this.error( - { - code: 'DUPLICATE_EXPORT', - message: `A module cannot have multiple exports with the same name ('${name}')` - }, - specifier.start - ); - } + for (const name in this.reexports) { + reexports[name] = true; + } - this.reexports[name] = { - start: specifier.start, - source, - localName: specifier.local.name, - module: null // filled in later - }; - } - } - } else if (isExportDefaultDeclaration(node)) { - // export default function foo () {} - // export default foo; - // export default 42; - if (this.exports.default) { - this.error( - { - code: 'DUPLICATE_EXPORT', - message: `A module can only have one default export` - }, - node.start - ); + this.exportAllModules.forEach(module => { + if (module.isExternal) { + reexports[`*${module.id}`] = true; + return; } - this.exports.default = { - localName: 'default', - identifier: node.variable.getOriginalVariableName(), - node - }; - } else if ((node).declaration) { - // export var { foo, bar } = ... - // export var foo = 42; - // export var a = 1, b = 2, c = 3; - // export function foo () {} - const declaration = (node).declaration; - - if (declaration.type === NodeType.VariableDeclaration) { - for (const decl of declaration.declarations) { - for (const localName of extractNames(decl.id)) { - this.exports[localName] = { localName, node }; - } - } - } else { - // export function foo () {} - const localName = declaration.id.name; - this.exports[localName] = { localName, node }; + for (const name of (module).getExports().concat((module).getReexports())) { + if (name !== 'default') reexports[name] = true; } - } else { - // export { foo, bar, baz } - for (const specifier of (node).specifiers) { - const localName = specifier.local.name; - const exportedName = specifier.exported.name; + }); - if (this.exports[exportedName] || this.reexports[exportedName]) { - this.error( - { - code: 'DUPLICATE_EXPORT', - message: `A module cannot have multiple exports with the same name ('${exportedName}')` - }, - specifier.start - ); - } + return Object.keys(reexports); + } - this.exports[exportedName] = { localName, node }; - } + getRenderedExports() { + // only direct exports are counted here, not reexports at all + const renderedExports: string[] = []; + const removedExports: string[] = []; + for (const exportName in this.exports) { + const expt = this.exports[exportName]; + (expt.node && expt.node.included ? renderedExports : removedExports).push(exportName); } + return { renderedExports, removedExports }; } - private addImport(node: ImportDeclaration) { - const source = node.source.value; - - if (this.sources.indexOf(source) === -1) this.sources.push(source); + getVariableForExportName(name: string, isExportAllSearch?: boolean): Variable | null { + if (name[0] === '*') { + if (name.length === 1) { + return this.getOrCreateNamespace(); + } else { + // export * from 'external' + const module = this.graph.moduleById.get(name.slice(1)); + return module.getVariableForExportName('*'); + } + } - for (const specifier of node.specifiers) { - const localName = specifier.local.name; + // export { foo } from './other' + const reexportDeclaration = this.reexports[name]; + if (reexportDeclaration) { + const declaration = reexportDeclaration.module.getVariableForExportName( + reexportDeclaration.localName + ); - if (this.importDescriptions[localName]) { - this.error( - { - code: 'DUPLICATE_IMPORT', - message: `Duplicated import '${localName}'` - }, - specifier.start + if (!declaration) { + handleMissingExport( + reexportDeclaration.localName, + this, + reexportDeclaration.module.id, + reexportDeclaration.start ); } - const isDefault = specifier.type === NodeType.ImportDefaultSpecifier; - const isNamespace = specifier.type === NodeType.ImportNamespaceSpecifier; + return declaration; + } - const name = isDefault - ? 'default' - : isNamespace - ? '*' - : (specifier).imported.name; - this.importDescriptions[localName] = { source, start: specifier.start, name, module: null }; + const exportDeclaration = this.exports[name]; + if (exportDeclaration) { + if (exportDeclaration === MISSING_EXPORT_SHIM_DESCRIPTION) { + return this.exportShimVariable; + } + const name = exportDeclaration.localName; + return this.traceVariable(name) || this.graph.scope.findVariable(name); } - } - private addDynamicImport(node: Import) { - this.dynamicImports.push({ node, alias: undefined, resolution: undefined }); - } + if (name !== 'default') { + for (let i = 0; i < this.exportAllModules.length; i += 1) { + const module = this.exportAllModules[i]; + const declaration = module.getVariableForExportName(name, true); - private addImportMeta(node: MetaProperty) { - this.importMetas.push(node); - } + if (declaration) return declaration; + } + } - basename() { - const base = basename(this.id); - const ext = extname(this.id); + // we don't want to create shims when we are just + // probing export * modules for exports + if (this.graph.shimMissingExports && !isExportAllSearch) { + this.shimMissingExport(name); + return this.exportShimVariable; + } + } - return makeLegal(ext ? base.slice(0, -ext.length) : base); + include(): void { + if (this.ast.shouldBeIncluded()) this.ast.include(false); } includeAllExports() { @@ -473,6 +425,14 @@ export default class Module { } } + includeAllInBundle() { + this.ast.include(true); + } + + isIncluded() { + return this.ast.included || (this.namespaceVariable && this.namespaceVariable.included); + } + linkDependencies() { for (const source of this.sources) { const id = this.resolvedIds[source].id; @@ -497,170 +457,109 @@ export default class Module { }); } - private addModulesToSpecifiers(specifiers: { - [name: string]: ImportDescription | ReexportDescription; - }) { - for (const name of Object.keys(specifiers)) { - const specifier = specifiers[name]; - const id = this.resolvedIds[specifier.source].id; - specifier.module = this.graph.moduleById.get(id); - } - } - - bindReferences() { - this.ast.bind(); - } - - getDynamicImportExpressions(): (string | Node)[] { - return this.dynamicImports.map(({ node }) => { - const importArgument = node.parent.arguments[0]; - if (isTemplateLiteral(importArgument)) { - if (importArgument.expressions.length === 0 && importArgument.quasis.length === 1) { - return importArgument.quasis[0].value.cooked; - } - } else if (isLiteral(importArgument)) { - if (typeof importArgument.value === 'string') { - return importArgument.value; - } - } else { - return importArgument; - } - }); + render(options: RenderOptions): MagicString { + const magicString = this.magicString.clone(); + this.ast.render(magicString, options); + this.usesTopLevelAwait = this.astContext.usesTopLevelAwait; + return magicString; } - error(props: RollupError, pos: number) { - if (pos !== undefined) { - props.pos = pos; + setSource({ + code, + originalCode, + originalSourcemap, + ast, + sourcemapChain, + resolvedIds, + transformDependencies, + customTransformCache + }: ModuleJSON) { + this.code = code; + this.originalCode = originalCode; + this.originalSourcemap = originalSourcemap; + this.sourcemapChain = sourcemapChain; + this.transformDependencies = transformDependencies; + this.customTransformCache = customTransformCache; - let location = locate(this.code, pos, { offsetLine: 1 }); - try { - location = getOriginalLocation(this.sourcemapChain, location); - } catch (e) { - this.warn( - { - loc: { - file: this.id, - line: location.line, - column: location.column - }, - pos, - message: `Error when using sourcemap for reporting an error: ${e.message}`, - code: 'SOURCEMAP_ERROR' - }, - undefined - ); - } + timeStart('generate ast', 3); - props.loc = { - file: this.id, - line: location.line, - column: location.column - }; - props.frame = getCodeFrame(this.originalCode, location.line, location.column); - } + this.esTreeAst = ( + (ast || tryParse(this, this.graph.acornParser, this.graph.acornOptions)) + ); + markPureCallExpressions(this.comments, this.esTreeAst); - error(props); - } + timeEnd('generate ast', 3); - getAllExports() { - const allExports = Object.assign(Object.create(null), this.exports, this.reexports); + this.resolvedIds = resolvedIds || Object.create(null); - this.exportAllModules.forEach(module => { - if (module.isExternal) { - allExports[`*${module.id}`] = true; - return; - } + // By default, `id` is the filename. Custom resolvers and loaders + // can change that, but it makes sense to use it for the source filename + const fileName = this.id; - for (const name of (module).getAllExports()) { - if (name !== 'default') allExports[name] = true; - } + this.magicString = new MagicString(code, { + filename: this.excludeFromSourcemap ? null : fileName, // don't include plugin helpers in sourcemap + indentExclusionRanges: [] }); + this.removeExistingSourceMap(); - return Object.keys(allExports); - } + timeStart('analyse ast', 3); - getExports() { - return Object.keys(this.exports); - } + this.astContext = { + addDynamicImport: this.addDynamicImport.bind(this), + addExport: this.addExport.bind(this), + addImport: this.addImport.bind(this), + addImportMeta: this.addImportMeta.bind(this), + annotations: this.graph.treeshake && this.graph.treeshakingOptions.annotations, + code, // Only needed for debugging + deoptimizationTracker: this.graph.deoptimizationTracker, + error: this.error.bind(this), + fileName, // Needed for warnings + getAssetFileName: this.graph.pluginDriver.getAssetFileName, + getExports: this.getExports.bind(this), + getModuleExecIndex: () => this.execIndex, + getModuleName: this.basename.bind(this), + getReexports: this.getReexports.bind(this), + importDescriptions: this.importDescriptions, + includeDynamicImport: this.includeDynamicImport.bind(this), + includeVariable: this.includeVariable.bind(this), + isCrossChunkImport: importDescription => importDescription.module.chunk !== this.chunk, + magicString: this.magicString, + module: this, + moduleContext: this.context, + nodeConstructors, + preserveModules: this.graph.preserveModules, + propertyReadSideEffects: + !this.graph.treeshake || this.graph.treeshakingOptions.propertyReadSideEffects, + traceExport: this.getVariableForExportName.bind(this), + traceVariable: this.traceVariable.bind(this), + treeshake: this.graph.treeshake, + usesTopLevelAwait: false, + warn: this.warn.bind(this) + }; - getReexports() { - const reexports = Object.create(null); + this.scope = new ModuleScope(this.graph.scope, this.astContext); + this.ast = new Program( + this.esTreeAst, + { type: 'Module', context: this.astContext }, + this.scope + ); - for (const name in this.reexports) { - reexports[name] = true; - } - - this.exportAllModules.forEach(module => { - if (module.isExternal) { - reexports[`*${module.id}`] = true; - return; - } - - for (const name of (module).getExports().concat((module).getReexports())) { - if (name !== 'default') reexports[name] = true; - } - }); - - return Object.keys(reexports); - } - - includeAllInBundle() { - this.ast.include(true); - } - - isIncluded() { - return this.ast.included || (this.namespaceVariable && this.namespaceVariable.included); - } - - include(): void { - if (this.ast.shouldBeIncluded()) this.ast.include(false); - } - - getOrCreateNamespace(): NamespaceVariable { - return ( - this.namespaceVariable || (this.namespaceVariable = new NamespaceVariable(this.astContext)) - ); - } - - private includeDynamicImport(node: Import) { - const resolution = this.dynamicImports.find(dynamicImport => dynamicImport.node === node) - .resolution; - if (resolution instanceof Module) { - resolution.dynamicallyImportedBy.push(this); - resolution.includeAllExports(); - } - } - - private includeVariable(variable: Variable) { - if (!variable.included) { - variable.include(); - this.graph.needsTreeshakingPass = true; - } - if (variable.module && variable.module !== this) { - this.imports.add(variable); - } - } - - render(options: RenderOptions): MagicString { - const magicString = this.magicString.clone(); - this.ast.render(magicString, options); - this.usesTopLevelAwait = this.astContext.usesTopLevelAwait; - return magicString; - } + timeEnd('analyse ast', 3); + } toJSON(): ModuleJSON { return { - id: this.id, - dependencies: this.dependencies.map(module => module.id), - transformDependencies: this.transformDependencies, - transformAssets: this.transformAssets, + ast: this.esTreeAst, code: this.code, + customTransformCache: this.customTransformCache, + dependencies: this.dependencies.map(module => module.id), + id: this.id, originalCode: this.originalCode, originalSourcemap: this.originalSourcemap, - ast: this.esTreeAst, - sourcemapChain: this.sourcemapChain, resolvedIds: this.resolvedIds, - customTransformCache: this.customTransformCache + sourcemapChain: this.sourcemapChain, + transformAssets: this.transformAssets, + transformDependencies: this.transformDependencies }; } @@ -689,94 +588,195 @@ export default class Module { return null; } - getRenderedExports() { - // only direct exports are counted here, not reexports at all - const renderedExports: string[] = []; - const removedExports: string[] = []; - for (const exportName in this.exports) { - const expt = this.exports[exportName]; - (expt.node && expt.node.included ? renderedExports : removedExports).push(exportName); + warn(warning: RollupWarning, pos: number) { + if (pos !== undefined) { + warning.pos = pos; + + const { line, column } = locate(this.code, pos, { offsetLine: 1 }); // TODO trace sourcemaps, cf. error() + + warning.loc = { file: this.id, line, column }; + warning.frame = getCodeFrame(this.code, line, column); } - return { renderedExports, removedExports }; + + warning.id = this.id; + this.graph.warn(warning); } - getVariableForExportName(name: string, isExportAllSearch?: boolean): Variable | null { - if (name[0] === '*') { - if (name.length === 1) { - return this.getOrCreateNamespace(); + private addDynamicImport(node: Import) { + this.dynamicImports.push({ node, alias: undefined, resolution: undefined }); + } + + private addExport( + node: ExportAllDeclaration | ExportNamedDeclaration | ExportDefaultDeclaration + ) { + const source = (node).source && (node).source.value; + + // export { name } from './other' + if (source) { + if (this.sources.indexOf(source) === -1) this.sources.push(source); + + if (node.type === NodeType.ExportAllDeclaration) { + // Store `export * from '...'` statements in an array of delegates. + // When an unknown import is encountered, we see if one of them can satisfy it. + this.exportAllSources.push(source); } else { - // export * from 'external' - const module = this.graph.moduleById.get(name.slice(1)); - return module.getVariableForExportName('*'); - } - } + for (const specifier of (node).specifiers) { + const name = specifier.exported.name; - // export { foo } from './other' - const reexportDeclaration = this.reexports[name]; - if (reexportDeclaration) { - const declaration = reexportDeclaration.module.getVariableForExportName( - reexportDeclaration.localName - ); + if (this.exports[name] || this.reexports[name]) { + this.error( + { + code: 'DUPLICATE_EXPORT', + message: `A module cannot have multiple exports with the same name ('${name}')` + }, + specifier.start + ); + } - if (!declaration) { - handleMissingExport( - reexportDeclaration.localName, - this, - reexportDeclaration.module.id, - reexportDeclaration.start + this.reexports[name] = { + localName: specifier.local.name, + module: null, // filled in later, + source, + start: specifier.start + }; + } + } + } else if (isExportDefaultDeclaration(node)) { + // export default function foo () {} + // export default foo; + // export default 42; + if (this.exports.default) { + this.error( + { + code: 'DUPLICATE_EXPORT', + message: `A module can only have one default export` + }, + node.start ); } - return declaration; - } + this.exports.default = { + identifier: node.variable.getOriginalVariableName(), + localName: 'default', + node + }; + } else if ((node).declaration) { + // export var { foo, bar } = ... + // export var foo = 42; + // export var a = 1, b = 2, c = 3; + // export function foo () {} + const declaration = (node).declaration; - const exportDeclaration = this.exports[name]; - if (exportDeclaration) { - if (exportDeclaration === MISSING_EXPORT_SHIM_DESCRIPTION) { - return this.exportShimVariable; + if (declaration.type === NodeType.VariableDeclaration) { + for (const decl of declaration.declarations) { + for (const localName of extractNames(decl.id)) { + this.exports[localName] = { localName, node }; + } + } + } else { + // export function foo () {} + const localName = declaration.id.name; + this.exports[localName] = { localName, node }; } - const name = exportDeclaration.localName; - return this.traceVariable(name) || this.graph.scope.findVariable(name); - } + } else { + // export { foo, bar, baz } + for (const specifier of (node).specifiers) { + const localName = specifier.local.name; + const exportedName = specifier.exported.name; - if (name !== 'default') { - for (let i = 0; i < this.exportAllModules.length; i += 1) { - const module = this.exportAllModules[i]; - const declaration = module.getVariableForExportName(name, true); + if (this.exports[exportedName] || this.reexports[exportedName]) { + this.error( + { + code: 'DUPLICATE_EXPORT', + message: `A module cannot have multiple exports with the same name ('${exportedName}')` + }, + specifier.start + ); + } - if (declaration) return declaration; + this.exports[exportedName] = { localName, node }; } } + } - // we don't want to create shims when we are just - // probing export * modules for exports - if (this.graph.shimMissingExports && !isExportAllSearch) { - this.shimMissingExport(name); - return this.exportShimVariable; + private addImport(node: ImportDeclaration) { + const source = node.source.value; + + if (this.sources.indexOf(source) === -1) this.sources.push(source); + + for (const specifier of node.specifiers) { + const localName = specifier.local.name; + + if (this.importDescriptions[localName]) { + this.error( + { + code: 'DUPLICATE_IMPORT', + message: `Duplicated import '${localName}'` + }, + specifier.start + ); + } + + const isDefault = specifier.type === NodeType.ImportDefaultSpecifier; + const isNamespace = specifier.type === NodeType.ImportNamespaceSpecifier; + + const name = isDefault + ? 'default' + : isNamespace + ? '*' + : (specifier).imported.name; + this.importDescriptions[localName] = { source, start: specifier.start, name, module: null }; } } - warn(warning: RollupWarning, pos: number) { - if (pos !== undefined) { - warning.pos = pos; + private addImportMeta(node: MetaProperty) { + this.importMetas.push(node); + } - const { line, column } = locate(this.code, pos, { offsetLine: 1 }); // TODO trace sourcemaps, cf. error() + private addModulesToSpecifiers(specifiers: { + [name: string]: ImportDescription | ReexportDescription; + }) { + for (const name of Object.keys(specifiers)) { + const specifier = specifiers[name]; + const id = this.resolvedIds[specifier.source].id; + specifier.module = this.graph.moduleById.get(id); + } + } - warning.loc = { file: this.id, line, column }; - warning.frame = getCodeFrame(this.code, line, column); + private includeDynamicImport(node: Import) { + const resolution = this.dynamicImports.find(dynamicImport => dynamicImport.node === node) + .resolution; + if (resolution instanceof Module) { + resolution.dynamicallyImportedBy.push(this); + resolution.includeAllExports(); } + } - warning.id = this.id; - this.graph.warn(warning); + private includeVariable(variable: Variable) { + if (!variable.included) { + variable.include(); + this.graph.needsTreeshakingPass = true; + } + if (variable.module && variable.module !== this) { + this.imports.add(variable); + } + } + + private removeExistingSourceMap() { + for (const comment of this.comments) { + if (!comment.block && SOURCEMAPPING_URL_RE.test(comment.text)) { + this.magicString.remove(comment.start, comment.end); + } + } } private shimMissingExport(name: string): void { if (!this.exports[name]) { this.graph.warn({ - message: `Missing export "${name}" has been shimmed in module ${relativeId(this.id)}.`, code: 'SHIMMED_EXPORT', + exporter: relativeId(this.id), exportName: name, - exporter: relativeId(this.id) + message: `Missing export "${name}" has been shimmed in module ${relativeId(this.id)}.` }); this.exports[name] = MISSING_EXPORT_SHIM_DESCRIPTION; } diff --git a/src/ast/CallOptions.ts b/src/ast/CallOptions.ts index 11c7eabdde8..9f795869e40 100644 --- a/src/ast/CallOptions.ts +++ b/src/ast/CallOptions.ts @@ -12,20 +12,20 @@ export type CallExpressionType = | Property; export interface CallCreateOptions { - withNew: boolean; args?: (ExpressionEntity | SpreadElement)[]; callIdentifier: Object; + withNew: boolean; } export default class CallOptions implements CallCreateOptions { - withNew: boolean; - args: (ExpressionEntity | SpreadElement)[]; - callIdentifier: Object; - static create(callOptions: CallCreateOptions) { return new this(callOptions); } + args: (ExpressionEntity | SpreadElement)[]; + callIdentifier: Object; + withNew: boolean; + constructor( { withNew = false, args = [], callIdentifier = undefined }: CallCreateOptions = {} as any ) { diff --git a/src/ast/Entity.ts b/src/ast/Entity.ts index 299b08c95c2..e67b566d614 100644 --- a/src/ast/Entity.ts +++ b/src/ast/Entity.ts @@ -6,8 +6,6 @@ export interface Entity { } export interface WritableEntity extends Entity { - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean; - /** * Reassign a given path of an object. * E.g., node.deoptimizePath(['x', 'y']) is called when something @@ -15,4 +13,5 @@ export interface WritableEntity extends Entity { * expression of this node is reassigned as well. */ deoptimizePath(path: ObjectPath): void; + hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean; } diff --git a/src/ast/ExecutionPathOptions.ts b/src/ast/ExecutionPathOptions.ts index 5ce274a5d17..d2a216a6c68 100644 --- a/src/ast/ExecutionPathOptions.ts +++ b/src/ast/ExecutionPathOptions.ts @@ -26,34 +26,18 @@ const RESULT_KEY: RESULT_KEY = {}; type KeyTypes = OptionTypes | Entity | RESULT_KEY; export class ExecutionPathOptions { - private optionValues: Immutable.Map; - static create() { return new this(Immutable.Map()); } + private optionValues: Immutable.Map; + private constructor( optionValues: Immutable.Map ) { this.optionValues = optionValues; } - private get(option: OptionTypes) { - return this.optionValues.get(option); - } - - private remove(option: OptionTypes) { - return new ExecutionPathOptions(this.optionValues.remove(option)); - } - - private set(option: OptionTypes, value: boolean | ExpressionEntity[]) { - return new ExecutionPathOptions(this.optionValues.set(option, value)); - } - - private setIn(optionPath: (string | Entity | RESULT_KEY)[], value: boolean | Entity) { - return new ExecutionPathOptions(this.optionValues.setIn(optionPath, value)); - } - addAccessedNodeAtPath(path: ObjectPath, node: ExpressionEntity) { return this.setIn([OptionTypes.ACCESSED_NODES, node, ...path, RESULT_KEY], true); } @@ -206,4 +190,20 @@ export class ExecutionPathOptions { setIgnoreReturnAwaitYield(value = true) { return this.set(OptionTypes.IGNORE_RETURN_AWAIT_YIELD, value); } + + private get(option: OptionTypes) { + return this.optionValues.get(option); + } + + private remove(option: OptionTypes) { + return new ExecutionPathOptions(this.optionValues.remove(option)); + } + + private set(option: OptionTypes, value: boolean | ExpressionEntity[]) { + return new ExecutionPathOptions(this.optionValues.set(option, value)); + } + + private setIn(optionPath: (string | Entity | RESULT_KEY)[], value: boolean | Entity) { + return new ExecutionPathOptions(this.optionValues.setIn(optionPath, value)); + } } diff --git a/src/ast/keys.ts b/src/ast/keys.ts index 3c477a91fda..0705c8c3854 100644 --- a/src/ast/keys.ts +++ b/src/ast/keys.ts @@ -3,8 +3,8 @@ import { GenericEsTreeNode } from './nodes/shared/Node'; export const keys: { [name: string]: string[]; } = { - Program: ['body'], - Literal: [] + Literal: [], + Program: ['body'] }; export function getAndCreateKeys(esTreeNode: GenericEsTreeNode) { diff --git a/src/ast/nodes/ArrayExpression.ts b/src/ast/nodes/ArrayExpression.ts index 99b60b9a042..8976d41386f 100644 --- a/src/ast/nodes/ArrayExpression.ts +++ b/src/ast/nodes/ArrayExpression.ts @@ -13,8 +13,8 @@ import { ExpressionNode, NodeBase } from './shared/Node'; import SpreadElement from './SpreadElement'; export default class ArrayExpression extends NodeBase { - type: NodeType.tArrayExpression; elements: (ExpressionNode | SpreadElement | null)[]; + type: NodeType.tArrayExpression; bind() { super.bind(); diff --git a/src/ast/nodes/ArrayPattern.ts b/src/ast/nodes/ArrayPattern.ts index b63faf7342e..411b88c020c 100644 --- a/src/ast/nodes/ArrayPattern.ts +++ b/src/ast/nodes/ArrayPattern.ts @@ -7,8 +7,8 @@ import { NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class ArrayPattern extends NodeBase implements PatternNode { - type: NodeType.tArrayPattern; elements: (PatternNode | null)[]; + type: NodeType.tArrayPattern; addExportedVariables(variables: Variable[]): void { for (const element of this.elements) { @@ -26,15 +26,6 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { - if (path.length > 0) return true; - for (const element of this.elements) { - if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) - return true; - } - return false; - } - deoptimizePath(path: ObjectPath) { if (path.length === 0) { for (const element of this.elements) { @@ -44,4 +35,13 @@ export default class ArrayPattern extends NodeBase implements PatternNode { } } } + + hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + if (path.length > 0) return true; + for (const element of this.elements) { + if (element !== null && element.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) + return true; + } + return false; + } } diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index e8b870f3ba4..7f9151ee747 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -9,17 +9,24 @@ import { ExpressionNode, GenericEsTreeNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class ArrowFunctionExpression extends NodeBase { - type: NodeType.tArrowFunctionExpression; body: BlockStatement | ExpressionNode; params: PatternNode[]; - - scope: ReturnValueScope; preventChildBlockScope: true; + scope: ReturnValueScope; + type: NodeType.tArrowFunctionExpression; createScope(parentScope: Scope) { this.scope = new ReturnValueScope(parentScope, this.context); } + deoptimizePath(path: ObjectPath) { + // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track + // which means the return expression needs to be reassigned + if (path.length === 1 && path[0] === UNKNOWN_KEY) { + this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); + } + } + getReturnExpressionWhenCalledAtPath(path: ObjectPath) { return path.length === 0 ? this.scope.getReturnExpression() : UNKNOWN_EXPRESSION; } @@ -72,14 +79,6 @@ export default class ArrowFunctionExpression extends NodeBase { } super.parseNode(esTreeNode); } - - deoptimizePath(path: ObjectPath) { - // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track - // which means the return expression needs to be reassigned - if (path.length === 1 && path[0] === UNKNOWN_KEY) { - this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); - } - } } ArrowFunctionExpression.prototype.preventChildBlockScope = true; diff --git a/src/ast/nodes/AssignmentExpression.ts b/src/ast/nodes/AssignmentExpression.ts index 32dba815c7d..6b8f5322121 100644 --- a/src/ast/nodes/AssignmentExpression.ts +++ b/src/ast/nodes/AssignmentExpression.ts @@ -9,7 +9,7 @@ import { ExpressionNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class AssignmentExpression extends NodeBase { - type: NodeType.tAssignmentExpression; + left: PatternNode | ExpressionNode; operator: | '=' | '+=' @@ -24,8 +24,8 @@ export default class AssignmentExpression extends NodeBase { | '^=' | '&=' | '**='; - left: PatternNode | ExpressionNode; right: ExpressionNode; + type: NodeType.tAssignmentExpression; bind() { super.bind(); diff --git a/src/ast/nodes/AssignmentPattern.ts b/src/ast/nodes/AssignmentPattern.ts index 3da429d404a..08923adb476 100644 --- a/src/ast/nodes/AssignmentPattern.ts +++ b/src/ast/nodes/AssignmentPattern.ts @@ -10,9 +10,9 @@ import { ExpressionNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class AssignmentPattern extends NodeBase implements PatternNode { - type: NodeType.tAssignmentPattern; left: PatternNode; right: ExpressionNode; + type: NodeType.tAssignmentPattern; addExportedVariables(variables: Variable[]): void { this.left.addExportedVariables(variables); @@ -28,14 +28,14 @@ export default class AssignmentPattern extends NodeBase implements PatternNode { this.left.declare(kind, init); } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); - } - deoptimizePath(path: ObjectPath) { path.length === 0 && this.left.deoptimizePath(path); } + hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); + } + render( code: MagicString, options: RenderOptions, diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index ebd63ac99f0..46b19ccc0f7 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -4,12 +4,15 @@ import { ExecutionPathOptions } from '../ExecutionPathOptions'; import ArrowFunctionExpression from './ArrowFunctionExpression'; import * as NodeType from './NodeType'; import FunctionNode from './shared/FunctionNode'; -import { Node } from './shared/Node'; -import { ExpressionNode, NodeBase } from './shared/Node'; +import { ExpressionNode, Node, NodeBase } from './shared/Node'; export default class AwaitExpression extends NodeBase { - type: NodeType.tAwaitExpression; argument: ExpressionNode; + type: NodeType.tAwaitExpression; + + hasEffects(options: ExecutionPathOptions) { + return super.hasEffects(options) || !options.ignoreReturnAwaitYield(); + } include(includeAllChildrenRecursively: boolean) { super.include(includeAllChildrenRecursively); @@ -22,10 +25,6 @@ export default class AwaitExpression extends NodeBase { } } - hasEffects(options: ExecutionPathOptions) { - return super.hasEffects(options) || !options.ignoreReturnAwaitYield(); - } - render(code: MagicString, options: RenderOptions) { super.render(code, options); } diff --git a/src/ast/nodes/BinaryExpression.ts b/src/ast/nodes/BinaryExpression.ts index ccef28f47da..ed3a95da6de 100644 --- a/src/ast/nodes/BinaryExpression.ts +++ b/src/ast/nodes/BinaryExpression.ts @@ -9,36 +9,36 @@ import { ExpressionNode, NodeBase } from './shared/Node'; const binaryOperators: { [operator: string]: (left: LiteralValue, right: LiteralValue) => LiteralValueOrUnknown; } = { - '==': (left, right) => left == right, '!=': (left, right) => left != right, - '===': (left, right) => left === right, '!==': (left, right) => left !== right, + '%': (left: any, right: any) => left % right, + '&': (left: any, right: any) => left & right, + '*': (left: any, right: any) => left * right, + // At the moment, "**" will be transpiled to Math.pow + '**': (left: any, right: any) => left ** right, + '+': (left: any, right: any) => left + right, + '-': (left: any, right: any) => left - right, + '/': (left: any, right: any) => left / right, '<': (left, right) => left < right, + '<<': (left: any, right: any) => left << right, '<=': (left, right) => left <= right, + '==': (left, right) => left == right, + '===': (left, right) => left === right, '>': (left, right) => left > right, '>=': (left, right) => left >= right, - '<<': (left: any, right: any) => left << right, '>>': (left: any, right: any) => left >> right, '>>>': (left: any, right: any) => left >>> right, - '+': (left: any, right: any) => left + right, - '-': (left: any, right: any) => left - right, - '*': (left: any, right: any) => left * right, - '/': (left: any, right: any) => left / right, - '%': (left: any, right: any) => left % right, - '|': (left: any, right: any) => left | right, '^': (left: any, right: any) => left ^ right, - '&': (left: any, right: any) => left & right, - // At the moment, "**" will be transpiled to Math.pow - '**': (left: any, right: any) => left ** right, in: () => UNKNOWN_VALUE, - instanceof: () => UNKNOWN_VALUE + instanceof: () => UNKNOWN_VALUE, + '|': (left: any, right: any) => left | right }; export default class BinaryExpression extends NodeBase { - type: NodeType.tBinaryExpression; left: ExpressionNode; - right: ExpressionNode; operator: keyof typeof binaryOperators; + right: ExpressionNode; + type: NodeType.tBinaryExpression; getLiteralValueAtPath( path: ObjectPath, diff --git a/src/ast/nodes/BlockStatement.ts b/src/ast/nodes/BlockStatement.ts index bbe79d6a4ab..8385331c84d 100644 --- a/src/ast/nodes/BlockStatement.ts +++ b/src/ast/nodes/BlockStatement.ts @@ -13,8 +13,8 @@ export function isBlockStatement(node: Node): node is BlockStatement { } export default class BlockStatement extends StatementBase { - type: NodeType.tBlockStatement; body: StatementNode[]; + type: NodeType.tBlockStatement; addImplicitReturnExpressionToScope() { const lastStatement = this.body[this.body.length - 1]; diff --git a/src/ast/nodes/BreakStatement.ts b/src/ast/nodes/BreakStatement.ts index 2dd3b5b0ffe..19967fdc6f2 100644 --- a/src/ast/nodes/BreakStatement.ts +++ b/src/ast/nodes/BreakStatement.ts @@ -4,8 +4,8 @@ import * as NodeType from './NodeType'; import { StatementBase } from './shared/Node'; export default class BreakStatement extends StatementBase { - type: NodeType.tBreakStatement; label: Identifier | null; + type: NodeType.tBreakStatement; hasEffects(options: ExecutionPathOptions) { return ( diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index e68bdc9332d..b69dfb02e48 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -23,17 +23,15 @@ import { ExpressionNode, NodeBase } from './shared/Node'; import SpreadElement from './SpreadElement'; export default class CallExpression extends NodeBase implements DeoptimizableEntity { - type: NodeType.tCallExpression; - callee: ExpressionNode; - arguments: (ExpressionNode | SpreadElement)[]; annotatedPure?: boolean; + arguments: (ExpressionNode | SpreadElement)[]; + callee: ExpressionNode; + type: NodeType.tCallExpression; private callOptions: CallOptions; - - // Caching and deoptimization: // We collect deoptimization information if returnExpression !== UNKNOWN_EXPRESSION - private returnExpression: ExpressionEntity | null; private expressionsToBeDeoptimized: DeoptimizableEntity[]; + private returnExpression: ExpressionEntity | null; bind() { super.bind(); @@ -83,6 +81,19 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt } } + deoptimizePath(path: ObjectPath) { + if (path.length > 0 && !this.context.deoptimizationTracker.track(this, path)) { + if (this.returnExpression === null) { + this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( + EMPTY_PATH, + EMPTY_IMMUTABLE_TRACKER, + this + ); + } + this.returnExpression.deoptimizePath(path); + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -196,26 +207,13 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt this.included = false; this.returnExpression = null; this.callOptions = CallOptions.create({ - withNew: false, args: this.arguments, - callIdentifier: this + callIdentifier: this, + withNew: false }); this.expressionsToBeDeoptimized = []; } - deoptimizePath(path: ObjectPath) { - if (path.length > 0 && !this.context.deoptimizationTracker.track(this, path)) { - if (this.returnExpression === null) { - this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath( - EMPTY_PATH, - EMPTY_IMMUTABLE_TRACKER, - this - ); - } - this.returnExpression.deoptimizePath(path); - } - } - render( code: MagicString, options: RenderOptions, diff --git a/src/ast/nodes/CatchClause.ts b/src/ast/nodes/CatchClause.ts index dd4df0e1aba..fb247c2d32c 100644 --- a/src/ast/nodes/CatchClause.ts +++ b/src/ast/nodes/CatchClause.ts @@ -7,12 +7,11 @@ import { GenericEsTreeNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class CatchClause extends NodeBase { - type: NodeType.tCatchClause; - param: PatternNode | null; body: BlockStatement; - - scope: CatchScope; + param: PatternNode | null; preventChildBlockScope: true; + scope: CatchScope; + type: NodeType.tCatchClause; createScope(parentScope: Scope) { this.scope = new CatchScope(parentScope, this.context); diff --git a/src/ast/nodes/ClassBody.ts b/src/ast/nodes/ClassBody.ts index 47012c00f90..d0b9e6aa590 100644 --- a/src/ast/nodes/ClassBody.ts +++ b/src/ast/nodes/ClassBody.ts @@ -6,8 +6,8 @@ import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; export default class ClassBody extends NodeBase { - type: NodeType.tClassBody; body: MethodDefinition[]; + type: NodeType.tClassBody; private classConstructor: MethodDefinition | null; diff --git a/src/ast/nodes/ClassDeclaration.ts b/src/ast/nodes/ClassDeclaration.ts index f7e485d8e63..7b54f22fcd1 100644 --- a/src/ast/nodes/ClassDeclaration.ts +++ b/src/ast/nodes/ClassDeclaration.ts @@ -11,8 +11,8 @@ export function isClassDeclaration(node: Node): node is ClassDeclaration { } export default class ClassDeclaration extends ClassNode { - type: NodeType.tClassDeclaration; id: Identifier; + type: NodeType.tClassDeclaration; initialise() { super.initialise(); diff --git a/src/ast/nodes/ConditionalExpression.ts b/src/ast/nodes/ConditionalExpression.ts index 617ba72c4de..89b45624ccd 100644 --- a/src/ast/nodes/ConditionalExpression.ts +++ b/src/ast/nodes/ConditionalExpression.ts @@ -23,17 +23,16 @@ import { MultiExpression } from './shared/MultiExpression'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class ConditionalExpression extends NodeBase implements DeoptimizableEntity { - type: NodeType.tConditionalExpression; - test: ExpressionNode; alternate: ExpressionNode; consequent: ExpressionNode; + test: ExpressionNode; + type: NodeType.tConditionalExpression; - // Caching and deoptimization: // We collect deoptimization information if usedBranch !== null + private expressionsToBeDeoptimized: DeoptimizableEntity[]; private isBranchResolutionAnalysed: boolean; - private usedBranch: ExpressionNode | null; private unusedBranch: ExpressionNode | null; - private expressionsToBeDeoptimized: DeoptimizableEntity[]; + private usedBranch: ExpressionNode | null; bind() { super.bind(); @@ -52,6 +51,18 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz } } + deoptimizePath(path: ObjectPath) { + if (path.length > 0) { + if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); + if (this.usedBranch === null) { + this.consequent.deoptimizePath(path); + this.alternate.deoptimizePath(path); + } else { + this.usedBranch.deoptimizePath(path); + } + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -122,14 +133,6 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz return this.usedBranch.hasEffectsWhenCalledAtPath(path, callOptions, options); } - initialise() { - this.included = false; - this.isBranchResolutionAnalysed = false; - this.usedBranch = null; - this.unusedBranch = null; - this.expressionsToBeDeoptimized = []; - } - include(includeAllChildrenRecursively: boolean) { this.included = true; if (includeAllChildrenRecursively || this.usedBranch === null || this.test.shouldBeIncluded()) { @@ -141,16 +144,12 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz } } - deoptimizePath(path: ObjectPath) { - if (path.length > 0) { - if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); - if (this.usedBranch === null) { - this.consequent.deoptimizePath(path); - this.alternate.deoptimizePath(path); - } else { - this.usedBranch.deoptimizePath(path); - } - } + initialise() { + this.included = false; + this.isBranchResolutionAnalysed = false; + this.usedBranch = null; + this.unusedBranch = null; + this.expressionsToBeDeoptimized = []; } render( @@ -163,10 +162,10 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz code.remove(this.usedBranch.end, this.end); removeAnnotations(this, code); this.usedBranch.render(code, options, { - renderedParentType: renderedParentType || this.parent.type, isCalleeOfRenderedParent: renderedParentType ? isCalleeOfRenderedParent - : (this.parent).callee === this + : (this.parent).callee === this, + renderedParentType: renderedParentType || this.parent.type }); } else { super.render(code, options); diff --git a/src/ast/nodes/DoWhileStatement.ts b/src/ast/nodes/DoWhileStatement.ts index a8de72bbbe9..807fe3eb08a 100644 --- a/src/ast/nodes/DoWhileStatement.ts +++ b/src/ast/nodes/DoWhileStatement.ts @@ -3,9 +3,9 @@ import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; export default class DoWhileStatement extends StatementBase { - type: NodeType.tDoWhileStatement; body: StatementNode; test: ExpressionNode; + type: NodeType.tDoWhileStatement; hasEffects(options: ExecutionPathOptions): boolean { return ( diff --git a/src/ast/nodes/ExportAllDeclaration.ts b/src/ast/nodes/ExportAllDeclaration.ts index 36ff9562e3e..d44786d6d55 100644 --- a/src/ast/nodes/ExportAllDeclaration.ts +++ b/src/ast/nodes/ExportAllDeclaration.ts @@ -6,10 +6,9 @@ import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; export default class ExportAllDeclaration extends NodeBase { - type: NodeType.tExportAllDeclaration; - source: Literal; - needsBoundaries: true; + source: Literal; + type: NodeType.tExportAllDeclaration; hasEffects() { return false; diff --git a/src/ast/nodes/ExportDefaultDeclaration.ts b/src/ast/nodes/ExportDefaultDeclaration.ts index b87580ec741..34bab720c0b 100644 --- a/src/ast/nodes/ExportDefaultDeclaration.ts +++ b/src/ast/nodes/ExportDefaultDeclaration.ts @@ -39,11 +39,10 @@ export function isExportDefaultDeclaration(node: Node): node is ExportDefaultDec } export default class ExportDefaultDeclaration extends NodeBase { - type: NodeType.tExportDefaultDeclaration; declaration: FunctionDeclaration | ClassDeclaration | ExpressionNode; - scope: ModuleScope; - needsBoundaries: true; + scope: ModuleScope; + type: NodeType.tExportDefaultDeclaration; variable: ExportDefaultVariable; private declarationName: string; @@ -97,8 +96,8 @@ export default class ExportDefaultDeclaration extends NodeBase { } else { code.remove(this.start, declarationStart); this.declaration.render(code, options, { - renderedParentType: NodeType.ExpressionStatement, - isCalleeOfRenderedParent: false + isCalleeOfRenderedParent: false, + renderedParentType: NodeType.ExpressionStatement }); if (code.original[this.end - 1] !== ';') { code.appendLeft(this.end, ';'); diff --git a/src/ast/nodes/ExportNamedDeclaration.ts b/src/ast/nodes/ExportNamedDeclaration.ts index 65a783c17c0..16824f2a81c 100644 --- a/src/ast/nodes/ExportNamedDeclaration.ts +++ b/src/ast/nodes/ExportNamedDeclaration.ts @@ -11,12 +11,11 @@ import { Node, NodeBase } from './shared/Node'; import VariableDeclaration from './VariableDeclaration'; export default class ExportNamedDeclaration extends NodeBase { - type: NodeType.tExportNamedDeclaration; declaration: FunctionDeclaration | ClassDeclaration | VariableDeclaration | null; - specifiers: ExportSpecifier[]; - source: Literal | null; - needsBoundaries: true; + source: Literal | null; + specifiers: ExportSpecifier[]; + type: NodeType.tExportNamedDeclaration; bind() { // Do not bind specifiers diff --git a/src/ast/nodes/ExportSpecifier.ts b/src/ast/nodes/ExportSpecifier.ts index 60057db2839..521091634e6 100644 --- a/src/ast/nodes/ExportSpecifier.ts +++ b/src/ast/nodes/ExportSpecifier.ts @@ -3,7 +3,7 @@ import * as NodeType from './NodeType'; import { Node } from './shared/Node'; export default interface ExportSpecifier extends Node { - type: NodeType.tExportSpecifier; - local: Identifier; exported: Identifier; + local: Identifier; + type: NodeType.tExportSpecifier; } diff --git a/src/ast/nodes/ExpressionStatement.ts b/src/ast/nodes/ExpressionStatement.ts index 0980c6fe9f0..a9def0969cb 100644 --- a/src/ast/nodes/ExpressionStatement.ts +++ b/src/ast/nodes/ExpressionStatement.ts @@ -27,15 +27,15 @@ export default class ExpressionStatement extends StatementBase { } } + render(code: MagicString, options: RenderOptions) { + super.render(code, options); + if (this.included) this.insertSemicolon(code); + } + shouldBeIncluded() { if (this.directive && this.directive !== 'use strict') return this.parent.type !== NodeType.Program; return super.shouldBeIncluded(); } - - render(code: MagicString, options: RenderOptions) { - super.render(code, options); - if (this.included) this.insertSemicolon(code); - } } diff --git a/src/ast/nodes/ForInStatement.ts b/src/ast/nodes/ForInStatement.ts index 824f83d265e..343d19da734 100644 --- a/src/ast/nodes/ForInStatement.ts +++ b/src/ast/nodes/ForInStatement.ts @@ -14,10 +14,10 @@ export function isForInStatement(node: Node): node is ForInStatement { } export default class ForInStatement extends StatementBase { - type: NodeType.tForInStatement; + body: StatementNode; left: VariableDeclaration | PatternNode; right: ExpressionNode; - body: StatementNode; + type: NodeType.tForInStatement; bind() { this.left.bind(); diff --git a/src/ast/nodes/ForOfStatement.ts b/src/ast/nodes/ForOfStatement.ts index d03861dcfba..31dc2b78b21 100644 --- a/src/ast/nodes/ForOfStatement.ts +++ b/src/ast/nodes/ForOfStatement.ts @@ -14,11 +14,11 @@ export function isForOfStatement(node: Node): node is ForOfStatement { } export default class ForOfStatement extends StatementBase { - type: NodeType.tForOfStatement; + await: boolean; + body: StatementNode; left: VariableDeclaration | PatternNode; right: ExpressionNode; - body: StatementNode; - await: boolean; + type: NodeType.tForOfStatement; bind() { this.left.bind(); diff --git a/src/ast/nodes/ForStatement.ts b/src/ast/nodes/ForStatement.ts index 08786c6eb90..e5bf059c776 100644 --- a/src/ast/nodes/ForStatement.ts +++ b/src/ast/nodes/ForStatement.ts @@ -12,11 +12,11 @@ export function isForStatement(node: Node): node is ForStatement { } export default class ForStatement extends StatementBase { - type: NodeType.tForStatement; + body: StatementNode; init: VariableDeclaration | ExpressionNode | null; test: ExpressionNode | null; + type: NodeType.tForStatement; update: ExpressionNode | null; - body: StatementNode; createScope(parentScope: Scope) { this.scope = new BlockScope(parentScope); diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index c0a60afd1db..b5cfbe77315 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -20,8 +20,8 @@ export function isIdentifier(node: Node): node is Identifier { } export default class Identifier extends NodeBase implements PatternNode { - type: NodeType.tIdentifier; name: string; + type: NodeType.tIdentifier; variable: Variable; private bound: boolean; @@ -67,6 +67,20 @@ export default class Identifier extends NodeBase implements PatternNode { } } + deoptimizePath(path: ObjectPath) { + if (!this.bound) this.bind(); + if (this.variable !== null) { + if ( + path.length === 0 && + this.name in this.context.importDescriptions && + !this.scope.contains(this.name) + ) { + this.disallowImportReassignment(); + } + this.variable.deoptimizePath(path); + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -123,20 +137,6 @@ export default class Identifier extends NodeBase implements PatternNode { } } - deoptimizePath(path: ObjectPath) { - if (!this.bound) this.bind(); - if (this.variable !== null) { - if ( - path.length === 0 && - this.name in this.context.importDescriptions && - !this.scope.contains(this.name) - ) { - this.disallowImportReassignment(); - } - this.variable.deoptimizePath(path); - } - } - render( code: MagicString, _options: RenderOptions, @@ -147,8 +147,8 @@ export default class Identifier extends NodeBase implements PatternNode { if (name !== this.name) { code.overwrite(this.start, this.end, name, { - storeName: true, - contentOnly: true + contentOnly: true, + storeName: true }); if (isShorthandProperty) { code.prependRight(this.start, `${this.name}: `); diff --git a/src/ast/nodes/IfStatement.ts b/src/ast/nodes/IfStatement.ts index 020326d9f5b..bd39b7208b7 100644 --- a/src/ast/nodes/IfStatement.ts +++ b/src/ast/nodes/IfStatement.ts @@ -9,13 +9,13 @@ import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; export default class IfStatement extends StatementBase implements DeoptimizableEntity { - type: NodeType.tIfStatement; - test: ExpressionNode; - consequent: StatementNode; alternate: StatementNode | null; + consequent: StatementNode; + test: ExpressionNode; + type: NodeType.tIfStatement; - private testValue: LiteralValueOrUnknown; private isTestValueAnalysed: boolean; + private testValue: LiteralValueOrUnknown; bind() { super.bind(); diff --git a/src/ast/nodes/Import.ts b/src/ast/nodes/Import.ts index 37164af3578..46ebba8c0f8 100644 --- a/src/ast/nodes/Import.ts +++ b/src/ast/nodes/Import.ts @@ -5,10 +5,10 @@ import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; interface DynamicImportMechanism { - left: string; - right: string; interopLeft?: string; interopRight?: string; + left: string; + right: string; } const getDynamicImportMechanism = (format: string, compact: boolean): DynamicImportMechanism => { @@ -16,10 +16,10 @@ const getDynamicImportMechanism = (format: string, compact: boolean): DynamicImp case 'cjs': { const _ = compact ? '' : ' '; return { - left: 'Promise.resolve(require(', - right: '))', interopLeft: `Promise.resolve({${_}default:${_}require(`, - interopRight: `)${_}})` + interopRight: `)${_}})`, + left: 'Promise.resolve(require(', + right: '))' }; } case 'amd': { @@ -27,10 +27,10 @@ const getDynamicImportMechanism = (format: string, compact: boolean): DynamicImp const resolve = compact ? 'c' : 'resolve'; const reject = compact ? 'e' : 'reject'; return { - left: `new Promise(function${_}(${resolve},${_}${reject})${_}{${_}require([`, - right: `],${_}${resolve},${_}${reject})${_}})`, interopLeft: `new Promise(function${_}(${resolve},${_}${reject})${_}{${_}require([`, - interopRight: `],${_}function${_}(m)${_}{${_}${resolve}({${_}default:${_}m${_}})${_}},${_}${reject})${_}})` + interopRight: `],${_}function${_}(m)${_}{${_}${resolve}({${_}default:${_}m${_}})${_}},${_}${reject})${_}})`, + left: `new Promise(function${_}(${resolve},${_}${reject})${_}{${_}require([`, + right: `],${_}${resolve},${_}${reject})${_}})` }; } case 'system': @@ -42,11 +42,11 @@ const getDynamicImportMechanism = (format: string, compact: boolean): DynamicImp }; export default class Import extends NodeBase { - type: NodeType.tImport; parent: CallExpression; + type: NodeType.tImport; - private resolutionNamespace: string; private resolutionInterop: boolean; + private resolutionNamespace: string; include() { this.included = true; @@ -60,12 +60,6 @@ export default class Import extends NodeBase { this.context.addDynamicImport(this); } - renderFinalResolution(code: MagicString, resolution: string) { - if (this.included) { - code.overwrite(this.parent.arguments[0].start, this.parent.arguments[0].end, resolution); - } - } - render(code: MagicString, options: RenderOptions) { if (this.resolutionNamespace) { const _ = options.compact ? '' : ' '; @@ -90,6 +84,12 @@ export default class Import extends NodeBase { } } + renderFinalResolution(code: MagicString, resolution: string) { + if (this.included) { + code.overwrite(this.parent.arguments[0].start, this.parent.arguments[0].end, resolution); + } + } + setResolution(interop: boolean, namespace: string = undefined): void { this.resolutionInterop = interop; this.resolutionNamespace = namespace; diff --git a/src/ast/nodes/ImportDeclaration.ts b/src/ast/nodes/ImportDeclaration.ts index da99ed6fffb..5604d7e79f3 100644 --- a/src/ast/nodes/ImportDeclaration.ts +++ b/src/ast/nodes/ImportDeclaration.ts @@ -9,23 +9,22 @@ import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; export default class ImportDeclaration extends NodeBase { - type: NodeType.tImportDeclaration; - specifiers: (ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier)[]; - source: Literal; - needsBoundaries: true; + source: Literal; + specifiers: (ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier)[]; + type: NodeType.tImportDeclaration; bind() {} + hasEffects() { + return false; + } + initialise() { this.included = false; this.context.addImport(this); } - hasEffects() { - return false; - } - render(code: MagicString, _options: RenderOptions, { start, end }: NodeRenderOptions = BLANK) { code.remove(start, end); } diff --git a/src/ast/nodes/ImportDefaultSpecifier.ts b/src/ast/nodes/ImportDefaultSpecifier.ts index ec908b285da..a03221ec86c 100644 --- a/src/ast/nodes/ImportDefaultSpecifier.ts +++ b/src/ast/nodes/ImportDefaultSpecifier.ts @@ -3,6 +3,6 @@ import * as NodeType from './NodeType'; import { Node } from './shared/Node'; export default interface ImportDefaultSpecifier extends Node { - type: NodeType.tImportDefaultSpecifier; local: Identifier; + type: NodeType.tImportDefaultSpecifier; } diff --git a/src/ast/nodes/ImportNamespaceSpecifier.ts b/src/ast/nodes/ImportNamespaceSpecifier.ts index 5506f8ef9b0..5d1c48f107c 100644 --- a/src/ast/nodes/ImportNamespaceSpecifier.ts +++ b/src/ast/nodes/ImportNamespaceSpecifier.ts @@ -3,6 +3,6 @@ import * as NodeType from './NodeType'; import { Node } from './shared/Node'; export default interface ImportNamespaceSpecifier extends Node { - type: NodeType.tImportNamespaceSpecifier; local: Identifier; + type: NodeType.tImportNamespaceSpecifier; } diff --git a/src/ast/nodes/ImportSpecifier.ts b/src/ast/nodes/ImportSpecifier.ts index 69702508bc4..39b964d8fd2 100644 --- a/src/ast/nodes/ImportSpecifier.ts +++ b/src/ast/nodes/ImportSpecifier.ts @@ -3,7 +3,7 @@ import * as NodeType from './NodeType'; import { Node } from './shared/Node'; export default interface ImportSpecifier extends Node { - type: NodeType.tImportSpecifier; - local: Identifier; imported: Identifier; + local: Identifier; + type: NodeType.tImportSpecifier; } diff --git a/src/ast/nodes/LabeledStatement.ts b/src/ast/nodes/LabeledStatement.ts index 2f4a74f27c1..9a670f49951 100644 --- a/src/ast/nodes/LabeledStatement.ts +++ b/src/ast/nodes/LabeledStatement.ts @@ -4,9 +4,9 @@ import * as NodeType from './NodeType'; import { StatementBase, StatementNode } from './shared/Node'; export default class LabeledStatement extends StatementBase { - type: NodeType.tLabeledStatement; - label: Identifier; body: StatementNode; + label: Identifier; + type: NodeType.tLabeledStatement; hasEffects(options: ExecutionPathOptions) { return this.body.hasEffects(options.setIgnoreLabel(this.label.name).setIgnoreBreakStatements()); diff --git a/src/ast/nodes/LogicalExpression.ts b/src/ast/nodes/LogicalExpression.ts index b5549f08378..db8fab9f029 100644 --- a/src/ast/nodes/LogicalExpression.ts +++ b/src/ast/nodes/LogicalExpression.ts @@ -25,17 +25,16 @@ import { ExpressionNode, NodeBase } from './shared/Node'; export type LogicalOperator = '||' | '&&'; export default class LogicalExpression extends NodeBase implements DeoptimizableEntity { - type: NodeType.tLogicalExpression; - operator: LogicalOperator; left: ExpressionNode; + operator: LogicalOperator; right: ExpressionNode; + type: NodeType.tLogicalExpression; - // Caching and deoptimization: // We collect deoptimization information if usedBranch !== null + private expressionsToBeDeoptimized: DeoptimizableEntity[]; private isBranchResolutionAnalysed: boolean; - private usedBranch: ExpressionNode | null; private unusedBranch: ExpressionNode | null; - private expressionsToBeDeoptimized: DeoptimizableEntity[]; + private usedBranch: ExpressionNode | null; bind() { super.bind(); @@ -54,6 +53,18 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable } } + deoptimizePath(path: ObjectPath) { + if (path.length > 0) { + if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); + if (this.usedBranch === null) { + this.left.deoptimizePath(path); + this.right.deoptimizePath(path); + } else { + this.usedBranch.deoptimizePath(path); + } + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -145,18 +156,6 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable this.expressionsToBeDeoptimized = []; } - deoptimizePath(path: ObjectPath) { - if (path.length > 0) { - if (!this.isBranchResolutionAnalysed) this.analyseBranchResolution(); - if (this.usedBranch === null) { - this.left.deoptimizePath(path); - this.right.deoptimizePath(path); - } else { - this.usedBranch.deoptimizePath(path); - } - } - } - render( code: MagicString, options: RenderOptions, @@ -167,10 +166,10 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable code.remove(this.usedBranch.end, this.end); removeAnnotations(this, code); this.usedBranch.render(code, options, { - renderedParentType: renderedParentType || this.parent.type, isCalleeOfRenderedParent: renderedParentType ? isCalleeOfRenderedParent - : (this.parent).callee === this + : (this.parent).callee === this, + renderedParentType: renderedParentType || this.parent.type }); } else { super.render(code, options); diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index 57053a88093..fbca8714740 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -73,16 +73,16 @@ export function isMemberExpression(node: Node): node is MemberExpression { } export default class MemberExpression extends NodeBase implements DeoptimizableEntity { - type: NodeType.tMemberExpression; + computed: boolean; object: ExpressionNode; property: ExpressionNode; - computed: boolean; - propertyKey: ObjectPathKey | null; + type: NodeType.tMemberExpression; variable: Variable = null; + private bound: boolean; - private replacement: string | null; private expressionsToBeDeoptimized: DeoptimizableEntity[]; + private replacement: string | null; bind() { if (this.bound) return; @@ -114,6 +114,17 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } } + deoptimizePath(path: ObjectPath) { + if (!this.bound) this.bind(); + if (path.length === 0) this.disallowNamespaceReassignment(); + if (this.variable) { + this.variable.deoptimizePath(path); + } else { + if (this.propertyKey === null) this.analysePropertyKey(); + this.object.deoptimizePath([this.propertyKey, ...path]); + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -205,17 +216,6 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.expressionsToBeDeoptimized = []; } - deoptimizePath(path: ObjectPath) { - if (!this.bound) this.bind(); - if (path.length === 0) this.disallowNamespaceReassignment(); - if (this.variable) { - this.variable.deoptimizePath(path); - } else { - if (this.propertyKey === null) this.analysePropertyKey(); - this.object.deoptimizePath([this.propertyKey, ...path]); - } - } - render( code: MagicString, options: RenderOptions, @@ -227,8 +227,8 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE let replacement = this.variable ? this.variable.getName() : this.replacement; if (isCalleeOfDifferentParent) replacement = '0, ' + replacement; code.overwrite(this.start, this.end, replacement, { - storeName: true, - contentOnly: true + contentOnly: true, + storeName: true }); } else { if (isCalleeOfDifferentParent) { @@ -238,6 +238,12 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } } + private analysePropertyKey() { + this.propertyKey = UNKNOWN_KEY; + const value = this.property.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); + this.propertyKey = value === UNKNOWN_VALUE ? UNKNOWN_KEY : String(value); + } + private disallowNamespaceReassignment() { if ( this.object instanceof Identifier && @@ -270,10 +276,10 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE this.context.warn( { code: 'MISSING_EXPORT', - missing: exportName, - importer: relativeId(this.context.fileName), exporter: relativeId(fileName), + importer: relativeId(this.context.fileName), message: `'${exportName}' is not exported by '${relativeId(fileName)}'`, + missing: exportName, url: `https://rollupjs.org/guide/en#error-name-is-not-exported-by-module-` }, path[0].pos @@ -282,10 +288,4 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE } return this.resolveNamespaceVariables(variable, path.slice(1)); } - - private analysePropertyKey() { - this.propertyKey = UNKNOWN_KEY; - const value = this.property.getLiteralValueAtPath(EMPTY_PATH, EMPTY_IMMUTABLE_TRACKER, this); - this.propertyKey = value === UNKNOWN_VALUE ? UNKNOWN_KEY : String(value); - } } diff --git a/src/ast/nodes/MetaProperty.ts b/src/ast/nodes/MetaProperty.ts index f6ff3244f90..a38a31f37da 100644 --- a/src/ast/nodes/MetaProperty.ts +++ b/src/ast/nodes/MetaProperty.ts @@ -41,19 +41,19 @@ const relUrlMechanisms: Record st const _ = compact ? '' : ' '; return `new URL('../${relPath}',${_}import.meta.url).href`; }, + iife: globalRelUrlMechanism, system: (relPath: string, compact: boolean) => { const _ = compact ? '' : ' '; return `new URL('../${relPath}',${_}module.url).href`; }, - iife: globalRelUrlMechanism, umd: globalRelUrlMechanism }; export default class MetaProperty extends NodeBase { - type: NodeType.tMetaProperty; meta: Identifier; property: Identifier; rendered: boolean; + type: NodeType.tMetaProperty; initialise() { if (this.meta.name === 'import') { diff --git a/src/ast/nodes/MethodDefinition.ts b/src/ast/nodes/MethodDefinition.ts index f782544e390..156466e80e7 100644 --- a/src/ast/nodes/MethodDefinition.ts +++ b/src/ast/nodes/MethodDefinition.ts @@ -6,12 +6,12 @@ import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class MethodDefinition extends NodeBase { - type: NodeType.tMethodDefinition; + computed: boolean; key: ExpressionNode; - value: FunctionExpression; kind: 'constructor' | 'method' | 'get' | 'set'; - computed: boolean; static: boolean; + type: NodeType.tMethodDefinition; + value: FunctionExpression; hasEffects(options: ExecutionPathOptions) { return this.key.hasEffects(options); diff --git a/src/ast/nodes/NewExpression.ts b/src/ast/nodes/NewExpression.ts index bb96071831c..12563d61807 100644 --- a/src/ast/nodes/NewExpression.ts +++ b/src/ast/nodes/NewExpression.ts @@ -5,10 +5,10 @@ import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class NewExpression extends NodeBase { - type: NodeType.tNewExpression; - callee: ExpressionNode; - arguments: ExpressionNode[]; annotatedPure?: boolean; + arguments: ExpressionNode[]; + callee: ExpressionNode; + type: NodeType.tNewExpression; private callOptions: CallOptions; @@ -39,9 +39,9 @@ export default class NewExpression extends NodeBase { initialise() { this.included = false; this.callOptions = CallOptions.create({ - withNew: true, args: this.arguments, - callIdentifier: this + callIdentifier: this, + withNew: true }); } } diff --git a/src/ast/nodes/ObjectExpression.ts b/src/ast/nodes/ObjectExpression.ts index 85732c33613..e926d1a2fd6 100644 --- a/src/ast/nodes/ObjectExpression.ts +++ b/src/ast/nodes/ObjectExpression.ts @@ -30,25 +30,24 @@ import SpreadElement from './SpreadElement'; interface PropertyMap { [key: string]: { - exactMatchWrite: Property | null; - propertiesSet: Property[]; exactMatchRead: Property | null; + exactMatchWrite: Property | null; propertiesRead: (Property | SpreadElement)[]; + propertiesSet: Property[]; }; } export default class ObjectExpression extends NodeBase { - type: NodeType.tObjectExpression; properties: (Property | SpreadElement)[]; + type: NodeType.tObjectExpression; - // Caching and deoptimization: + private deoptimizedPaths: NameCollection; // We collect deoptimization information if we can resolve a computed property access + private expressionsToBeDeoptimized: { [key: string]: DeoptimizableEntity[] }; + private hasUnknownDeoptimizedProperty: boolean; private propertyMap: PropertyMap | null; private unmatchablePropertiesRead: (Property | SpreadElement)[] | null; private unmatchablePropertiesWrite: Property[] | null; - private deoptimizedPaths: NameCollection; - private hasUnknownDeoptimizedProperty: boolean; - private expressionsToBeDeoptimized: { [key: string]: DeoptimizableEntity[] }; bind() { super.bind(); @@ -60,6 +59,41 @@ export default class ObjectExpression extends NodeBase { if (!this.hasUnknownDeoptimizedProperty) this.deoptimizeAllProperties(); } + deoptimizePath(path: ObjectPath) { + if (this.hasUnknownDeoptimizedProperty) return; + if (this.propertyMap === null) this.buildPropertyMap(); + if (path.length === 0) { + this.deoptimizeAllProperties(); + return; + } + const key = path[0]; + if (path.length === 1) { + if (typeof key !== 'string') { + this.deoptimizeAllProperties(); + return; + } + if (!this.deoptimizedPaths[key]) { + this.deoptimizedPaths[key] = true; + + // we only deoptimizeCache exact matches as in all other cases, + // we do not return a literal value or return expression + if (this.expressionsToBeDeoptimized[key]) { + for (const expression of this.expressionsToBeDeoptimized[key]) { + expression.deoptimizeCache(); + } + } + } + } + const subPath = path.length === 1 ? UNKNOWN_PATH : path.slice(1); + for (const property of typeof key === 'string' + ? this.propertyMap[key] + ? this.propertyMap[key].propertiesRead + : [] + : this.properties) { + property.deoptimizePath(subPath); + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -235,53 +269,6 @@ export default class ObjectExpression extends NodeBase { this.expressionsToBeDeoptimized = Object.create(null); } - deoptimizePath(path: ObjectPath) { - if (this.hasUnknownDeoptimizedProperty) return; - if (this.propertyMap === null) this.buildPropertyMap(); - if (path.length === 0) { - this.deoptimizeAllProperties(); - return; - } - const key = path[0]; - if (path.length === 1) { - if (typeof key !== 'string') { - this.deoptimizeAllProperties(); - return; - } - if (!this.deoptimizedPaths[key]) { - this.deoptimizedPaths[key] = true; - - // we only deoptimizeCache exact matches as in all other cases, - // we do not return a literal value or return expression - if (this.expressionsToBeDeoptimized[key]) { - for (const expression of this.expressionsToBeDeoptimized[key]) { - expression.deoptimizeCache(); - } - } - } - } - const subPath = path.length === 1 ? UNKNOWN_PATH : path.slice(1); - for (const property of typeof key === 'string' - ? this.propertyMap[key] - ? this.propertyMap[key].propertiesRead - : [] - : this.properties) { - property.deoptimizePath(subPath); - } - } - - private deoptimizeAllProperties() { - this.hasUnknownDeoptimizedProperty = true; - for (const property of this.properties) { - property.deoptimizePath(UNKNOWN_PATH); - } - for (const key of Object.keys(this.expressionsToBeDeoptimized)) { - for (const expression of this.expressionsToBeDeoptimized[key]) { - expression.deoptimizeCache(); - } - } - } - render( code: MagicString, options: RenderOptions, @@ -331,8 +318,8 @@ export default class ObjectExpression extends NodeBase { if (!propertyMapProperty) { this.propertyMap[key] = { exactMatchRead: isRead ? property : null, - propertiesRead: isRead ? [property, ...this.unmatchablePropertiesRead] : [], exactMatchWrite: isWrite ? property : null, + propertiesRead: isRead ? [property, ...this.unmatchablePropertiesRead] : [], propertiesSet: isWrite && !isRead ? [property, ...this.unmatchablePropertiesWrite] : [] }; continue; @@ -347,4 +334,16 @@ export default class ObjectExpression extends NodeBase { } } } + + private deoptimizeAllProperties() { + this.hasUnknownDeoptimizedProperty = true; + for (const property of this.properties) { + property.deoptimizePath(UNKNOWN_PATH); + } + for (const key of Object.keys(this.expressionsToBeDeoptimized)) { + for (const expression of this.expressionsToBeDeoptimized[key]) { + expression.deoptimizeCache(); + } + } + } } diff --git a/src/ast/nodes/ObjectPattern.ts b/src/ast/nodes/ObjectPattern.ts index a2ceed3e817..550fdc08582 100644 --- a/src/ast/nodes/ObjectPattern.ts +++ b/src/ast/nodes/ObjectPattern.ts @@ -9,8 +9,8 @@ import { NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class ObjectPattern extends NodeBase implements PatternNode { - type: NodeType.tObjectPattern; properties: (Property | RestElement)[]; + type: NodeType.tObjectPattern; addExportedVariables(variables: Variable[]): void { for (const property of this.properties) { @@ -28,14 +28,6 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { - if (path.length > 0) return true; - for (const property of this.properties) { - if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) return true; - } - return false; - } - deoptimizePath(path: ObjectPath) { if (path.length === 0) { for (const property of this.properties) { @@ -43,4 +35,12 @@ export default class ObjectPattern extends NodeBase implements PatternNode { } } } + + hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions) { + if (path.length > 0) return true; + for (const property of this.properties) { + if (property.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options)) return true; + } + return false; + } } diff --git a/src/ast/nodes/Program.ts b/src/ast/nodes/Program.ts index 102d4c02f1e..9f8bb45815e 100644 --- a/src/ast/nodes/Program.ts +++ b/src/ast/nodes/Program.ts @@ -5,9 +5,9 @@ import * as NodeType from './NodeType'; import { NodeBase, StatementNode } from './shared/Node'; export default class Program extends NodeBase { - type: NodeType.tProgram; body: StatementNode[]; sourceType: 'module'; + type: NodeType.tProgram; hasEffects(options: ExecutionPathOptions) { for (const node of this.body) { diff --git a/src/ast/nodes/Property.ts b/src/ast/nodes/Property.ts index 9b209da7d32..78848e50625 100644 --- a/src/ast/nodes/Property.ts +++ b/src/ast/nodes/Property.ts @@ -20,17 +20,17 @@ import { ExpressionEntity } from './shared/Expression'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class Property extends NodeBase implements DeoptimizableEntity { - type: NodeType.tProperty; + computed: boolean; key: ExpressionNode; - value: ExpressionNode; kind: 'init' | 'get' | 'set'; method: boolean; shorthand: boolean; - computed: boolean; + type: NodeType.tProperty; + value: ExpressionNode; private accessorCallOptions: CallOptions; - private returnExpression: ExpressionEntity | null; private declarationInit: ExpressionEntity | null = null; + private returnExpression: ExpressionEntity | null; bind() { super.bind(); @@ -51,6 +51,17 @@ export default class Property extends NodeBase implements DeoptimizableEntity { throw new Error('Unexpected deoptimization'); } + deoptimizePath(path: ObjectPath) { + if (this.kind === 'get') { + if (path.length > 0) { + if (this.returnExpression === null) this.updateReturnExpression(); + this.returnExpression.deoptimizePath(path); + } + } else if (this.kind !== 'set') { + this.value.deoptimizePath(path); + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -135,22 +146,11 @@ export default class Property extends NodeBase implements DeoptimizableEntity { this.included = false; this.returnExpression = null; this.accessorCallOptions = CallOptions.create({ - withNew: false, - callIdentifier: this + callIdentifier: this, + withNew: false }); } - deoptimizePath(path: ObjectPath) { - if (this.kind === 'get') { - if (path.length > 0) { - if (this.returnExpression === null) this.updateReturnExpression(); - this.returnExpression.deoptimizePath(path); - } - } else if (this.kind !== 'set') { - this.value.deoptimizePath(path); - } - } - render(code: MagicString, options: RenderOptions) { if (!this.shorthand) { this.key.render(code, options); diff --git a/src/ast/nodes/RestElement.ts b/src/ast/nodes/RestElement.ts index 5de35c80134..11696ac2f4c 100644 --- a/src/ast/nodes/RestElement.ts +++ b/src/ast/nodes/RestElement.ts @@ -7,8 +7,8 @@ import { NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class RestElement extends NodeBase implements PatternNode { - type: NodeType.tRestElement; argument: PatternNode; + type: NodeType.tRestElement; private declarationInit: ExpressionEntity | null = null; @@ -28,11 +28,11 @@ export default class RestElement extends NodeBase implements PatternNode { this.declarationInit = init; } - hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { - return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); - } - deoptimizePath(path: ObjectPath) { path.length === 0 && this.argument.deoptimizePath(EMPTY_PATH); } + + hasEffectsWhenAssignedAtPath(path: ObjectPath, options: ExecutionPathOptions): boolean { + return path.length > 0 || this.argument.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options); + } } diff --git a/src/ast/nodes/ReturnStatement.ts b/src/ast/nodes/ReturnStatement.ts index b459de17ea6..23780a16c93 100644 --- a/src/ast/nodes/ReturnStatement.ts +++ b/src/ast/nodes/ReturnStatement.ts @@ -6,8 +6,8 @@ import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; export default class ReturnStatement extends StatementBase { - type: NodeType.tReturnStatement; argument: ExpressionNode | null; + type: NodeType.tReturnStatement; hasEffects(options: ExecutionPathOptions) { return ( diff --git a/src/ast/nodes/SequenceExpression.ts b/src/ast/nodes/SequenceExpression.ts index 522b917a004..e26f658ccbd 100644 --- a/src/ast/nodes/SequenceExpression.ts +++ b/src/ast/nodes/SequenceExpression.ts @@ -16,8 +16,12 @@ import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class SequenceExpression extends NodeBase { - type: NodeType.tSequenceExpression; expressions: ExpressionNode[]; + type: NodeType.tSequenceExpression; + + deoptimizePath(path: ObjectPath) { + if (path.length > 0) this.expressions[this.expressions.length - 1].deoptimizePath(path); + } getLiteralValueAtPath( path: ObjectPath, @@ -74,10 +78,6 @@ export default class SequenceExpression extends NodeBase { this.expressions[this.expressions.length - 1].include(includeAllChildrenRecursively); } - deoptimizePath(path: ObjectPath) { - if (path.length > 0) this.expressions[this.expressions.length - 1].deoptimizePath(path); - } - render( code: MagicString, options: RenderOptions, @@ -101,10 +101,10 @@ export default class SequenceExpression extends NodeBase { lastEnd = end; if (node === this.expressions[this.expressions.length - 1] && includedNodes === 1) { node.render(code, options, { - renderedParentType: renderedParentType || this.parent.type, isCalleeOfRenderedParent: renderedParentType ? isCalleeOfRenderedParent - : (this.parent).callee === this + : (this.parent).callee === this, + renderedParentType: renderedParentType || this.parent.type }); } else { node.render(code, options); diff --git a/src/ast/nodes/SpreadElement.ts b/src/ast/nodes/SpreadElement.ts index 2a2c93d0f17..0d37012cb9e 100644 --- a/src/ast/nodes/SpreadElement.ts +++ b/src/ast/nodes/SpreadElement.ts @@ -3,8 +3,8 @@ import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class SpreadElement extends NodeBase { - type: NodeType.tSpreadElement; argument: ExpressionNode; + type: NodeType.tSpreadElement; bind() { super.bind(); diff --git a/src/ast/nodes/SwitchCase.ts b/src/ast/nodes/SwitchCase.ts index 30068eaf487..c9148ba79c1 100644 --- a/src/ast/nodes/SwitchCase.ts +++ b/src/ast/nodes/SwitchCase.ts @@ -8,9 +8,9 @@ import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase, StatementNode } from './shared/Node'; export default class SwitchCase extends NodeBase { - type: NodeType.tSwitchCase; - test: ExpressionNode | null; consequent: StatementNode[]; + test: ExpressionNode | null; + type: NodeType.tSwitchCase; include(includeAllChildrenRecursively: boolean) { this.included = true; diff --git a/src/ast/nodes/SwitchStatement.ts b/src/ast/nodes/SwitchStatement.ts index 157eb1ba84c..def7a56c944 100644 --- a/src/ast/nodes/SwitchStatement.ts +++ b/src/ast/nodes/SwitchStatement.ts @@ -6,9 +6,9 @@ import { ExpressionNode, StatementBase } from './shared/Node'; import SwitchCase from './SwitchCase'; export default class SwitchStatement extends StatementBase { - type: NodeType.tSwitchStatement; - discriminant: ExpressionNode; cases: SwitchCase[]; + discriminant: ExpressionNode; + type: NodeType.tSwitchStatement; createScope(parentScope: Scope) { this.scope = new BlockScope(parentScope); diff --git a/src/ast/nodes/TaggedTemplateExpression.ts b/src/ast/nodes/TaggedTemplateExpression.ts index 5e8be02709f..adb427315fc 100644 --- a/src/ast/nodes/TaggedTemplateExpression.ts +++ b/src/ast/nodes/TaggedTemplateExpression.ts @@ -7,9 +7,9 @@ import { ExpressionNode, NodeBase } from './shared/Node'; import TemplateLiteral from './TemplateLiteral'; export default class TaggedTemplateExpression extends NodeBase { - type: NodeType.tTaggedTemplateExpression; - tag: ExpressionNode; quasi: TemplateLiteral; + tag: ExpressionNode; + type: NodeType.tTaggedTemplateExpression; private callOptions: CallOptions; @@ -55,8 +55,8 @@ export default class TaggedTemplateExpression extends NodeBase { initialise() { this.included = false; this.callOptions = CallOptions.create({ - withNew: false, - callIdentifier: this + callIdentifier: this, + withNew: false }); } } diff --git a/src/ast/nodes/TemplateElement.ts b/src/ast/nodes/TemplateElement.ts index d47eff9fdea..9c2e5b9333f 100644 --- a/src/ast/nodes/TemplateElement.ts +++ b/src/ast/nodes/TemplateElement.ts @@ -3,8 +3,8 @@ import * as NodeType from './NodeType'; import { NodeBase } from './shared/Node'; export default class TemplateElement extends NodeBase { - type: NodeType.tTemplateElement; tail: boolean; + type: NodeType.tTemplateElement; value: { cooked: string | null; raw: string; diff --git a/src/ast/nodes/TemplateLiteral.ts b/src/ast/nodes/TemplateLiteral.ts index 72750c3c554..b38e52a122b 100644 --- a/src/ast/nodes/TemplateLiteral.ts +++ b/src/ast/nodes/TemplateLiteral.ts @@ -10,9 +10,9 @@ export function isTemplateLiteral(node: Node): node is TemplateLiteral { } export default class TemplateLiteral extends NodeBase { - type: NodeType.tTemplateLiteral; - quasis: TemplateElement[]; expressions: ExpressionNode[]; + quasis: TemplateElement[]; + type: NodeType.tTemplateLiteral; getLiteralValueAtPath(path: ObjectPath): LiteralValueOrUnknown { if (path.length > 0 || this.quasis.length !== 1) { diff --git a/src/ast/nodes/ThisExpression.ts b/src/ast/nodes/ThisExpression.ts index f14e95acb04..5da098b2224 100644 --- a/src/ast/nodes/ThisExpression.ts +++ b/src/ast/nodes/ThisExpression.ts @@ -46,8 +46,8 @@ export default class ThisExpression extends NodeBase { render(code: MagicString, _options: RenderOptions) { if (this.alias !== null) { code.overwrite(this.start, this.end, this.alias, { - storeName: true, - contentOnly: false + contentOnly: false, + storeName: true }); } } diff --git a/src/ast/nodes/ThrowStatement.ts b/src/ast/nodes/ThrowStatement.ts index 15518c1760d..422d6e40976 100644 --- a/src/ast/nodes/ThrowStatement.ts +++ b/src/ast/nodes/ThrowStatement.ts @@ -3,8 +3,8 @@ import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase } from './shared/Node'; export default class ThrowStatement extends StatementBase { - type: NodeType.tThrowStatement; argument: ExpressionNode; + type: NodeType.tThrowStatement; hasEffects(_options: ExecutionPathOptions) { return true; diff --git a/src/ast/nodes/UnaryExpression.ts b/src/ast/nodes/UnaryExpression.ts index a99a3f60db2..daea53f020e 100644 --- a/src/ast/nodes/UnaryExpression.ts +++ b/src/ast/nodes/UnaryExpression.ts @@ -9,20 +9,20 @@ import { ExpressionNode, NodeBase } from './shared/Node'; const unaryOperators: { [operator: string]: (value: LiteralValue) => LiteralValueOrUnknown; } = { - '-': value => -value, - '+': value => +value, '!': value => !value, - '~': value => ~value, + '+': value => +value, + '-': value => -value, + delete: () => UNKNOWN_VALUE, typeof: value => typeof value, void: () => undefined, - delete: () => UNKNOWN_VALUE + '~': value => ~value }; export default class UnaryExpression extends NodeBase { - type: NodeType.tUnaryExpression; + argument: ExpressionNode; operator: keyof typeof unaryOperators; prefix: boolean; - argument: ExpressionNode; + type: NodeType.tUnaryExpression; bind() { super.bind(); diff --git a/src/ast/nodes/UpdateExpression.ts b/src/ast/nodes/UpdateExpression.ts index 327c97b735b..22cbaec58a2 100644 --- a/src/ast/nodes/UpdateExpression.ts +++ b/src/ast/nodes/UpdateExpression.ts @@ -7,10 +7,10 @@ import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class UpdateExpression extends NodeBase { - type: NodeType.tUpdateExpression; - operator: '++' | '--'; argument: ExpressionNode; + operator: '++' | '--'; prefix: boolean; + type: NodeType.tUpdateExpression; bind() { super.bind(); diff --git a/src/ast/nodes/VariableDeclaration.ts b/src/ast/nodes/VariableDeclaration.ts index 3aa516c871f..d103698589a 100644 --- a/src/ast/nodes/VariableDeclaration.ts +++ b/src/ast/nodes/VariableDeclaration.ts @@ -35,26 +35,32 @@ function areAllDeclarationsIncludedAndNotExported(declarations: VariableDeclarat } export default class VariableDeclaration extends NodeBase { - type: NodeType.tVariableDeclaration; declarations: VariableDeclarator[]; kind: 'var' | 'let' | 'const'; + type: NodeType.tVariableDeclaration; + + deoptimizePath(_path: ObjectPath) { + for (const declarator of this.declarations) { + declarator.deoptimizePath(EMPTY_PATH); + } + } hasEffectsWhenAssignedAtPath(_path: ObjectPath, _options: ExecutionPathOptions) { return false; } - includeWithAllDeclaredVariables(includeAllChildrenRecursively: boolean) { + include(includeAllChildrenRecursively: boolean) { this.included = true; for (const declarator of this.declarations) { - declarator.include(includeAllChildrenRecursively); + if (includeAllChildrenRecursively || declarator.shouldBeIncluded()) + declarator.include(includeAllChildrenRecursively); } } - include(includeAllChildrenRecursively: boolean) { + includeWithAllDeclaredVariables(includeAllChildrenRecursively: boolean) { this.included = true; for (const declarator of this.declarations) { - if (includeAllChildrenRecursively || declarator.shouldBeIncluded()) - declarator.include(includeAllChildrenRecursively); + declarator.include(includeAllChildrenRecursively); } } @@ -65,12 +71,6 @@ export default class VariableDeclaration extends NodeBase { } } - deoptimizePath(_path: ObjectPath) { - for (const declarator of this.declarations) { - declarator.deoptimizePath(EMPTY_PATH); - } - } - render(code: MagicString, options: RenderOptions, nodeRenderOptions: NodeRenderOptions = BLANK) { if (areAllDeclarationsIncludedAndNotExported(this.declarations)) { for (const declarator of this.declarations) { @@ -87,6 +87,46 @@ export default class VariableDeclaration extends NodeBase { } } + private renderDeclarationEnd( + code: MagicString, + separatorString: string, + lastSeparatorPos: number, + actualContentEnd: number, + renderedContentEnd: number, + addSemicolon: boolean, + systemPatternExports: Variable[] + ): void { + if (code.original.charCodeAt(this.end - 1) === 59 /*";"*/) { + code.remove(this.end - 1, this.end); + } + if (addSemicolon) { + separatorString += ';'; + } + if (lastSeparatorPos !== null) { + if ( + code.original.charCodeAt(actualContentEnd - 1) === 10 /*"\n"*/ && + (code.original.charCodeAt(this.end) === 10 /*"\n"*/ || + code.original.charCodeAt(this.end) === 13) /*"\r"*/ + ) { + actualContentEnd--; + if (code.original.charCodeAt(actualContentEnd) === 13 /*"\r"*/) { + actualContentEnd--; + } + } + if (actualContentEnd === lastSeparatorPos + 1) { + code.overwrite(lastSeparatorPos, renderedContentEnd, separatorString); + } else { + code.overwrite(lastSeparatorPos, lastSeparatorPos + 1, separatorString); + code.remove(actualContentEnd, renderedContentEnd); + } + } else { + code.appendLeft(renderedContentEnd, separatorString); + } + if (systemPatternExports.length > 0) { + code.appendLeft(renderedContentEnd, ' ' + getSystemExportStatement(systemPatternExports)); + } + } + private renderReplacedDeclarations( code: MagicString, options: RenderOptions, @@ -176,44 +216,4 @@ export default class VariableDeclaration extends NodeBase { code.remove(start, end); } } - - private renderDeclarationEnd( - code: MagicString, - separatorString: string, - lastSeparatorPos: number, - actualContentEnd: number, - renderedContentEnd: number, - addSemicolon: boolean, - systemPatternExports: Variable[] - ): void { - if (code.original.charCodeAt(this.end - 1) === 59 /*";"*/) { - code.remove(this.end - 1, this.end); - } - if (addSemicolon) { - separatorString += ';'; - } - if (lastSeparatorPos !== null) { - if ( - code.original.charCodeAt(actualContentEnd - 1) === 10 /*"\n"*/ && - (code.original.charCodeAt(this.end) === 10 /*"\n"*/ || - code.original.charCodeAt(this.end) === 13) /*"\r"*/ - ) { - actualContentEnd--; - if (code.original.charCodeAt(actualContentEnd) === 13 /*"\r"*/) { - actualContentEnd--; - } - } - if (actualContentEnd === lastSeparatorPos + 1) { - code.overwrite(lastSeparatorPos, renderedContentEnd, separatorString); - } else { - code.overwrite(lastSeparatorPos, lastSeparatorPos + 1, separatorString); - code.remove(actualContentEnd, renderedContentEnd); - } - } else { - code.appendLeft(renderedContentEnd, separatorString); - } - if (systemPatternExports.length > 0) { - code.appendLeft(renderedContentEnd, ' ' + getSystemExportStatement(systemPatternExports)); - } - } } diff --git a/src/ast/nodes/VariableDeclarator.ts b/src/ast/nodes/VariableDeclarator.ts index 9f82d0e0de3..b6c5eaa4101 100644 --- a/src/ast/nodes/VariableDeclarator.ts +++ b/src/ast/nodes/VariableDeclarator.ts @@ -6,9 +6,9 @@ import { ExpressionNode, NodeBase } from './shared/Node'; import { PatternNode } from './shared/Pattern'; export default class VariableDeclarator extends NodeBase { - type: NodeType.tVariableDeclarator; id: PatternNode; init: ExpressionNode | null; + type: NodeType.tVariableDeclarator; declareDeclarator(kind: string) { this.id.declare(kind, this.init || UNDEFINED_EXPRESSION); diff --git a/src/ast/nodes/WhileStatement.ts b/src/ast/nodes/WhileStatement.ts index 3967f0da8a2..30d54805fad 100644 --- a/src/ast/nodes/WhileStatement.ts +++ b/src/ast/nodes/WhileStatement.ts @@ -3,9 +3,9 @@ import * as NodeType from './NodeType'; import { ExpressionNode, StatementBase, StatementNode } from './shared/Node'; export default class WhileStatement extends StatementBase { - type: NodeType.tWhileStatement; - test: ExpressionNode; body: StatementNode; + test: ExpressionNode; + type: NodeType.tWhileStatement; hasEffects(options: ExecutionPathOptions): boolean { return ( diff --git a/src/ast/nodes/YieldExpression.ts b/src/ast/nodes/YieldExpression.ts index b189f62e6ee..b06b611eee8 100644 --- a/src/ast/nodes/YieldExpression.ts +++ b/src/ast/nodes/YieldExpression.ts @@ -6,9 +6,9 @@ import * as NodeType from './NodeType'; import { ExpressionNode, NodeBase } from './shared/Node'; export default class YieldExpression extends NodeBase { - type: NodeType.tYieldExpression; argument: ExpressionNode | null; delegate: boolean; + type: NodeType.tYieldExpression; bind() { super.bind(); diff --git a/src/ast/nodes/index.ts b/src/ast/nodes/index.ts index 69415dcb58d..0002a4fbc7e 100644 --- a/src/ast/nodes/index.ts +++ b/src/ast/nodes/index.ts @@ -83,9 +83,9 @@ export const nodeConstructors: { ExportDefaultDeclaration, ExportNamedDeclaration, ExpressionStatement, - ForStatement, ForInStatement, ForOfStatement, + ForStatement, FunctionDeclaration, FunctionExpression, Identifier, @@ -118,8 +118,8 @@ export const nodeConstructors: { UnaryExpression, UnknownNode, UpdateExpression, - VariableDeclarator, VariableDeclaration, + VariableDeclarator, WhileStatement, YieldExpression }; diff --git a/src/ast/nodes/shared/ClassNode.ts b/src/ast/nodes/shared/ClassNode.ts index 9326642599c..e17cdda287a 100644 --- a/src/ast/nodes/shared/ClassNode.ts +++ b/src/ast/nodes/shared/ClassNode.ts @@ -9,8 +9,8 @@ import { ExpressionNode, NodeBase } from './Node'; export default class ClassNode extends NodeBase { body: ClassBody; - superClass: ExpressionNode | null; id: Identifier | null; + superClass: ExpressionNode | null; createScope(parentScope: Scope) { this.scope = new ChildScope(parentScope); diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index a75483b7f99..3b4a635a250 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -9,13 +9,12 @@ import { GenericEsTreeNode, NodeBase } from './Node'; import { PatternNode } from './Pattern'; export default class FunctionNode extends NodeBase { - id: Identifier | null; + async: boolean; body: BlockStatement; + id: Identifier | null; params: PatternNode[]; - async: boolean; - - scope: BlockScope; preventChildBlockScope: true; + scope: BlockScope; private isPrototypeDeoptimized: boolean; @@ -23,6 +22,20 @@ export default class FunctionNode extends NodeBase { this.scope = new FunctionScope(parentScope, this.context); } + deoptimizePath(path: ObjectPath) { + if (path.length === 1) { + if (path[0] === 'prototype') { + this.isPrototypeDeoptimized = true; + } else if (path[0] === UNKNOWN_KEY) { + this.isPrototypeDeoptimized = true; + + // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track + // which means the return expression needs to be reassigned as well + this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); + } + } + } + getReturnExpressionWhenCalledAtPath(path: ObjectPath) { return path.length === 0 ? this.scope.getReturnExpression() : UNKNOWN_EXPRESSION; } @@ -87,20 +100,6 @@ export default class FunctionNode extends NodeBase { ); super.parseNode(esTreeNode); } - - deoptimizePath(path: ObjectPath) { - if (path.length === 1) { - if (path[0] === 'prototype') { - this.isPrototypeDeoptimized = true; - } else if (path[0] === UNKNOWN_KEY) { - this.isPrototypeDeoptimized = true; - - // A reassignment of UNKNOWN_PATH is considered equivalent to having lost track - // which means the return expression needs to be reassigned as well - this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); - } - } - } } FunctionNode.prototype.preventChildBlockScope = true; diff --git a/src/ast/nodes/shared/MultiExpression.ts b/src/ast/nodes/shared/MultiExpression.ts index 91d4e1bf239..dd82a12c784 100644 --- a/src/ast/nodes/shared/MultiExpression.ts +++ b/src/ast/nodes/shared/MultiExpression.ts @@ -14,6 +14,12 @@ export class MultiExpression implements ExpressionEntity { this.expressions = expressions; } + deoptimizePath(path: ObjectPath): void { + for (const expression of this.expressions) { + expression.deoptimizePath(path); + } + } + getLiteralValueAtPath(): LiteralValueOrUnknown { return UNKNOWN_VALUE; } @@ -56,10 +62,4 @@ export class MultiExpression implements ExpressionEntity { } include(): void {} - - deoptimizePath(path: ObjectPath): void { - for (const expression of this.expressions) { - expression.deoptimizePath(path); - } - } } diff --git a/src/ast/nodes/shared/Node.ts b/src/ast/nodes/shared/Node.ts index 849c30ef7a3..5c73239b28c 100644 --- a/src/ast/nodes/shared/Node.ts +++ b/src/ast/nodes/shared/Node.ts @@ -80,18 +80,18 @@ export interface ExpressionNode extends ExpressionEntity, Node {} const NEW_EXECUTION_PATH = ExecutionPathOptions.create(); export class NodeBase implements ExpressionNode { - type: string; + context: AstContext; + end: number; + included: boolean; keys: string[]; + parent: Node | { context: AstContext; type: string }; scope: ChildScope; start: number; - end: number; - context: AstContext; - parent: Node | { type: string; context: AstContext }; - included: boolean; + type: string; constructor( esTreeNode: GenericEsTreeNode, - parent: Node | { type: string; context: AstContext }, + parent: Node | { context: AstContext; type: string }, parentScope: ChildScope ) { this.keys = keys[esTreeNode.type] || getAndCreateKeys(esTreeNode); @@ -131,6 +131,8 @@ export class NodeBase implements ExpressionNode { declare(_kind: string, _init: ExpressionEntity | null) {} + deoptimizePath(_path: ObjectPath) {} + getLiteralValueAtPath( _path: ObjectPath, _recursionTracker: ImmutableEntityPathTracker, @@ -241,8 +243,6 @@ export class NodeBase implements ExpressionNode { } } - deoptimizePath(_path: ObjectPath) {} - render(code: MagicString, options: RenderOptions) { for (const key of this.keys) { const value = (this)[key]; diff --git a/src/ast/scopes/FunctionScope.ts b/src/ast/scopes/FunctionScope.ts index 7c16461232b..a3b77ee5bc6 100644 --- a/src/ast/scopes/FunctionScope.ts +++ b/src/ast/scopes/FunctionScope.ts @@ -13,10 +13,10 @@ import ReturnValueScope from './ReturnValueScope'; export default class FunctionScope extends ReturnValueScope { variables: { - this: ThisVariable; - default: ExportDefaultVariable; - arguments: ArgumentsVariable; [name: string]: LocalVariable | GlobalVariable | ExternalVariable | ArgumentsVariable; + arguments: ArgumentsVariable; + default: ExportDefaultVariable; + this: ThisVariable; }; constructor(parent: ChildScope, context: AstContext) { diff --git a/src/ast/scopes/ModuleScope.ts b/src/ast/scopes/ModuleScope.ts index fe506aaf1c1..b38bd20dadf 100644 --- a/src/ast/scopes/ModuleScope.ts +++ b/src/ast/scopes/ModuleScope.ts @@ -10,8 +10,8 @@ import ChildScope from './ChildScope'; import GlobalScope from './GlobalScope'; export default class ModuleScope extends ChildScope { - parent: GlobalScope; context: AstContext; + parent: GlobalScope; constructor(parent: GlobalScope, context: AstContext) { super(parent); diff --git a/src/ast/scopes/ParameterScope.ts b/src/ast/scopes/ParameterScope.ts index 8d0c0ade1af..32460fa07a4 100644 --- a/src/ast/scopes/ParameterScope.ts +++ b/src/ast/scopes/ParameterScope.ts @@ -8,8 +8,8 @@ import Scope from './Scope'; export default class ParameterScope extends ChildScope { hoistedBodyVarScope: ChildScope; - private parameters: LocalVariable[] = []; private context: AstContext; + private parameters: LocalVariable[] = []; constructor(parent: Scope, context: AstContext) { super(parent); diff --git a/src/ast/scopes/ReturnValueScope.ts b/src/ast/scopes/ReturnValueScope.ts index 73625270334..36042a067ab 100644 --- a/src/ast/scopes/ReturnValueScope.ts +++ b/src/ast/scopes/ReturnValueScope.ts @@ -3,8 +3,8 @@ import { UNKNOWN_EXPRESSION, UNKNOWN_PATH } from '../values'; import ParameterScope from './ParameterScope'; export default class ReturnValueScope extends ParameterScope { - private returnExpressions: ExpressionEntity[] = []; private returnExpression: ExpressionEntity | null = null; + private returnExpressions: ExpressionEntity[] = []; addReturnExpression(expression: ExpressionEntity) { this.returnExpressions.push(expression); diff --git a/src/ast/scopes/Scope.ts b/src/ast/scopes/Scope.ts index 25aea78ada4..8c02756b560 100644 --- a/src/ast/scopes/Scope.ts +++ b/src/ast/scopes/Scope.ts @@ -10,13 +10,13 @@ import Variable from '../variables/Variable'; import ChildScope from './ChildScope'; export default class Scope { + children: ChildScope[] = []; variables: { - this?: ThisVariable | LocalVariable; - default?: ExportDefaultVariable; - arguments?: ArgumentsVariable; [name: string]: Variable; + arguments?: ArgumentsVariable; + default?: ExportDefaultVariable; + this?: ThisVariable | LocalVariable; } = Object.create(null); - children: ChildScope[] = []; addDeclaration( identifier: Identifier, diff --git a/src/ast/utils/EntityPathTracker.ts b/src/ast/utils/EntityPathTracker.ts index 126372dce79..a0929fe0753 100644 --- a/src/ast/utils/EntityPathTracker.ts +++ b/src/ast/utils/EntityPathTracker.ts @@ -2,15 +2,15 @@ import { Entity } from '../Entity'; import { ObjectPath } from '../values'; interface TrackedPaths { + paths: { [key: string]: TrackedPaths }; tracked: boolean; unknownPath: TrackedPaths | null; - paths: { [key: string]: TrackedPaths }; } const getNewTrackedPaths = (): TrackedPaths => ({ + paths: Object.create(null), tracked: false, - unknownPath: null, - paths: Object.create(null) + unknownPath: null }); export class EntityPathTracker { diff --git a/src/ast/values.ts b/src/ast/values.ts index 768687c799c..4e68c6c9eed 100644 --- a/src/ast/values.ts +++ b/src/ast/values.ts @@ -14,10 +14,10 @@ export const EMPTY_PATH: ObjectPath = []; export const UNKNOWN_PATH: ObjectPath = [UNKNOWN_KEY]; export interface MemberDescription { + callsArgs: number[] | null; + mutatesSelf: boolean; returns: { new (): ExpressionEntity } | null; returnsPrimitive: ExpressionEntity | null; - mutatesSelf: boolean; - callsArgs: number[] | null; } export interface MemberDescriptions { @@ -42,33 +42,33 @@ export const UNKNOWN_VALUE: UnknownValue = { UNKNOWN_VALUE: true }; export type LiteralValueOrUnknown = LiteralValue | UnknownValue; export const UNKNOWN_EXPRESSION: ExpressionEntity = { - included: true, + deoptimizePath: () => {}, getLiteralValueAtPath: () => UNKNOWN_VALUE, getReturnExpressionWhenCalledAtPath: () => UNKNOWN_EXPRESSION, hasEffectsWhenAccessedAtPath: path => path.length > 0, hasEffectsWhenAssignedAtPath: path => path.length > 0, hasEffectsWhenCalledAtPath: () => true, include: () => {}, - deoptimizePath: () => {}, + included: true, toString: () => '[[UNKNOWN]]' }; export const UNDEFINED_EXPRESSION: ExpressionEntity = { - included: true, + deoptimizePath: () => {}, getLiteralValueAtPath: () => undefined, getReturnExpressionWhenCalledAtPath: () => UNKNOWN_EXPRESSION, hasEffectsWhenAccessedAtPath: path => path.length > 0, hasEffectsWhenAssignedAtPath: path => path.length > 0, hasEffectsWhenCalledAtPath: () => true, include: () => {}, - deoptimizePath: () => {}, + included: true, toString: () => 'undefined' }; const returnsUnknown: RawMemberDescription = { value: { - returns: null, - returnsPrimitive: UNKNOWN_EXPRESSION, callsArgs: null, - mutatesSelf: false + mutatesSelf: false, + returns: null, + returnsPrimitive: UNKNOWN_EXPRESSION } }; const mutatesSelfReturnsUnknown: RawMemberDescription = { @@ -81,6 +81,8 @@ const callsArgReturnsUnknown: RawMemberDescription = { export class UnknownArrayExpression implements ExpressionEntity { included: boolean = false; + deoptimizePath() {} + getLiteralValueAtPath() { return UNKNOWN_VALUE; } @@ -115,8 +117,6 @@ export class UnknownArrayExpression implements ExpressionEntity { this.included = true; } - deoptimizePath() {} - toString() { return '[[UNKNOWN ARRAY]]'; } @@ -124,39 +124,39 @@ export class UnknownArrayExpression implements ExpressionEntity { const returnsArray: RawMemberDescription = { value: { - returns: UnknownArrayExpression, - returnsPrimitive: null, callsArgs: null, - mutatesSelf: false + mutatesSelf: false, + returns: UnknownArrayExpression, + returnsPrimitive: null } }; const mutatesSelfReturnsArray: RawMemberDescription = { value: { - returns: UnknownArrayExpression, - returnsPrimitive: null, callsArgs: null, - mutatesSelf: true + mutatesSelf: true, + returns: UnknownArrayExpression, + returnsPrimitive: null } }; const callsArgReturnsArray: RawMemberDescription = { value: { - returns: UnknownArrayExpression, - returnsPrimitive: null, callsArgs: [0], - mutatesSelf: false + mutatesSelf: false, + returns: UnknownArrayExpression, + returnsPrimitive: null } }; const callsArgMutatesSelfReturnsArray: RawMemberDescription = { value: { - returns: UnknownArrayExpression, - returnsPrimitive: null, callsArgs: [0], - mutatesSelf: true + mutatesSelf: true, + returns: UnknownArrayExpression, + returnsPrimitive: null } }; const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { - included: true, + deoptimizePath: () => {}, getLiteralValueAtPath: () => UNKNOWN_VALUE, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { @@ -174,29 +174,29 @@ const UNKNOWN_LITERAL_BOOLEAN: ExpressionEntity = { return true; }, include: () => {}, - deoptimizePath: () => {}, + included: true, toString: () => '[[UNKNOWN BOOLEAN]]' }; const returnsBoolean: RawMemberDescription = { value: { - returns: null, - returnsPrimitive: UNKNOWN_LITERAL_BOOLEAN, callsArgs: null, - mutatesSelf: false + mutatesSelf: false, + returns: null, + returnsPrimitive: UNKNOWN_LITERAL_BOOLEAN } }; const callsArgReturnsBoolean: RawMemberDescription = { value: { - returns: null, - returnsPrimitive: UNKNOWN_LITERAL_BOOLEAN, callsArgs: [0], - mutatesSelf: false + mutatesSelf: false, + returns: null, + returnsPrimitive: UNKNOWN_LITERAL_BOOLEAN } }; const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { - included: true, + deoptimizePath: () => {}, getLiteralValueAtPath: () => UNKNOWN_VALUE, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { @@ -214,37 +214,37 @@ const UNKNOWN_LITERAL_NUMBER: ExpressionEntity = { return true; }, include: () => {}, - deoptimizePath: () => {}, + included: true, toString: () => '[[UNKNOWN NUMBER]]' }; const returnsNumber: RawMemberDescription = { value: { - returns: null, - returnsPrimitive: UNKNOWN_LITERAL_NUMBER, callsArgs: null, - mutatesSelf: false + mutatesSelf: false, + returns: null, + returnsPrimitive: UNKNOWN_LITERAL_NUMBER } }; const mutatesSelfReturnsNumber: RawMemberDescription = { value: { - returns: null, - returnsPrimitive: UNKNOWN_LITERAL_NUMBER, callsArgs: null, - mutatesSelf: true + mutatesSelf: true, + returns: null, + returnsPrimitive: UNKNOWN_LITERAL_NUMBER } }; const callsArgReturnsNumber: RawMemberDescription = { value: { - returns: null, - returnsPrimitive: UNKNOWN_LITERAL_NUMBER, callsArgs: [0], - mutatesSelf: false + mutatesSelf: false, + returns: null, + returnsPrimitive: UNKNOWN_LITERAL_NUMBER } }; const UNKNOWN_LITERAL_STRING: ExpressionEntity = { - included: true, + deoptimizePath: () => {}, getLiteralValueAtPath: () => UNKNOWN_VALUE, getReturnExpressionWhenCalledAtPath: path => { if (path.length === 1) { @@ -261,22 +261,24 @@ const UNKNOWN_LITERAL_STRING: ExpressionEntity = { return true; }, include: () => {}, - deoptimizePath: () => {}, + included: true, toString: () => '[[UNKNOWN STRING]]' }; const returnsString: RawMemberDescription = { value: { - returns: null, - returnsPrimitive: UNKNOWN_LITERAL_STRING, callsArgs: null, - mutatesSelf: false + mutatesSelf: false, + returns: null, + returnsPrimitive: UNKNOWN_LITERAL_STRING } }; export class UnknownObjectExpression implements ExpressionEntity { included: boolean = false; + deoptimizePath() {} + getLiteralValueAtPath() { return UNKNOWN_VALUE; } @@ -311,8 +313,6 @@ export class UnknownObjectExpression implements ExpressionEntity { this.included = true; } - deoptimizePath() {} - toString() { return '[[UNKNOWN OBJECT]]'; } @@ -381,8 +381,8 @@ const literalStringMembers: MemberDescriptions = assembleMemberDescriptions( charCodeAt: returnsNumber, codePointAt: returnsNumber, concat: returnsString, - includes: returnsBoolean, endsWith: returnsBoolean, + includes: returnsBoolean, indexOf: returnsNumber, lastIndexOf: returnsNumber, localeCompare: returnsNumber, @@ -393,10 +393,10 @@ const literalStringMembers: MemberDescriptions = assembleMemberDescriptions( repeat: returnsString, replace: { value: { - returns: null, - returnsPrimitive: UNKNOWN_LITERAL_STRING, callsArgs: [1], - mutatesSelf: false + mutatesSelf: false, + returns: null, + returnsPrimitive: UNKNOWN_LITERAL_STRING } }, search: returnsNumber, @@ -444,9 +444,9 @@ export function hasMemberEffectWhenCalled( callOptions.args[argIndex].hasEffectsWhenCalledAtPath( EMPTY_PATH, CallOptions.create({ - withNew: false, args: [], - callIdentifier: {} // make sure the caller is unique to avoid this check being ignored + callIdentifier: {}, // make sure the caller is unique to avoid this check being ignored, + withNew: false }), options.getHasEffectsWhenCalledOptions() ) diff --git a/src/ast/variables/ArgumentsVariable.ts b/src/ast/variables/ArgumentsVariable.ts index b9cdfcc5030..b78367cd6c7 100644 --- a/src/ast/variables/ArgumentsVariable.ts +++ b/src/ast/variables/ArgumentsVariable.ts @@ -22,6 +22,15 @@ export default class ArgumentsVariable extends LocalVariable { this.parameters = parameters; } + deoptimizePath(path: ObjectPath) { + const firstArgNum = parseInt(path[0], 10); + if (path.length > 0) { + if (firstArgNum >= 0 && this.parameters[firstArgNum]) { + this.parameters[firstArgNum].deoptimizePath(path.slice(1)); + } + } + } + hasEffectsWhenAccessedAtPath(path: ObjectPath, options: ExecutionPathOptions) { return ( path.length > 1 && @@ -51,13 +60,4 @@ export default class ArgumentsVariable extends LocalVariable { options ); } - - deoptimizePath(path: ObjectPath) { - const firstArgNum = parseInt(path[0], 10); - if (path.length > 0) { - if (firstArgNum >= 0 && this.parameters[firstArgNum]) { - this.parameters[firstArgNum].deoptimizePath(path.slice(1)); - } - } - } } diff --git a/src/ast/variables/ExportDefaultVariable.ts b/src/ast/variables/ExportDefaultVariable.ts index 96fdccbc46f..0a47396b933 100644 --- a/src/ast/variables/ExportDefaultVariable.ts +++ b/src/ast/variables/ExportDefaultVariable.ts @@ -12,8 +12,8 @@ export function isExportDefaultVariable(variable: Variable): variable is ExportD } export default class ExportDefaultVariable extends LocalVariable { - isDefault: true; hasId: boolean; + isDefault: true; // Not initialised during construction private originalId: Identifier | null = null; diff --git a/src/ast/variables/ExternalVariable.ts b/src/ast/variables/ExternalVariable.ts index 3136b14c710..797edfca0d7 100644 --- a/src/ast/variables/ExternalVariable.ts +++ b/src/ast/variables/ExternalVariable.ts @@ -3,9 +3,9 @@ import Identifier from '../nodes/Identifier'; import Variable from './Variable'; export default class ExternalVariable extends Variable { - module: ExternalModule; isExternal: true; isNamespace: boolean; + module: ExternalModule; referenced: boolean; constructor(module: ExternalModule, name: string) { diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 9949e1a9582..54d82cc255a 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -22,11 +22,11 @@ import Variable from './Variable'; const MAX_PATH_DEPTH = 7; export default class LocalVariable extends Variable { + additionalInitializers: ExpressionEntity[] | null = null; declarations: (Identifier | ExportDefaultDeclaration)[]; init: ExpressionEntity | null; isLocal: true; module: Module; - additionalInitializers: ExpressionEntity[] | null = null; // Caching and deoptimization: // We track deoptimization when we do not return something unknown @@ -67,6 +67,25 @@ export default class LocalVariable extends Variable { } } + deoptimizePath(path: ObjectPath) { + if (path.length > MAX_PATH_DEPTH) return; + if (!(this.isReassigned || this.deoptimizationTracker.track(this, path))) { + if (path.length === 0) { + if (!this.isReassigned) { + this.isReassigned = true; + for (const expression of this.expressionsToBeDeoptimized) { + expression.deoptimizeCache(); + } + if (this.init) { + this.init.deoptimizePath(UNKNOWN_PATH); + } + } + } else if (this.init) { + this.init.deoptimizePath(path); + } + } + } + getLiteralValueAtPath( path: ObjectPath, recursionTracker: ImmutableEntityPathTracker, @@ -168,25 +187,6 @@ export default class LocalVariable extends Variable { } } } - - deoptimizePath(path: ObjectPath) { - if (path.length > MAX_PATH_DEPTH) return; - if (!(this.isReassigned || this.deoptimizationTracker.track(this, path))) { - if (path.length === 0) { - if (!this.isReassigned) { - this.isReassigned = true; - for (const expression of this.expressionsToBeDeoptimized) { - expression.deoptimizeCache(); - } - if (this.init) { - this.init.deoptimizePath(UNKNOWN_PATH); - } - } - } else if (this.init) { - this.init.deoptimizePath(path); - } - } - } } LocalVariable.prototype.isLocal = true; diff --git a/src/ast/variables/NamespaceVariable.ts b/src/ast/variables/NamespaceVariable.ts index 7020c3cab2f..ffb033ca55b 100644 --- a/src/ast/variables/NamespaceVariable.ts +++ b/src/ast/variables/NamespaceVariable.ts @@ -5,16 +5,14 @@ import { UNKNOWN_PATH } from '../values'; import Variable from './Variable'; export default class NamespaceVariable extends Variable { - isNamespace: true; context: AstContext; - module: Module; - - // Not initialised during construction + isNamespace: true; memberVariables: { [name: string]: Variable } = Object.create(null); + module: Module; + private containsExternalNamespace: boolean = false; private referencedEarly: boolean = false; private references: Identifier[] = []; - private containsExternalNamespace: boolean = false; constructor(context: AstContext) { super(context.getModuleName()); @@ -31,14 +29,24 @@ export default class NamespaceVariable extends Variable { this.name = identifier.name; } + // This is only called if "UNKNOWN_PATH" is reassigned as in all other situations, either the + // build fails due to an illegal namespace reassignment or MemberExpression already forwards + // the reassignment to the right variable. This means we lost track of this variable and thus + // need to reassign all exports. + deoptimizePath() { + for (const key in this.memberVariables) { + this.memberVariables[key].deoptimizePath(UNKNOWN_PATH); + } + } + include() { if (!this.included) { if (this.containsExternalNamespace) { this.context.error( { code: 'NAMESPACE_CANNOT_CONTAIN_EXTERNAL', - message: `Cannot create an explicit namespace object for module "${this.context.getModuleName()}" because it contains a reexported external namespace`, - id: this.module.id + id: this.module.id, + message: `Cannot create an explicit namespace object for module "${this.context.getModuleName()}" because it contains a reexported external namespace` }, undefined ); @@ -60,20 +68,6 @@ export default class NamespaceVariable extends Variable { } } - renderFirst() { - return this.referencedEarly; - } - - // This is only called if "UNKNOWN_PATH" is reassigned as in all other situations, either the - // build fails due to an illegal namespace reassignment or MemberExpression already forwards - // the reassignment to the right variable. This means we lost track of this variable and thus - // need to reassign all exports. - deoptimizePath() { - for (const key in this.memberVariables) { - this.memberVariables[key].deoptimizePath(UNKNOWN_PATH); - } - } - renderBlock(options: RenderOptions) { const _ = options.compact ? '' : ' '; const n = options.compact ? '' : '\n'; @@ -117,6 +111,10 @@ export default class NamespaceVariable extends Variable { return output; } + + renderFirst() { + return this.referencedEarly; + } } NamespaceVariable.prototype.isNamespace = true; diff --git a/src/ast/variables/ThisVariable.ts b/src/ast/variables/ThisVariable.ts index 7dc22aa1b46..a65c9706d7f 100644 --- a/src/ast/variables/ThisVariable.ts +++ b/src/ast/variables/ThisVariable.ts @@ -10,6 +10,10 @@ export default class ThisVariable extends LocalVariable { super('this', null, null, context); } + _getInit(options: ExecutionPathOptions): ExpressionEntity { + return options.getReplacedVariableInit(this) || UNKNOWN_EXPRESSION; + } + getLiteralValueAtPath(): LiteralValueOrUnknown { return UNKNOWN_VALUE; } @@ -38,8 +42,4 @@ export default class ThisVariable extends LocalVariable { super.hasEffectsWhenCalledAtPath(path, callOptions, options) ); } - - _getInit(options: ExecutionPathOptions): ExpressionEntity { - return options.getReplacedVariableInit(this) || UNKNOWN_EXPRESSION; - } } diff --git a/src/ast/variables/Variable.ts b/src/ast/variables/Variable.ts index 24dbbbf1f4d..272e53368b4 100644 --- a/src/ast/variables/Variable.ts +++ b/src/ast/variables/Variable.ts @@ -9,21 +9,19 @@ import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker' import { LiteralValueOrUnknown, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_VALUE } from '../values'; export default class Variable implements ExpressionEntity { - name: string; - renderName: string | null = null; - renderBaseName: string | null = null; - isExternal?: boolean; - isDefault?: boolean; - isNamespace?: boolean; - module: Module | ExternalModule | null; - - // Not initialised during construction exportName: string | null = null; - safeExportName: string | null = null; included: boolean = false; + isDefault?: boolean; + isExternal?: boolean; isId: boolean = false; - reexported: boolean = false; + isNamespace?: boolean; isReassigned: boolean = false; + module: Module | ExternalModule | null; + name: string; + reexported: boolean = false; + renderBaseName: string | null = null; + renderName: string | null = null; + safeExportName: string | null = null; constructor(name: string) { this.name = name; @@ -35,15 +33,12 @@ export default class Variable implements ExpressionEntity { */ addReference(_identifier: Identifier) {} + deoptimizePath(_path: ObjectPath) {} + getBaseVariableName(): string { return this.renderBaseName || this.renderName || this.name; } - getName(): string { - const name = this.renderName || this.name; - return this.renderBaseName ? `${this.renderBaseName}.${name}` : name; - } - getLiteralValueAtPath( _path: ObjectPath, _recursionTracker: ImmutableEntityPathTracker, @@ -52,6 +47,11 @@ export default class Variable implements ExpressionEntity { return UNKNOWN_VALUE; } + getName(): string { + const name = this.renderName || this.name; + return this.renderBaseName ? `${this.renderBaseName}.${name}` : name; + } + getReturnExpressionWhenCalledAtPath( _path: ObjectPath, _recursionTracker: ImmutableEntityPathTracker, @@ -86,8 +86,6 @@ export default class Variable implements ExpressionEntity { this.included = true; } - deoptimizePath(_path: ObjectPath) {} - setRenderNames(baseName: string | null, name: string | null) { this.renderBaseName = baseName; this.renderName = name; diff --git a/src/finalisers/shared/warnOnBuiltins.ts b/src/finalisers/shared/warnOnBuiltins.ts index 4e1e078baae..ea4be210a9a 100644 --- a/src/finalisers/shared/warnOnBuiltins.ts +++ b/src/finalisers/shared/warnOnBuiltins.ts @@ -2,27 +2,27 @@ import { ChunkDependencies } from '../../Chunk'; import { RollupWarning } from '../../rollup/types'; const builtins = { - process: true, - events: true, - stream: true, - util: true, - path: true, + assert: true, buffer: true, - querystring: true, - url: true, - string_decoder: true, - punycode: true, + console: true, + constants: true, + domain: true, + events: true, http: true, https: true, os: true, - assert: true, - constants: true, + path: true, + process: true, + punycode: true, + querystring: true, + stream: true, + string_decoder: true, timers: true, - console: true, - vm: true, - zlib: true, tty: true, - domain: true + url: true, + util: true, + vm: true, + zlib: true }; // Creating a browser chunk that depends on Node.js built-in modules ('util'). You might need to include https://www.npmjs.com/package/rollup-plugin-node-builtins @@ -45,7 +45,7 @@ export default function warnOnBuiltins( warn({ code: 'MISSING_NODE_BUILTINS', - modules: externalBuiltins, - message: `Creating a browser bundle that depends on Node.js built-in ${detail}. You might need to include https://www.npmjs.com/package/rollup-plugin-node-builtins` + message: `Creating a browser bundle that depends on Node.js built-in ${detail}. You might need to include https://www.npmjs.com/package/rollup-plugin-node-builtins`, + modules: externalBuiltins }); } diff --git a/src/rollup/index.ts b/src/rollup/index.ts index 3fc8dfdab76..42f9d060434 100644 --- a/src/rollup/index.ts +++ b/src/rollup/index.ts @@ -279,13 +279,13 @@ export default function rollup(rawInputOptions: GenericConfigObject): Promise((rawOutputOptions: GenericConfigObject) => { const promise = generate(rawOutputOptions, false).then(result => createOutput(result)); Object.defineProperty(promise, 'code', throwAsyncGenerateError); Object.defineProperty(promise, 'map', throwAsyncGenerateError); return promise; }), + watchFiles: Object.keys(graph.watchFiles), write: ((outputOptions: OutputOptions) => { if (!outputOptions || (!outputOptions.dir && !outputOptions.file)) { error({ diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 8489f31d6f6..84a23ff6d8d 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -4,7 +4,7 @@ import { EventEmitter } from 'events'; export const VERSION: string; export interface IdMap { - [key: string]: { id: string; external: boolean }; + [key: string]: { external: boolean; id: string }; } export interface RollupError extends RollupLogProps { @@ -30,9 +30,9 @@ export interface RollupLogProps { hook?: string; id?: string; loc?: { + column: number; file?: string; line: number; - column: number; }; message: string; name?: string; @@ -43,25 +43,24 @@ export interface RollupLogProps { } export interface ExistingRawSourceMap { - version: number; - sources: string[]; + file?: string; + mappings: string; names: string[]; sourceRoot?: string; + sources: string[]; sourcesContent?: string[]; - mappings: string; - file?: string; + version: number; } export type RawSourceMap = { mappings: '' } | ExistingRawSourceMap; export interface SourceMap { - version: string; file: string; + mappings: string; + names: string[]; sources: string[]; sourcesContent: string[]; - names: string[]; - mappings: string; - + version: string; toString(): string; toUrl(): string; } @@ -77,31 +76,31 @@ export interface TransformSourceDescription extends SourceDescription { } export interface ModuleJSON { - id: string; - dependencies: string[]; - transformDependencies: string[] | null; - transformAssets: Asset[] | void; + ast: ESTree.Program; code: string; + // note if plugins use new this.cache to opt-out auto transform cache + customTransformCache: boolean; + dependencies: string[]; + id: string; originalCode: string; originalSourcemap: RawSourceMap | void; - ast: ESTree.Program; - sourcemapChain: RawSourceMap[]; resolvedIds: IdMap; - // note if plugins use new this.cache to opt-out auto transform cache - customTransformCache: boolean; + sourcemapChain: RawSourceMap[]; + transformAssets: Asset[] | void; + transformDependencies: string[] | null; } export interface Asset { + fileName: string; name: string; source: string | Buffer; - fileName: string; } export interface PluginCache { - has(id: string): boolean; + delete(id: string): boolean; get(id: string): T; + has(id: string): boolean; set(id: string, value: T): void; - delete(id: string): boolean; } export interface MinimalPluginContext { @@ -109,26 +108,26 @@ export interface MinimalPluginContext { } export interface PluginContext extends MinimalPluginContext { - /** @deprecated */ - watcher: EventEmitter; addWatchFile: (id: string) => void; cache: PluginCache; - resolveId: ResolveIdHook; - isExternal: IsExternal; - parse: (input: string, options: any) => ESTree.Program; - emitAsset(name: string, source?: string | Buffer): string; - setAssetSource: (assetId: string, source: string | Buffer) => void; getAssetFileName: (assetId: string) => string; - warn(warning: RollupWarning | string, pos?: { line: number; column: number }): void; - error(err: RollupError | string, pos?: { line: number; column: number }): void; - moduleIds: IterableIterator; getModuleInfo: ( moduleId: string ) => { id: string; - isExternal: boolean; importedIds: string[]; + isExternal: boolean; }; + isExternal: IsExternal; + moduleIds: IterableIterator; + parse: (input: string, options: any) => ESTree.Program; + resolveId: ResolveIdHook; + setAssetSource: (assetId: string, source: string | Buffer) => void; + /** @deprecated */ + watcher: EventEmitter; + emitAsset(name: string, source?: string | Buffer): string; + error(err: RollupError | string, pos?: { column: number; line: number }): void; + warn(warning: RollupWarning | string, pos?: { column: number; line: number }): void; } export interface PluginContextMeta { @@ -215,7 +214,6 @@ export interface Plugin { bundle: OutputBundle, isWrite: boolean ) => void | Promise; - writeBundle?: (this: PluginContext, bundle: OutputBundle) => void | Promise; intro?: AddonHook; load?: LoadHook; name: string; @@ -244,11 +242,12 @@ export interface Plugin { /** @deprecated */ transformChunk?: TransformChunkHook; watchChange?: (id: string) => void; + writeBundle?: (this: PluginContext, bundle: OutputBundle) => void | Promise; } export interface TreeshakingOptions { - propertyReadSideEffects: boolean; annotations: boolean; + propertyReadSideEffects: boolean; pureExternalModules: boolean; } @@ -286,8 +285,8 @@ export type OptionsPaths = Record | ((id: string) => string); export interface OutputOptions { amd?: { - id?: string; define?: string; + id?: string; }; assetFileNames?: string; banner?: string | (() => string | Promise); @@ -295,9 +294,9 @@ export interface OutputOptions { compact?: boolean; // only required for bundle.write dir?: string; - exports?: 'default' | 'named' | 'none' | 'auto'; entryFileNames?: string; esModule?: boolean; + exports?: 'default' | 'named' | 'none' | 'auto'; extend?: boolean; // only required for bundle.write file?: string; @@ -330,17 +329,17 @@ export interface SerializedTimings { } export interface OutputAsset { - isAsset: true; code?: undefined; fileName: string; + isAsset: true; source: string | Buffer; } export interface RenderedModule { - renderedExports: string[]; + originalLength: number; removedExports: string[]; + renderedExports: string[]; renderedLength: number; - originalLength: number; } export interface RenderedChunk { @@ -378,10 +377,10 @@ export interface RollupOutput { export interface RollupBuild { cache: RollupCache; - watchFiles: string[]; generate: (outputOptions: OutputOptions) => Promise; - write: (options: OutputOptions) => Promise; getTimings?: () => SerializedTimings; + watchFiles: string[]; + write: (options: OutputOptions) => Promise; } export interface RollupOptions extends InputOptions { @@ -391,33 +390,33 @@ export interface RollupOptions extends InputOptions { export function rollup(options: RollupOptions): Promise; // chokidar watch options export interface WatchOptions { - persistent?: boolean; - ignored?: any; - ignoreInitial?: boolean; - followSymlinks?: boolean; - cwd?: string; - disableGlobbing?: boolean; - usePolling?: boolean; - useFsEvents?: boolean; alwaysStat?: boolean; - depth?: number; - interval?: number; - binaryInterval?: number; - ignorePermissionErrors?: boolean; atomic?: boolean | number; awaitWriteFinish?: | { - stabilityThreshold?: number; pollInterval?: number; + stabilityThreshold?: number; } | boolean; + binaryInterval?: number; + cwd?: string; + depth?: number; + disableGlobbing?: boolean; + followSymlinks?: boolean; + ignored?: any; + ignoreInitial?: boolean; + ignorePermissionErrors?: boolean; + interval?: number; + persistent?: boolean; + useFsEvents?: boolean; + usePolling?: boolean; } export interface WatcherOptions { chokidar?: boolean | WatchOptions; - include?: string[]; - exclude?: string[]; clearScreen?: boolean; + exclude?: string[]; + include?: string[]; } export interface RollupWatchOptions extends InputOptions { diff --git a/src/utils/addons.ts b/src/utils/addons.ts index f2e29c359ef..fe6d30f53fb 100644 --- a/src/utils/addons.ts +++ b/src/utils/addons.ts @@ -3,10 +3,10 @@ import { OutputOptions } from '../rollup/types'; import { error } from './error'; export interface Addons { - intro?: string; - outro?: string; banner?: string; footer?: string; + intro?: string; + outro?: string; } function evalIfFn(strOrFn: string | (() => string | Promise)): string | Promise { diff --git a/src/utils/assetHooks.ts b/src/utils/assetHooks.ts index e55cb9cf067..619757f426a 100644 --- a/src/utils/assetHooks.ts +++ b/src/utils/assetHooks.ts @@ -114,8 +114,8 @@ export function finaliseAsset(asset: Asset, outputBundle: OutputBundle, assetFil const fileName = getAssetFileName(asset, outputBundle, assetFileNames); asset.fileName = fileName; outputBundle[fileName] = { - isAsset: true, fileName, + isAsset: true, source: asset.source }; } @@ -129,9 +129,9 @@ export function createTransformEmitAsset(assetsById: Map, emitAss const asset = assetsById.get(assetId); // distinguish transform assets assets.push({ + fileName: undefined, name: asset.name, - source: asset.source, - fileName: undefined + source: asset.source }); return assetId; } diff --git a/src/utils/collapseSourcemaps.ts b/src/utils/collapseSourcemaps.ts index 8c2da89822b..1a2c07bdf5a 100644 --- a/src/utils/collapseSourcemaps.ts +++ b/src/utils/collapseSourcemaps.ts @@ -6,9 +6,9 @@ import { error } from './error'; import { basename, dirname, relative, resolve } from './path'; class Source { - isOriginal: boolean; - filename: string; content: string; + filename: string; + isOriginal: boolean; constructor(filename: string, content: string) { this.isOriginal = true; @@ -26,18 +26,18 @@ type SourceMapSegmentVector = | [number, number, number, number]; interface SourceMapSegmentObject { - line: number; column: number; + line: number; name: string; source: Source; } class Link { - sources: Source[]; - names: string[]; mappings: SourceMapSegmentVector[][]; + names: string[]; + sources: Source[]; - constructor(map: { names: string[]; mappings: SourceMapSegmentVector[][] }, sources: Source[]) { + constructor(map: { mappings: SourceMapSegmentVector[][]; names: string[] }, sources: Source[]) { this.sources = sources; this.names = map.names; this.mappings = map.mappings; @@ -147,53 +147,55 @@ export default function collapseSourcemaps( if (map.missing) { bundle.graph.warn({ code: 'SOURCEMAP_BROKEN', - plugin: map.plugin, message: `Sourcemap is likely to be incorrect: a plugin${ map.plugin ? ` ('${map.plugin}')` : `` } was used to transform files, but didn't generate a sourcemap for the transformation. Consult the plugin documentation for help`, + plugin: map.plugin, url: `https://rollupjs.org/guide/en#warning-sourcemap-is-likely-to-be-incorrect` }); map = { - names: [], - mappings: '' + mappings: '', + names: [] }; } return new Link(map, [source]); } - const moduleSources = modules.filter(module => !module.excludeFromSourcemap).map(module => { - let sourcemapChain = module.sourcemapChain; - - let source: Source; - const originalSourcemap = module.originalSourcemap; - if (!originalSourcemap) { - source = new Source(module.id, module.originalCode); - } else { - const sources = originalSourcemap.sources; - const sourcesContent = originalSourcemap.sourcesContent || []; + const moduleSources = modules + .filter(module => !module.excludeFromSourcemap) + .map(module => { + let sourcemapChain = module.sourcemapChain; - if (sources == null || (sources.length <= 1 && sources[0] == null)) { - source = new Source(module.id, sourcesContent[0]); - sourcemapChain = [originalSourcemap].concat(sourcemapChain); + let source: Source; + const originalSourcemap = module.originalSourcemap; + if (!originalSourcemap) { + source = new Source(module.id, module.originalCode); } else { - // TODO indiscriminately treating IDs and sources as normal paths is probably bad. - const directory = dirname(module.id) || '.'; - const sourceRoot = originalSourcemap.sourceRoot || '.'; - - const baseSources = sources.map((source, i) => { - return new Source(resolve(directory, sourceRoot, source), sourcesContent[i]); - }); - - source = new Link(originalSourcemap, baseSources); + const sources = originalSourcemap.sources; + const sourcesContent = originalSourcemap.sourcesContent || []; + + if (sources == null || (sources.length <= 1 && sources[0] == null)) { + source = new Source(module.id, sourcesContent[0]); + sourcemapChain = [originalSourcemap].concat(sourcemapChain); + } else { + // TODO indiscriminately treating IDs and sources as normal paths is probably bad. + const directory = dirname(module.id) || '.'; + const sourceRoot = originalSourcemap.sourceRoot || '.'; + + const baseSources = sources.map((source, i) => { + return new Source(resolve(directory, sourceRoot, source), sourcesContent[i]); + }); + + source = new Link(originalSourcemap, baseSources); + } } - } - source = sourcemapChain.reduce(linkMap, source); + source = sourcemapChain.reduce(linkMap, source); - return source; - }); + return source; + }); let source = new Link(map, moduleSources); diff --git a/src/utils/deconflictChunk.ts b/src/utils/deconflictChunk.ts index 4b2db098e32..71ee531193b 100644 --- a/src/utils/deconflictChunk.ts +++ b/src/utils/deconflictChunk.ts @@ -15,12 +15,12 @@ const DECONFLICT_IMPORTED_VARIABLES_BY_FORMAT: { preserveModules: boolean ) => void; } = { + amd: deconflictImportsOther, cjs: deconflictImportsOther, + es: deconflictImportsEsm, iife: deconflictImportsOther, - amd: deconflictImportsOther, - umd: deconflictImportsOther, system: deconflictImportsEsm, - es: deconflictImportsEsm + umd: deconflictImportsOther }; export function deconflictChunk( diff --git a/src/utils/error.ts b/src/utils/error.ts index b7a20fc3f55..d32b3d56ee8 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -10,7 +10,7 @@ export function error(base: Error | RollupError, props?: RollupError) { export function augmentCodeLocation( object: RollupError | RollupWarning, - pos: { line: number; column: number }, + pos: { column: number; line: number }, source: string, id: string ): void { diff --git a/src/utils/getOriginalLocation.ts b/src/utils/getOriginalLocation.ts index c6329151434..dc7d5bbb16c 100644 --- a/src/utils/getOriginalLocation.ts +++ b/src/utils/getOriginalLocation.ts @@ -2,7 +2,7 @@ import { ExistingRawSourceMap, RawSourceMap } from '../rollup/types'; export function getOriginalLocation( sourcemapChain: RawSourceMap[], - location: { line: number; column: number; source?: string; name?: string } + location: { column: number; line: number; name?: string; source?: string } ) { const filteredSourcemapChain = sourcemapChain.filter(sourcemap => sourcemap.mappings); @@ -16,10 +16,10 @@ export function getOriginalLocation( if (segment[0] >= location.column) { if (segment.length < 4) break; location = { - line: segment[2] + 1, column: segment[3], - source: sourcemap.sources[segment[1]], - name: sourcemap.names[segment[4]] + line: segment[2] + 1, + name: sourcemap.names[segment[4]], + source: sourcemap.sources[segment[1]] }; locationFound = true; break; diff --git a/src/utils/mergeOptions.ts b/src/utils/mergeOptions.ts index 30abada9b8f..78a8555de52 100644 --- a/src/utils/mergeOptions.ts +++ b/src/utils/mergeOptions.ts @@ -84,13 +84,13 @@ export default function mergeOptions({ command: rawCommandOptions = {}, defaultOnWarnHandler }: { - config: GenericConfigObject; command?: GenericConfigObject; + config: GenericConfigObject; defaultOnWarnHandler?: WarningHandler; }): { inputOptions: any; - outputOptions: any; optionError: string | null; + outputOptions: any; } { const command = getCommandOptions(rawCommandOptions); const inputOptions = getInputOptions(config, command, defaultOnWarnHandler); @@ -143,8 +143,8 @@ export default function mergeOptions({ return { inputOptions, - outputOptions, - optionError: unknownOptionErrors.length > 0 ? unknownOptionErrors.join('\n') : null + optionError: unknownOptionErrors.length > 0 ? unknownOptionErrors.join('\n') : null, + outputOptions }; } @@ -197,23 +197,23 @@ function getInputOptions( acorn: config.acorn, acornInjectPlugins: config.acornInjectPlugins, cache: getOption('cache'), - experimentalCacheExpiry: getOption('experimentalCacheExpiry', 10), + chunkGroupingSize: getOption('chunkGroupingSize', 5000), context: config.context, + experimentalCacheExpiry: getOption('experimentalCacheExpiry', 10), + experimentalOptimizeChunks: getOption('experimentalOptimizeChunks'), experimentalTopLevelAwait: getOption('experimentalTopLevelAwait'), external: getExternal(config, command), inlineDynamicImports: getOption('inlineDynamicImports', false), input: getOption('input'), manualChunks: getOption('manualChunks'), - chunkGroupingSize: getOption('chunkGroupingSize', 5000), - experimentalOptimizeChunks: getOption('experimentalOptimizeChunks'), moduleContext: config.moduleContext, onwarn: getOnWarn(config, command, defaultOnWarnHandler), perf: getOption('perf', false), plugins: config.plugins, preserveModules: getOption('preserveModules'), preserveSymlinks: getOption('preserveSymlinks'), - treeshake: getObjectOption(config, command, 'treeshake'), shimMissingExports: getOption('shimMissingExports'), + treeshake: getObjectOption(config, command, 'treeshake'), watch: config.watch }; @@ -235,9 +235,9 @@ function getOutputOptions( amd: { ...config.amd, ...command.amd }, assetFileNames: getOption('assetFileNames'), banner: getOption('banner'), - dir: getOption('dir'), chunkFileNames: getOption('chunkFileNames'), compact: getOption('compact', false), + dir: getOption('dir'), entryFileNames: getOption('entryFileNames'), esModule: getOption('esModule', true), exports: getOption('exports'), diff --git a/src/utils/pluginDriver.ts b/src/utils/pluginDriver.ts index 16bf33700a9..b7dcd08aed1 100644 --- a/src/utils/pluginDriver.ts +++ b/src/utils/pluginDriver.ts @@ -19,9 +19,8 @@ import { NameCollection } from './reservedNames'; export interface PluginDriver { emitAsset: EmitAsset; + hasLoadersOrTransforms: boolean; getAssetFileName(assetId: string): string; - hookSeq(hook: string, args?: any[], context?: HookContext): Promise; - hookSeqSync(hook: string, args?: any[], context?: HookContext): void; hookFirst(hook: string, args?: any[], hookContext?: HookContext): Promise; hookParallel(hook: string, args?: any[], hookContext?: HookContext): Promise; hookReduceArg0( @@ -37,17 +36,18 @@ export interface PluginDriver { reduce: Reduce, hookContext?: HookContext ): Promise; - hasLoadersOrTransforms: boolean; + hookSeq(hook: string, args?: any[], context?: HookContext): Promise; + hookSeqSync(hook: string, args?: any[], context?: HookContext): void; } export type Reduce = (reduction: T, result: R, plugin: Plugin) => T; export type HookContext = (context: PluginContext, plugin?: Plugin) => PluginContext; const deprecatedHookNames: Record = { - transformBundle: 'renderChunk', - transformChunk: 'renderChunk', ongenerate: 'generateBundle', - onwrite: 'generateBundle' + onwrite: 'generateBundle', + transformBundle: 'renderChunk', + transformChunk: 'renderChunk' }; export function createPluginDriver( @@ -121,9 +121,24 @@ export function createPluginDriver( return graph.isExternal(id, parentId, isResolved); }, getAssetFileName, + getModuleInfo: (moduleId: string) => { + const foundModule = graph.moduleById.get(moduleId); + if (foundModule == null) { + throw new Error(`Unable to find module ${moduleId}`); + } + + return { + id: foundModule.id, + importedIds: foundModule.isExternal + ? [] + : (foundModule as Module).sources.map(id => (foundModule as Module).resolvedIds[id].id), + isExternal: !!foundModule.isExternal + }; + }, meta: { rollupVersion }, + moduleIds: graph.moduleById.keys(), parse: graph.contextParse, resolveId(id: string, parent: string) { return pluginDriver.hookFirst('resolveId', [id, parent]); @@ -136,26 +151,11 @@ export function createPluginDriver( warning.plugin = plugin.name || `Plugin at position ${pidx + 1}`; graph.warn(warning); }, - moduleIds: graph.moduleById.keys(), - getModuleInfo: (moduleId: string) => { - const foundModule = graph.moduleById.get(moduleId); - if (foundModule == null) { - throw new Error(`Unable to find module ${moduleId}`); - } - - return { - id: foundModule.id, - isExternal: !!foundModule.isExternal, - importedIds: foundModule.isExternal - ? [] - : (foundModule as Module).sources.map(id => (foundModule as Module).resolvedIds[id].id) - }; - }, watcher: watcher ? { ...(watcher), - on: deprecatedWatchListener, - addListener: deprecatedWatchListener + addListener: deprecatedWatchListener, + on: deprecatedWatchListener } : undefined }; diff --git a/src/utils/pureComments.ts b/src/utils/pureComments.ts index 406f540ae78..cfcc3a1b196 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/pureComments.ts @@ -41,7 +41,7 @@ const isPureComment = (comment: CommentDescription) => pureCommentRegex.test(com export function markPureCallExpressions(comments: CommentDescription[], esTreeAst: ESTree.Program) { handlePureAnnotationsOfNode(esTreeAst, { - commentNodes: comments.filter(isPureComment), - commentIndex: 0 + commentIndex: 0, + commentNodes: comments.filter(isPureComment) }); } diff --git a/src/utils/renderChunk.ts b/src/utils/renderChunk.ts index 2056538ccb1..b8e148c9c0e 100644 --- a/src/utils/renderChunk.ts +++ b/src/utils/renderChunk.ts @@ -12,12 +12,12 @@ export default function renderChunk({ sourcemapChain, options }: { - graph: Graph; chunk: Chunk; - renderChunk: RenderedChunk; code: string; - sourcemapChain: RawSourceMap[]; + graph: Graph; options: OutputOptions; + renderChunk: RenderedChunk; + sourcemapChain: RawSourceMap[]; }) { const renderChunkReducer = (code: string, result: any, plugin: Plugin): string => { if (result == null) return code; diff --git a/src/utils/renderHelpers.ts b/src/utils/renderHelpers.ts index dc11da21357..c81297cc5e1 100644 --- a/src/utils/renderHelpers.ts +++ b/src/utils/renderHelpers.ts @@ -12,12 +12,12 @@ export interface RenderOptions { } export interface NodeRenderOptions { - start?: number; end?: number; - isNoStatement?: boolean; - renderedParentType?: string; // also serves as a flag if the rendered parent is different from the actual parent isCalleeOfRenderedParent?: boolean; + isNoStatement?: boolean; isShorthandProperty?: boolean; + renderedParentType?: string; // also serves as a flag if the rendered parent is different from the actual parent + start?: number; } export const NO_SEMICOLON: NodeRenderOptions = { isNoStatement: true }; @@ -100,8 +100,8 @@ export function renderStatementList( if (currentNode.included) { currentNodeNeedsBoundaries ? currentNode.render(code, options, { - start: currentNodeStart, - end: nextNodeStart + end: nextNodeStart, + start: currentNodeStart }) : currentNode.render(code, options); } else { @@ -120,11 +120,11 @@ export function getCommaSeparatedNodesWithBoundaries( start: number, end: number ): ({ - node: N; - start: number; - separator: number | null; contentEnd: number; end: number; + node: N; + separator: number | null; + start: number; })[] { const splitUpNodes = []; let node, nextNode, nextNodeStart, contentEnd, char; @@ -148,22 +148,22 @@ export function getCommaSeparatedNodesWithBoundaries( nextNodeStart++; if (node !== undefined) { splitUpNodes.push({ - node, - start, contentEnd, + end: nextNodeStart, + node, separator, - end: nextNodeStart + start }); } node = nextNode; start = nextNodeStart; } splitUpNodes.push({ + contentEnd: end, + end, node, - start, separator: null, - contentEnd: end, - end + start }); return splitUpNodes; } diff --git a/src/utils/reservedNames.ts b/src/utils/reservedNames.ts index c2f4fcb840a..ea1d87f286c 100644 --- a/src/utils/reservedNames.ts +++ b/src/utils/reservedNames.ts @@ -56,18 +56,18 @@ const NONE: NameCollection = {}; const EXPORTS: NameCollection = { exports: true }; export const RESERVED_NAMES_BY_FORMAT: { - [format: string]: { formatGlobals: NameCollection; forbiddenNames: NameCollection }; + [format: string]: { forbiddenNames: NameCollection; formatGlobals: NameCollection }; } = { + amd: { formatGlobals: EXPORTS, forbiddenNames: RESERVED_NAMES }, cjs: { - formatGlobals: { exports: true, module: true, [INTEROP_DEFAULT_VARIABLE]: true }, - forbiddenNames: RESERVED_NAMES + forbiddenNames: RESERVED_NAMES, + formatGlobals: { exports: true, module: true, [INTEROP_DEFAULT_VARIABLE]: true } }, + es: { formatGlobals: NONE, forbiddenNames: RESERVED_NAMES }, iife: { formatGlobals: EXPORTS, forbiddenNames: RESERVED_NAMES }, - amd: { formatGlobals: EXPORTS, forbiddenNames: RESERVED_NAMES }, - umd: { formatGlobals: EXPORTS, forbiddenNames: RESERVED_NAMES }, system: { - formatGlobals: NONE, - forbiddenNames: Object.assign(Object.create(null), RESERVED_NAMES, EXPORTS) + forbiddenNames: Object.assign(Object.create(null), RESERVED_NAMES, EXPORTS), + formatGlobals: NONE }, - es: { formatGlobals: NONE, forbiddenNames: RESERVED_NAMES } + umd: { formatGlobals: EXPORTS, forbiddenNames: RESERVED_NAMES } }; diff --git a/src/utils/timers.ts b/src/utils/timers.ts index 8a733e3535a..884b62dd484 100644 --- a/src/utils/timers.ts +++ b/src/utils/timers.ts @@ -1,23 +1,23 @@ import { InputOptions, SerializedTimings } from '../rollup/types'; type StartTime = [number, number] | number; + interface Timer { - time: number; memory: number; - totalMemory: number; - startTime: StartTime; startMemory: number; + startTime: StartTime; + time: number; + totalMemory: number; } + interface Timers { [label: string]: Timer; } const NOOP = () => {}; - let getStartTime: () => StartTime = () => 0; let getElapsedTime: (previous: StartTime) => number = () => 0; let getMemory: () => number = () => 0; - let timers: Timers = {}; const normalizeHrTime = (time: [number, number]) => time[0] * 1e3 + time[1] / 1e6; @@ -52,11 +52,11 @@ function timeStartImpl(label: string, level: number = 3) { label = getPersistedLabel(label, level); if (!timers.hasOwnProperty(label)) { timers[label] = { - totalMemory: 0, - startTime: undefined, + memory: 0, startMemory: undefined, + startTime: undefined, time: 0, - memory: 0 + totalMemory: 0 }; } const currentMemory = getMemory(); @@ -86,13 +86,13 @@ export let timeStart: (label: string, level?: number) => void = NOOP, timeEnd: (label: string, level?: number) => void = NOOP; const TIMED_PLUGIN_HOOKS: { [hook: string]: boolean } = { - transform: true, - transformBundle: true, load: true, - resolveId: true, ongenerate: true, onwrite: true, - resolveDynamicImport: true + resolveDynamicImport: true, + resolveId: true, + transform: true, + transformBundle: true }; function getPluginWithTimers(plugin: any, index: number): Plugin { diff --git a/src/utils/transform.ts b/src/utils/transform.ts index bf8eb240629..b388d3e5f4b 100644 --- a/src/utils/transform.ts +++ b/src/utils/transform.ts @@ -31,15 +31,12 @@ export default function transform( originalSourcemap.mappings = decode(originalSourcemap.mappings); const baseEmitAsset = graph.pluginDriver.emitAsset; - const originalCode = source.code; let ast = source.ast; - let transformDependencies: string[]; - let assets: Asset[]; let customTransformCache = false; - let trackedPluginCache: { used: boolean; cache: PluginCache }; + let trackedPluginCache: { cache: PluginCache; used: boolean }; let curPlugin: Plugin; const curSource: string = source.code; @@ -75,8 +72,8 @@ export default function transform( if (typeof result === 'string') { result = { - code: result, ast: undefined, + code: result, map: undefined }; } else if (typeof result.map === 'string') { @@ -115,14 +112,14 @@ export default function transform( return { ...pluginContext, cache: trackedPluginCache ? trackedPluginCache.cache : pluginContext.cache, - warn(warning: RollupWarning | string, pos?: { line: number; column: number }) { + warn(warning: RollupWarning | string, pos?: { column: number; line: number }) { if (typeof warning === 'string') warning = { message: warning } as RollupWarning; if (pos) augmentCodeLocation(warning, pos, curSource, id); warning.id = id; warning.hook = 'transform'; pluginContext.warn(warning); }, - error(err: RollupError | string, pos?: { line: number; column: number }) { + error(err: RollupError | string, pos?: { column: number; line: number }) { if (typeof err === 'string') err = { message: err }; if (pos) augmentCodeLocation(err, pos, curSource, id); err.id = id; @@ -164,13 +161,13 @@ export default function transform( if (!customTransformCache && setAssetSourceErr) throw setAssetSourceErr; return { + ast: ast, code, - transformDependencies, + customTransformCache, originalCode, originalSourcemap, - ast: ast, sourcemapChain, - customTransformCache + transformDependencies }; }); } diff --git a/src/watch/fileWatchers.ts b/src/watch/fileWatchers.ts index e4e38ed2e47..0908a2bdf73 100644 --- a/src/watch/fileWatchers.ts +++ b/src/watch/fileWatchers.ts @@ -32,8 +32,9 @@ export function deleteTask(id: string, target: Task, chokidarOptionsHash: string } export default class FileWatcher { - fsWatcher: FSWatcher | fs.FSWatcher; fileExists: boolean; + fsWatcher: FSWatcher | fs.FSWatcher; + private id: string; private tasks: Set; private transformDependencyTasks: Set; @@ -96,6 +97,10 @@ export default class FileWatcher { else this.tasks.add(task); } + close() { + this.fsWatcher.close(); + } + deleteTask(task: Task, group: Map) { let deleted = this.tasks.delete(task); deleted = this.transformDependencyTasks.delete(task) || deleted; @@ -106,10 +111,6 @@ export default class FileWatcher { } } - close() { - this.fsWatcher.close(); - } - trigger(id: string) { this.tasks.forEach(task => { task.invalidate(id, false); diff --git a/src/watch/index.ts b/src/watch/index.ts index b2b352d5234..fdef0188b60 100644 --- a/src/watch/index.ts +++ b/src/watch/index.ts @@ -19,12 +19,13 @@ const DELAY = 200; export class Watcher { emitter: RollupWatcher; + private buildTimeout: NodeJS.Timer; - private running: boolean; + private invalidatedIds: Set = new Set(); private rerun: boolean = false; - private tasks: Task[]; + private running: boolean; private succeeded: boolean = false; - private invalidatedIds: Set = new Set(); + private tasks: Task[]; constructor(configs: RollupWatchOptions[]) { this.emitter = new class extends EventEmitter implements RollupWatcher { @@ -44,10 +45,6 @@ export class Watcher { process.nextTick(() => this.run()); } - emit(event: string, value?: any) { - this.emitter.emit(event, value); - } - close() { if (this.buildTimeout) clearTimeout(this.buildTimeout); this.tasks.forEach(task => { @@ -57,6 +54,10 @@ export class Watcher { this.emitter.removeAllListeners(); } + emit(event: string, value?: any) { + this.emitter.emit(event, value); + } + invalidate(id?: string) { if (id) { this.invalidatedIds.add(id); @@ -112,19 +113,19 @@ export class Watcher { } export class Task { - private watcher: Watcher; - private closed: boolean; - private watched: Set; - private inputOptions: InputOptions; cache: RollupCache; watchFiles: string[]; + private chokidarOptions: WatchOptions; private chokidarOptionsHash: string; + private closed: boolean; + private filter: (id: string) => boolean; + private inputOptions: InputOptions; + private invalidated = true; private outputFiles: string[]; private outputs: OutputOptions[]; - private invalidated = true; - - private filter: (id: string) => boolean; + private watched: Set; + private watcher: Watcher; constructor(watcher: Watcher, config: RollupWatchOptions) { this.cache = null; @@ -149,8 +150,8 @@ export class Task { if (chokidarOptions) { chokidarOptions = { ...(chokidarOptions === true ? {} : chokidarOptions), - ignoreInitial: true, - disableGlobbing: true + disableGlobbing: true, + ignoreInitial: true }; } @@ -237,9 +238,9 @@ export class Task { .then((result: RollupBuild) => { this.watcher.emit('event', { code: 'BUNDLE_END', + duration: Date.now() - start, input: this.inputOptions.input, output: this.outputFiles, - duration: Date.now() - start, result }); }) diff --git a/test/misc/optionList.js b/test/misc/optionList.js index 50080676ae6..0dd93c024cd 100644 --- a/test/misc/optionList.js +++ b/test/misc/optionList.js @@ -1,3 +1,3 @@ exports.input = 'acorn, acornInjectPlugins, cache, chunkGroupingSize, context, experimentalCacheExpiry, experimentalOptimizeChunks, experimentalTopLevelAwait, external, inlineDynamicImports, input, manualChunks, moduleContext, onwarn, perf, plugins, preserveModules, preserveSymlinks, shimMissingExports, treeshake, watch'; exports.flags = 'acorn, acornInjectPlugins, amd, assetFileNames, banner, c, cache, chunkFileNames, chunkGroupingSize, compact, config, context, d, dir, e, entryFileNames, environment, esModule, experimentalCacheExpiry, experimentalOptimizeChunks, experimentalTopLevelAwait, exports, extend, external, f, file, footer, format, freeze, g, globals, h, i, indent, inlineDynamicImports, input, interop, intro, m, manualChunks, moduleContext, n, name, namespaceToStringTag, noConflict, o, onwarn, outro, paths, perf, plugins, preferConst, preserveModules, preserveSymlinks, shimMissingExports, silent, sourcemap, sourcemapExcludeSources, sourcemapFile, strict, treeshake, v, w, watch'; -exports.output = 'amd, assetFileNames, banner, dir, chunkFileNames, compact, entryFileNames, esModule, exports, extend, file, footer, format, freeze, globals, indent, interop, intro, name, namespaceToStringTag, noConflict, outro, paths, preferConst, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict'; +exports.output = 'amd, assetFileNames, banner, chunkFileNames, compact, dir, entryFileNames, esModule, exports, extend, file, footer, format, freeze, globals, indent, interop, intro, name, namespaceToStringTag, noConflict, outro, paths, preferConst, sourcemap, sourcemapExcludeSources, sourcemapFile, sourcemapPathTransform, strict'; diff --git a/tslint.json b/tslint.json index cd8418d6722..a7c778c62f2 100644 --- a/tslint.json +++ b/tslint.json @@ -3,9 +3,20 @@ "rules": { "arrow-return-shorthand": true, "interface-over-type-literal": true, + "member-ordering": [ + true, + { + "order": "statics-first", + "alphabetize": true + } + ], "no-string-literal": true, "no-unnecessary-type-assertion": true, "object-literal-shorthand": true, + "object-literal-sort-keys": [ + true, + "ignore-case" + ], "ordered-imports": true, "prefer-const": [true, { "destructuring": "all" }], "prefer-object-spread": true