/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const eslintScope = require("eslint-scope"); const Referencer = require("eslint-scope/lib/referencer"); const { SyncBailHook } = require("tapable"); const { CachedSource, ConcatSource, ReplaceSource } = require("webpack-sources"); const ConcatenationScope = require("../ConcatenationScope"); const { UsageState } = require("../ExportsInfo"); const Module = require("../Module"); const { JS_TYPES } = require("../ModuleSourceTypesConstants"); const { JAVASCRIPT_MODULE_TYPE_ESM } = require("../ModuleTypeConstants"); const RuntimeGlobals = require("../RuntimeGlobals"); const Template = require("../Template"); const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency"); const JavascriptParser = require("../javascript/JavascriptParser"); const { equals } = require("../util/ArrayHelpers"); const LazySet = require("../util/LazySet"); const { concatComparators } = require("../util/comparators"); const { RESERVED_NAMES, findNewName, addScopeSymbols, getAllReferences, getPathInAst, getUsedNamesInScopeInfo } = require("../util/concatenate"); const createHash = require("../util/createHash"); const { makePathsRelative } = require("../util/identifier"); const makeSerializable = require("../util/makeSerializable"); const propertyAccess = require("../util/propertyAccess"); const { propertyName } = require("../util/propertyName"); const { filterRuntime, intersectRuntime, mergeRuntimeCondition, mergeRuntimeConditionNonFalse, runtimeConditionToString, subtractRuntimeCondition } = require("../util/runtime"); /** @typedef {import("eslint-scope").Reference} Reference */ /** @typedef {import("eslint-scope").Scope} Scope */ /** @typedef {import("eslint-scope").Variable} Variable */ /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ /** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ /** @typedef {import("../Compilation")} Compilation */ /** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplates")} DependencyTemplates */ /** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ /** @typedef {import("../Module").BuildInfo} BuildInfo */ /** @typedef {import("../Module").BuildMeta} BuildMeta */ /** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ /** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ /** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ /** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ /** @typedef {import("../Module").SourceTypes} SourceTypes */ /** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ /** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ /** @typedef {import("../ModuleParseError")} ModuleParseError */ /** @typedef {import("../RequestShortener")} RequestShortener */ /** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ /** @typedef {import("../WebpackError")} WebpackError */ /** @typedef {import("../javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ /** @typedef {import("../javascript/JavascriptParser").Program} Program */ /** @typedef {import("../javascript/JavascriptParser").Range} Range */ /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ /** @typedef {import("../util/Hash")} Hash */ /** @typedef {typeof import("../util/Hash")} HashConstructor */ /** @typedef {import("../util/concatenate").UsedNames} UsedNames */ /** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ /** * @template T * @typedef {import("../InitFragment")} InitFragment */ /** * @template T * @typedef {import("../util/comparators").Comparator} Comparator */ // fix eslint-scope to support class properties correctly // cspell:word Referencer const ReferencerClass = /** @type {any} */ (Referencer); if (!ReferencerClass.prototype.PropertyDefinition) { ReferencerClass.prototype.PropertyDefinition = ReferencerClass.prototype.Property; } /** * @typedef {object} ReexportInfo * @property {Module} module * @property {string[]} export */ /** @typedef {RawBinding | SymbolBinding} Binding */ /** * @typedef {object} RawBinding * @property {ModuleInfo} info * @property {string} rawName * @property {string=} comment * @property {string[]} ids * @property {string[]} exportName */ /** * @typedef {object} SymbolBinding * @property {ConcatenatedModuleInfo} info * @property {string} name * @property {string=} comment * @property {string[]} ids * @property {string[]} exportName */ /** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo } ModuleInfo */ /** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo | ReferenceToModuleInfo } ModuleInfoOrReference */ /** * @typedef {object} ConcatenatedModuleInfo * @property {"concatenated"} type * @property {Module} module * @property {number} index * @property {Program | undefined} ast * @property {Source | undefined} internalSource * @property {ReplaceSource | undefined} source * @property {InitFragment[]=} chunkInitFragments * @property {ReadOnlyRuntimeRequirements | undefined} runtimeRequirements * @property {Scope | undefined} globalScope * @property {Scope | undefined} moduleScope * @property {Map} internalNames * @property {Map | undefined} exportMap * @property {Map | undefined} rawExportMap * @property {string=} namespaceExportSymbol * @property {string | undefined} namespaceObjectName * @property {boolean} interopNamespaceObjectUsed * @property {string | undefined} interopNamespaceObjectName * @property {boolean} interopNamespaceObject2Used * @property {string | undefined} interopNamespaceObject2Name * @property {boolean} interopDefaultAccessUsed * @property {string | undefined} interopDefaultAccessName */ /** * @typedef {object} ExternalModuleInfo * @property {"external"} type * @property {Module} module * @property {RuntimeSpec | boolean} runtimeCondition * @property {number} index * @property {string | undefined} name * @property {boolean} interopNamespaceObjectUsed * @property {string | undefined} interopNamespaceObjectName * @property {boolean} interopNamespaceObject2Used * @property {string | undefined} interopNamespaceObject2Name * @property {boolean} interopDefaultAccessUsed * @property {string | undefined} interopDefaultAccessName */ /** * @typedef {object} ReferenceToModuleInfo * @property {"reference"} type * @property {RuntimeSpec | boolean} runtimeCondition * @property {ModuleInfo} target */ /** * @template T * @param {string} property property * @param {function(T[keyof T], T[keyof T]): 0 | 1 | -1} comparator comparator * @returns {Comparator} comparator */ const createComparator = (property, comparator) => (a, b) => comparator( a[/** @type {keyof T} */ (property)], b[/** @type {keyof T} */ (property)] ); /** * @param {number} a a * @param {number} b b * @returns {0 | 1 | -1} result */ const compareNumbers = (a, b) => { if (Number.isNaN(a)) { if (!Number.isNaN(b)) { return 1; } } else { if (Number.isNaN(b)) { return -1; } if (a !== b) { return a < b ? -1 : 1; } } return 0; }; const bySourceOrder = createComparator("sourceOrder", compareNumbers); const byRangeStart = createComparator("rangeStart", compareNumbers); /** * @param {Iterable} iterable iterable object * @returns {string} joined iterable object */ const joinIterableWithComma = iterable => { // This is more performant than Array.from().join(", ") // as it doesn't create an array let str = ""; let first = true; for (const item of iterable) { if (first) { first = false; } else { str += ", "; } str += item; } return str; }; /** * @typedef {object} ConcatenationEntry * @property {"concatenated" | "external"} type * @property {Module} module * @property {RuntimeSpec | boolean} runtimeCondition */ /** * @param {ModuleGraph} moduleGraph the module graph * @param {ModuleInfo} info module info * @param {string[]} exportName exportName * @param {Map} moduleToInfoMap moduleToInfoMap * @param {RuntimeSpec} runtime for which runtime * @param {RequestShortener} requestShortener the request shortener * @param {RuntimeTemplate} runtimeTemplate the runtime template * @param {Set} neededNamespaceObjects modules for which a namespace object should be generated * @param {boolean} asCall asCall * @param {boolean | undefined} strictHarmonyModule strictHarmonyModule * @param {boolean | undefined} asiSafe asiSafe * @param {Set} alreadyVisited alreadyVisited * @returns {Binding} the final variable */ const getFinalBinding = ( moduleGraph, info, exportName, moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, asCall, strictHarmonyModule, asiSafe, alreadyVisited = new Set() ) => { const exportsType = info.module.getExportsType( moduleGraph, strictHarmonyModule ); if (exportName.length === 0) { switch (exportsType) { case "default-only": info.interopNamespaceObject2Used = true; return { info, rawName: /** @type {string} */ (info.interopNamespaceObject2Name), ids: exportName, exportName }; case "default-with-named": info.interopNamespaceObjectUsed = true; return { info, rawName: /** @type {string} */ (info.interopNamespaceObjectName), ids: exportName, exportName }; case "namespace": case "dynamic": break; default: throw new Error(`Unexpected exportsType ${exportsType}`); } } else { switch (exportsType) { case "namespace": break; case "default-with-named": switch (exportName[0]) { case "default": exportName = exportName.slice(1); break; case "__esModule": return { info, rawName: "/* __esModule */true", ids: exportName.slice(1), exportName }; } break; case "default-only": { const exportId = exportName[0]; if (exportId === "__esModule") { return { info, rawName: "/* __esModule */true", ids: exportName.slice(1), exportName }; } exportName = exportName.slice(1); if (exportId !== "default") { return { info, rawName: "/* non-default import from default-exporting module */undefined", ids: exportName, exportName }; } break; } case "dynamic": switch (exportName[0]) { case "default": { exportName = exportName.slice(1); info.interopDefaultAccessUsed = true; const defaultExport = asCall ? `${info.interopDefaultAccessName}()` : asiSafe ? `(${info.interopDefaultAccessName}())` : asiSafe === false ? `;(${info.interopDefaultAccessName}())` : `${info.interopDefaultAccessName}.a`; return { info, rawName: defaultExport, ids: exportName, exportName }; } case "__esModule": return { info, rawName: "/* __esModule */true", ids: exportName.slice(1), exportName }; } break; default: throw new Error(`Unexpected exportsType ${exportsType}`); } } if (exportName.length === 0) { switch (info.type) { case "concatenated": neededNamespaceObjects.add(info); return { info, rawName: /** @type {NonNullable} */ (info.namespaceObjectName), ids: exportName, exportName }; case "external": return { info, rawName: /** @type {NonNullable} */ (info.name), ids: exportName, exportName }; } } const exportsInfo = moduleGraph.getExportsInfo(info.module); const exportInfo = exportsInfo.getExportInfo(exportName[0]); if (alreadyVisited.has(exportInfo)) { return { info, rawName: "/* circular reexport */ Object(function x() { x() }())", ids: [], exportName }; } alreadyVisited.add(exportInfo); switch (info.type) { case "concatenated": { const exportId = exportName[0]; if (exportInfo.provided === false) { // It's not provided, but it could be on the prototype neededNamespaceObjects.add(info); return { info, rawName: /** @type {string} */ (info.namespaceObjectName), ids: exportName, exportName }; } const directExport = info.exportMap && info.exportMap.get(exportId); if (directExport) { const usedName = /** @type {string[]} */ ( exportsInfo.getUsedName(exportName, runtime) ); if (!usedName) { return { info, rawName: "/* unused export */ undefined", ids: exportName.slice(1), exportName }; } return { info, name: directExport, ids: usedName.slice(1), exportName }; } const rawExport = info.rawExportMap && info.rawExportMap.get(exportId); if (rawExport) { return { info, rawName: rawExport, ids: exportName.slice(1), exportName }; } const reexport = exportInfo.findTarget(moduleGraph, module => moduleToInfoMap.has(module) ); if (reexport === false) { throw new Error( `Target module of reexport from '${info.module.readableIdentifier( requestShortener )}' is not part of the concatenation (export '${exportId}')\nModules in the concatenation:\n${Array.from( moduleToInfoMap, ([m, info]) => ` * ${info.type} ${m.readableIdentifier(requestShortener)}` ).join("\n")}` ); } if (reexport) { const refInfo = moduleToInfoMap.get(reexport.module); return getFinalBinding( moduleGraph, /** @type {ModuleInfo} */ (refInfo), reexport.export ? [...reexport.export, ...exportName.slice(1)] : exportName.slice(1), moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, asCall, /** @type {BuildMeta} */ (info.module.buildMeta).strictHarmonyModule, asiSafe, alreadyVisited ); } if (info.namespaceExportSymbol) { const usedName = /** @type {string[]} */ ( exportsInfo.getUsedName(exportName, runtime) ); return { info, rawName: /** @type {string} */ (info.namespaceObjectName), ids: usedName, exportName }; } throw new Error( `Cannot get final name for export '${exportName.join( "." )}' of ${info.module.readableIdentifier(requestShortener)}` ); } case "external": { const used = /** @type {string[]} */ ( exportsInfo.getUsedName(exportName, runtime) ); if (!used) { return { info, rawName: "/* unused export */ undefined", ids: exportName.slice(1), exportName }; } const comment = equals(used, exportName) ? "" : Template.toNormalComment(`${exportName.join(".")}`); return { info, rawName: info.name + comment, ids: used, exportName }; } } }; /** * @param {ModuleGraph} moduleGraph the module graph * @param {ModuleInfo} info module info * @param {string[]} exportName exportName * @param {Map} moduleToInfoMap moduleToInfoMap * @param {RuntimeSpec} runtime for which runtime * @param {RequestShortener} requestShortener the request shortener * @param {RuntimeTemplate} runtimeTemplate the runtime template * @param {Set} neededNamespaceObjects modules for which a namespace object should be generated * @param {boolean} asCall asCall * @param {boolean | undefined} callContext callContext * @param {boolean | undefined} strictHarmonyModule strictHarmonyModule * @param {boolean | undefined} asiSafe asiSafe * @returns {string} the final name */ const getFinalName = ( moduleGraph, info, exportName, moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, asCall, callContext, strictHarmonyModule, asiSafe ) => { const binding = getFinalBinding( moduleGraph, info, exportName, moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, asCall, strictHarmonyModule, asiSafe ); { const { ids, comment } = binding; let reference; let isPropertyAccess; if ("rawName" in binding) { reference = `${binding.rawName}${comment || ""}${propertyAccess(ids)}`; isPropertyAccess = ids.length > 0; } else { const { info, name: exportId } = binding; const name = info.internalNames.get(exportId); if (!name) { throw new Error( `The export "${exportId}" in "${info.module.readableIdentifier( requestShortener )}" has no internal name (existing names: ${ Array.from( info.internalNames, ([name, symbol]) => `${name}: ${symbol}` ).join(", ") || "none" })` ); } reference = `${name}${comment || ""}${propertyAccess(ids)}`; isPropertyAccess = ids.length > 1; } if (isPropertyAccess && asCall && callContext === false) { return asiSafe ? `(0,${reference})` : asiSafe === false ? `;(0,${reference})` : `/*#__PURE__*/Object(${reference})`; } return reference; } }; /** * @typedef {object} ConcatenateModuleHooks * @property {SyncBailHook<[Record], boolean | void>} exportsDefinitions */ /** @type {WeakMap} */ const compilationHooksMap = new WeakMap(); class ConcatenatedModule extends Module { /** * @param {Module} rootModule the root module of the concatenation * @param {Set} modules all modules in the concatenation (including the root module) * @param {RuntimeSpec} runtime the runtime * @param {Compilation} compilation the compilation * @param {object=} associatedObjectForCache object for caching * @param {string | HashConstructor=} hashFunction hash function to use * @returns {ConcatenatedModule} the module */ static create( rootModule, modules, runtime, compilation, associatedObjectForCache, hashFunction = "md4" ) { const identifier = ConcatenatedModule._createIdentifier( rootModule, modules, associatedObjectForCache, hashFunction ); return new ConcatenatedModule({ identifier, rootModule, modules, runtime, compilation }); } /** * @param {Compilation} compilation the compilation * @returns {ConcatenateModuleHooks} the attached hooks */ static getCompilationHooks(compilation) { let hooks = compilationHooksMap.get(compilation); if (hooks === undefined) { hooks = { exportsDefinitions: new SyncBailHook(["definitions"]) }; compilationHooksMap.set(compilation, hooks); } return hooks; } /** * @param {object} options options * @param {string} options.identifier the identifier of the module * @param {Module} options.rootModule the root module of the concatenation * @param {RuntimeSpec} options.runtime the selected runtime * @param {Set} options.modules all concatenated modules * @param {Compilation} options.compilation the compilation */ constructor({ identifier, rootModule, modules, runtime, compilation }) { super(JAVASCRIPT_MODULE_TYPE_ESM, null, rootModule && rootModule.layer); // Info from Factory /** @type {string} */ this._identifier = identifier; /** @type {Module} */ this.rootModule = rootModule; /** @type {Set} */ this._modules = modules; this._runtime = runtime; this.factoryMeta = rootModule && rootModule.factoryMeta; /** @type {Compilation | undefined} */ this.compilation = compilation; } /** * Assuming this module is in the cache. Update the (cached) module with * the fresh module from the factory. Usually updates internal references * and properties. * @param {Module} module fresh module * @returns {void} */ updateCacheModule(module) { throw new Error("Must not be called"); } /** * @returns {SourceTypes} types available (do not mutate) */ getSourceTypes() { return JS_TYPES; } get modules() { return Array.from(this._modules); } /** * @returns {string} a unique identifier of the module */ identifier() { return this._identifier; } /** * @param {RequestShortener} requestShortener the request shortener * @returns {string} a user readable identifier of the module */ readableIdentifier(requestShortener) { return `${this.rootModule.readableIdentifier( requestShortener )} + ${this._modules.size - 1} modules`; } /** * @param {LibIdentOptions} options options * @returns {string | null} an identifier for library inclusion */ libIdent(options) { return this.rootModule.libIdent(options); } /** * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) */ nameForCondition() { return this.rootModule.nameForCondition(); } /** * @param {ModuleGraph} moduleGraph the module graph * @returns {ConnectionState} how this module should be connected to referencing modules when consumed for side-effects only */ getSideEffectsConnectionState(moduleGraph) { return this.rootModule.getSideEffectsConnectionState(moduleGraph); } /** * @param {WebpackOptions} options webpack options * @param {Compilation} compilation the compilation * @param {ResolverWithOptions} resolver the resolver * @param {InputFileSystem} fs the file system * @param {function(WebpackError=): void} callback callback function * @returns {void} */ build(options, compilation, resolver, fs, callback) { const { rootModule } = this; const { moduleArgument, exportsArgument } = /** @type {BuildInfo} */ (rootModule.buildInfo); this.buildInfo = { strict: true, cacheable: true, moduleArgument, exportsArgument, fileDependencies: new LazySet(), contextDependencies: new LazySet(), missingDependencies: new LazySet(), topLevelDeclarations: new Set(), assets: undefined }; this.buildMeta = rootModule.buildMeta; this.clearDependenciesAndBlocks(); this.clearWarningsAndErrors(); for (const m of this._modules) { // populate cacheable if (!(/** @type {BuildInfo} */ (m.buildInfo).cacheable)) { this.buildInfo.cacheable = false; } // populate dependencies for (const d of m.dependencies.filter( dep => !(dep instanceof HarmonyImportDependency) || !this._modules.has( /** @type {Module} */ (compilation.moduleGraph.getModule(dep)) ) )) { this.dependencies.push(d); } // populate blocks for (const d of m.blocks) { this.blocks.push(d); } // populate warnings const warnings = m.getWarnings(); if (warnings !== undefined) { for (const warning of warnings) { this.addWarning(warning); } } // populate errors const errors = m.getErrors(); if (errors !== undefined) { for (const error of errors) { this.addError(error); } } const { assets, assetsInfo, topLevelDeclarations } = /** @type {BuildInfo} */ (m.buildInfo); // populate topLevelDeclarations if (topLevelDeclarations) { const topLevelDeclarations = this.buildInfo.topLevelDeclarations; if (topLevelDeclarations !== undefined) { for (const decl of topLevelDeclarations) { topLevelDeclarations.add(decl); } } } else { this.buildInfo.topLevelDeclarations = undefined; } // populate assets if (assets) { if (this.buildInfo.assets === undefined) { this.buildInfo.assets = Object.create(null); } Object.assign( /** @type {NonNullable} */ ( /** @type {BuildInfo} */ (this.buildInfo).assets ), assets ); } if (assetsInfo) { if (this.buildInfo.assetsInfo === undefined) { this.buildInfo.assetsInfo = new Map(); } for (const [key, value] of assetsInfo) { this.buildInfo.assetsInfo.set(key, value); } } } callback(); } /** * @param {string=} type the source type for which the size should be estimated * @returns {number} the estimated size of the module (must be non-zero) */ size(type) { // Guess size from embedded modules let size = 0; for (const module of this._modules) { size += module.size(type); } return size; } /** * @private * @param {Module} rootModule the root of the concatenation * @param {Set} modulesSet a set of modules which should be concatenated * @param {RuntimeSpec} runtime for this runtime * @param {ModuleGraph} moduleGraph the module graph * @returns {ConcatenationEntry[]} concatenation list */ _createConcatenationList(rootModule, modulesSet, runtime, moduleGraph) { /** @type {ConcatenationEntry[]} */ const list = []; /** @type {Map} */ const existingEntries = new Map(); /** * @param {Module} module a module * @returns {Iterable<{ connection: ModuleGraphConnection, runtimeCondition: RuntimeSpec | true }>} imported modules in order */ const getConcatenatedImports = module => { const connections = Array.from( moduleGraph.getOutgoingConnections(module) ); if (module === rootModule) { for (const c of moduleGraph.getOutgoingConnections(this)) connections.push(c); } /** * @type {Array<{ connection: ModuleGraphConnection, sourceOrder: number, rangeStart: number }>} */ const references = connections .filter(connection => { if (!(connection.dependency instanceof HarmonyImportDependency)) return false; return ( connection && connection.resolvedOriginModule === module && connection.module && connection.isTargetActive(runtime) ); }) .map(connection => { const dep = /** @type {HarmonyImportDependency} */ ( connection.dependency ); return { connection, sourceOrder: dep.sourceOrder, rangeStart: dep.range && dep.range[0] }; }); /** * bySourceOrder * @example * import a from "a"; // sourceOrder=1 * import b from "b"; // sourceOrder=2 * * byRangeStart * @example * import {a, b} from "a"; // sourceOrder=1 * a.a(); // first range * b.b(); // second range * * If there is no reexport, we have the same source. * If there is reexport, but module has side effects, this will lead to reexport module only. * If there is side-effects-free reexport, we can get simple deterministic result with range start comparison. */ references.sort(concatComparators(bySourceOrder, byRangeStart)); /** @type {Map} */ const referencesMap = new Map(); for (const { connection } of references) { const runtimeCondition = filterRuntime(runtime, r => connection.isTargetActive(r) ); if (runtimeCondition === false) continue; const module = connection.module; const entry = referencesMap.get(module); if (entry === undefined) { referencesMap.set(module, { connection, runtimeCondition }); continue; } entry.runtimeCondition = mergeRuntimeConditionNonFalse( entry.runtimeCondition, runtimeCondition, runtime ); } return referencesMap.values(); }; /** * @param {ModuleGraphConnection} connection graph connection * @param {RuntimeSpec | true} runtimeCondition runtime condition * @returns {void} */ const enterModule = (connection, runtimeCondition) => { const module = connection.module; if (!module) return; const existingEntry = existingEntries.get(module); if (existingEntry === true) { return; } if (modulesSet.has(module)) { existingEntries.set(module, true); if (runtimeCondition !== true) { throw new Error( `Cannot runtime-conditional concatenate a module (${module.identifier()} in ${this.rootModule.identifier()}, ${runtimeConditionToString( runtimeCondition )}). This should not happen.` ); } const imports = getConcatenatedImports(module); for (const { connection, runtimeCondition } of imports) enterModule(connection, runtimeCondition); list.push({ type: "concatenated", module: connection.module, runtimeCondition }); } else { if (existingEntry !== undefined) { const reducedRuntimeCondition = subtractRuntimeCondition( runtimeCondition, existingEntry, runtime ); if (reducedRuntimeCondition === false) return; runtimeCondition = reducedRuntimeCondition; existingEntries.set( connection.module, mergeRuntimeConditionNonFalse( existingEntry, runtimeCondition, runtime ) ); } else { existingEntries.set(connection.module, runtimeCondition); } if (list.length > 0) { const lastItem = list[list.length - 1]; if ( lastItem.type === "external" && lastItem.module === connection.module ) { lastItem.runtimeCondition = mergeRuntimeCondition( lastItem.runtimeCondition, runtimeCondition, runtime ); return; } } list.push({ type: "external", get module() { // We need to use a getter here, because the module in the dependency // could be replaced by some other process (i. e. also replaced with a // concatenated module) return connection.module; }, runtimeCondition }); } }; existingEntries.set(rootModule, true); const imports = getConcatenatedImports(rootModule); for (const { connection, runtimeCondition } of imports) enterModule(connection, runtimeCondition); list.push({ type: "concatenated", module: rootModule, runtimeCondition: true }); return list; } /** * @param {Module} rootModule the root module of the concatenation * @param {Set} modules all modules in the concatenation (including the root module) * @param {object=} associatedObjectForCache object for caching * @param {string | HashConstructor=} hashFunction hash function to use * @returns {string} the identifier */ static _createIdentifier( rootModule, modules, associatedObjectForCache, hashFunction = "md4" ) { const cachedMakePathsRelative = makePathsRelative.bindContextCache( /** @type {string} */ (rootModule.context), associatedObjectForCache ); const identifiers = []; for (const module of modules) { identifiers.push(cachedMakePathsRelative(module.identifier())); } identifiers.sort(); const hash = createHash(hashFunction); hash.update(identifiers.join(" ")); return `${rootModule.identifier()}|${hash.digest("hex")}`; } /** * @param {LazySet} fileDependencies set where file dependencies are added to * @param {LazySet} contextDependencies set where context dependencies are added to * @param {LazySet} missingDependencies set where missing dependencies are added to * @param {LazySet} buildDependencies set where build dependencies are added to */ addCacheDependencies( fileDependencies, contextDependencies, missingDependencies, buildDependencies ) { for (const module of this._modules) { module.addCacheDependencies( fileDependencies, contextDependencies, missingDependencies, buildDependencies ); } } /** * @param {CodeGenerationContext} context context for code generation * @returns {CodeGenerationResult} result */ codeGeneration({ dependencyTemplates, runtimeTemplate, moduleGraph, chunkGraph, runtime: generationRuntime, codeGenerationResults }) { /** @type {RuntimeRequirements} */ const runtimeRequirements = new Set(); const runtime = intersectRuntime(generationRuntime, this._runtime); const requestShortener = runtimeTemplate.requestShortener; // Meta info for each module const [modulesWithInfo, moduleToInfoMap] = this._getModulesWithInfo( moduleGraph, runtime ); // Set with modules that need a generated namespace object /** @type {Set} */ const neededNamespaceObjects = new Set(); // Generate source code and analyse scopes // Prepare a ReplaceSource for the final source for (const info of moduleToInfoMap.values()) { this._analyseModule( moduleToInfoMap, info, dependencyTemplates, runtimeTemplate, moduleGraph, chunkGraph, runtime, /** @type {CodeGenerationResults} */ (codeGenerationResults) ); } // List of all used names to avoid conflicts const allUsedNames = new Set(RESERVED_NAMES); // Updated Top level declarations are created by renaming const topLevelDeclarations = new Set(); // List of additional names in scope for module references /** @type {Map }>} */ const usedNamesInScopeInfo = new Map(); /** * @param {string} module module identifier * @param {string} id export id * @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set }} info */ // Set of already checked scopes const ignoredScopes = new Set(); // get all global names for (const info of modulesWithInfo) { if (info.type === "concatenated") { // ignore symbols from moduleScope if (info.moduleScope) { ignoredScopes.add(info.moduleScope); } // The super class expression in class scopes behaves weird // We get ranges of all super class expressions to make // renaming to work correctly const superClassCache = new WeakMap(); /** * @param {Scope} scope scope * @returns {TODO} result */ const getSuperClassExpressions = scope => { const cacheEntry = superClassCache.get(scope); if (cacheEntry !== undefined) return cacheEntry; const superClassExpressions = []; for (const childScope of scope.childScopes) { if (childScope.type !== "class") continue; const block = childScope.block; if ( (block.type === "ClassDeclaration" || block.type === "ClassExpression") && block.superClass ) { superClassExpressions.push({ range: block.superClass.range, variables: childScope.variables }); } } superClassCache.set(scope, superClassExpressions); return superClassExpressions; }; // add global symbols if (info.globalScope) { for (const reference of info.globalScope.through) { const name = reference.identifier.name; if (ConcatenationScope.isModuleReference(name)) { const match = ConcatenationScope.matchModuleReference(name); if (!match) continue; const referencedInfo = modulesWithInfo[match.index]; if (referencedInfo.type === "reference") throw new Error("Module reference can't point to a reference"); const binding = getFinalBinding( moduleGraph, referencedInfo, match.ids, moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, false, /** @type {BuildMeta} */ (info.module.buildMeta).strictHarmonyModule, true ); if (!binding.ids) continue; const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo( usedNamesInScopeInfo, binding.info.module.identifier(), "name" in binding ? binding.name : "" ); for (const expr of getSuperClassExpressions(reference.from)) { if ( expr.range[0] <= /** @type {Range} */ (reference.identifier.range)[0] && expr.range[1] >= /** @type {Range} */ (reference.identifier.range)[1] ) { for (const variable of expr.variables) { usedNames.add(variable.name); } } } addScopeSymbols( reference.from, usedNames, alreadyCheckedScopes, ignoredScopes ); } else { allUsedNames.add(name); } } } } } // generate names for symbols for (const info of moduleToInfoMap.values()) { const { usedNames: namespaceObjectUsedNames } = getUsedNamesInScopeInfo( usedNamesInScopeInfo, info.module.identifier(), "" ); switch (info.type) { case "concatenated": { const variables = /** @type {Scope} */ (info.moduleScope).variables; for (const variable of variables) { const name = variable.name; const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo( usedNamesInScopeInfo, info.module.identifier(), name ); if (allUsedNames.has(name) || usedNames.has(name)) { const references = getAllReferences(variable); for (const ref of references) { addScopeSymbols( ref.from, usedNames, alreadyCheckedScopes, ignoredScopes ); } const newName = findNewName( name, allUsedNames, usedNames, info.module.readableIdentifier(requestShortener) ); allUsedNames.add(newName); info.internalNames.set(name, newName); topLevelDeclarations.add(newName); const source = /** @type {ReplaceSource} */ (info.source); const allIdentifiers = new Set( references.map(r => r.identifier).concat(variable.identifiers) ); for (const identifier of allIdentifiers) { const r = /** @type {Range} */ (identifier.range); const path = getPathInAst( /** @type {NonNullable} */ (info.ast), identifier ); if (path && path.length > 1) { const maybeProperty = path[1].type === "AssignmentPattern" && path[1].left === path[0] ? path[2] : path[1]; if ( maybeProperty.type === "Property" && maybeProperty.shorthand ) { source.insert(r[1], `: ${newName}`); continue; } } source.replace(r[0], r[1] - 1, newName); } } else { allUsedNames.add(name); info.internalNames.set(name, name); topLevelDeclarations.add(name); } } let namespaceObjectName; if (info.namespaceExportSymbol) { namespaceObjectName = info.internalNames.get( info.namespaceExportSymbol ); } else { namespaceObjectName = findNewName( "namespaceObject", allUsedNames, namespaceObjectUsedNames, info.module.readableIdentifier(requestShortener) ); allUsedNames.add(namespaceObjectName); } info.namespaceObjectName = /** @type {string} */ (namespaceObjectName); topLevelDeclarations.add(namespaceObjectName); break; } case "external": { const externalName = findNewName( "", allUsedNames, namespaceObjectUsedNames, info.module.readableIdentifier(requestShortener) ); allUsedNames.add(externalName); info.name = externalName; topLevelDeclarations.add(externalName); break; } } const buildMeta = /** @type {BuildMeta} */ (info.module.buildMeta); if (buildMeta.exportsType !== "namespace") { const externalNameInterop = findNewName( "namespaceObject", allUsedNames, namespaceObjectUsedNames, info.module.readableIdentifier(requestShortener) ); allUsedNames.add(externalNameInterop); info.interopNamespaceObjectName = externalNameInterop; topLevelDeclarations.add(externalNameInterop); } if ( buildMeta.exportsType === "default" && buildMeta.defaultObject !== "redirect" ) { const externalNameInterop = findNewName( "namespaceObject2", allUsedNames, namespaceObjectUsedNames, info.module.readableIdentifier(requestShortener) ); allUsedNames.add(externalNameInterop); info.interopNamespaceObject2Name = externalNameInterop; topLevelDeclarations.add(externalNameInterop); } if (buildMeta.exportsType === "dynamic" || !buildMeta.exportsType) { const externalNameInterop = findNewName( "default", allUsedNames, namespaceObjectUsedNames, info.module.readableIdentifier(requestShortener) ); allUsedNames.add(externalNameInterop); info.interopDefaultAccessName = externalNameInterop; topLevelDeclarations.add(externalNameInterop); } } // Find and replace references to modules for (const info of moduleToInfoMap.values()) { if (info.type === "concatenated") { const globalScope = /** @type {Scope} */ (info.globalScope); for (const reference of globalScope.through) { const name = reference.identifier.name; const match = ConcatenationScope.matchModuleReference(name); if (match) { const referencedInfo = modulesWithInfo[match.index]; if (referencedInfo.type === "reference") throw new Error("Module reference can't point to a reference"); const finalName = getFinalName( moduleGraph, referencedInfo, match.ids, moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, match.call, !match.directImport, /** @type {BuildMeta} */ (info.module.buildMeta).strictHarmonyModule, match.asiSafe ); const r = /** @type {Range} */ (reference.identifier.range); const source = /** @type {ReplaceSource} */ (info.source); // range is extended by 2 chars to cover the appended "._" source.replace(r[0], r[1] + 1, finalName); } } } } // Map with all root exposed used exports /** @type {Map} */ const exportsMap = new Map(); // Set with all root exposed unused exports /** @type {Set} */ const unusedExports = new Set(); const rootInfo = /** @type {ConcatenatedModuleInfo} */ ( moduleToInfoMap.get(this.rootModule) ); const strictHarmonyModule = /** @type {BuildMeta} */ (rootInfo.module.buildMeta).strictHarmonyModule; const exportsInfo = moduleGraph.getExportsInfo(rootInfo.module); /** @type {Record} */ const exportsFinalName = {}; for (const exportInfo of exportsInfo.orderedExports) { const name = exportInfo.name; if (exportInfo.provided === false) continue; const used = exportInfo.getUsedName(undefined, runtime); if (!used) { unusedExports.add(name); continue; } exportsMap.set(used, requestShortener => { try { const finalName = getFinalName( moduleGraph, rootInfo, [name], moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, false, false, strictHarmonyModule, true ); exportsFinalName[used] = finalName; return `/* ${ exportInfo.isReexport() ? "reexport" : "binding" } */ ${finalName}`; } catch (err) { /** @type {Error} */ (err).message += `\nwhile generating the root export '${name}' (used name: '${used}')`; throw err; } }); } const result = new ConcatSource(); // add harmony compatibility flag (must be first because of possible circular dependencies) let shouldAddHarmonyFlag = false; if ( moduleGraph.getExportsInfo(this).otherExportsInfo.getUsed(runtime) !== UsageState.Unused ) { shouldAddHarmonyFlag = true; } // define exports if (exportsMap.size > 0) { const { exportsDefinitions } = ConcatenatedModule.getCompilationHooks( /** @type {Compilation} */ (this.compilation) ); const definitions = []; for (const [key, value] of exportsMap) { definitions.push( `\n ${propertyName(key)}: ${runtimeTemplate.returningFunction( value(requestShortener) )}` ); } const shouldSkipRenderDefinitions = exportsDefinitions.call(exportsFinalName); if (!shouldSkipRenderDefinitions) { runtimeRequirements.add(RuntimeGlobals.exports); runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); if (shouldAddHarmonyFlag) { result.add("// ESM COMPAT FLAG\n"); result.add( runtimeTemplate.defineEsModuleFlagStatement({ exportsArgument: this.exportsArgument, runtimeRequirements }) ); } result.add("\n// EXPORTS\n"); result.add( `${RuntimeGlobals.definePropertyGetters}(${ this.exportsArgument }, {${definitions.join(",")}\n});\n` ); } else { /** @type {BuildMeta} */ (this.buildMeta).exportsFinalName = exportsFinalName; } } // list unused exports if (unusedExports.size > 0) { result.add( `\n// UNUSED EXPORTS: ${joinIterableWithComma(unusedExports)}\n` ); } // generate namespace objects const namespaceObjectSources = new Map(); for (const info of neededNamespaceObjects) { if (info.namespaceExportSymbol) continue; const nsObj = []; const exportsInfo = moduleGraph.getExportsInfo(info.module); for (const exportInfo of exportsInfo.orderedExports) { if (exportInfo.provided === false) continue; const usedName = exportInfo.getUsedName(undefined, runtime); if (usedName) { const finalName = getFinalName( moduleGraph, info, [exportInfo.name], moduleToInfoMap, runtime, requestShortener, runtimeTemplate, neededNamespaceObjects, false, undefined, /** @type {BuildMeta} */ (info.module.buildMeta).strictHarmonyModule, true ); nsObj.push( `\n ${propertyName(usedName)}: ${runtimeTemplate.returningFunction( finalName )}` ); } } const name = info.namespaceObjectName; const defineGetters = nsObj.length > 0 ? `${RuntimeGlobals.definePropertyGetters}(${name}, {${nsObj.join( "," )}\n});\n` : ""; if (nsObj.length > 0) runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); namespaceObjectSources.set( info, ` // NAMESPACE OBJECT: ${info.module.readableIdentifier(requestShortener)} var ${name} = {}; ${RuntimeGlobals.makeNamespaceObject}(${name}); ${defineGetters}` ); runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject); } // define required namespace objects (must be before evaluation modules) for (const info of modulesWithInfo) { if (info.type === "concatenated") { const source = namespaceObjectSources.get(info); if (!source) continue; result.add(source); } } const chunkInitFragments = []; // evaluate modules in order for (const rawInfo of modulesWithInfo) { let name; let isConditional = false; const info = rawInfo.type === "reference" ? rawInfo.target : rawInfo; switch (info.type) { case "concatenated": { result.add( `\n;// ${info.module.readableIdentifier(requestShortener)}\n` ); result.add(/** @type {ReplaceSource} */ (info.source)); if (info.chunkInitFragments) { for (const f of info.chunkInitFragments) chunkInitFragments.push(f); } if (info.runtimeRequirements) { for (const r of info.runtimeRequirements) { runtimeRequirements.add(r); } } name = info.namespaceObjectName; break; } case "external": { result.add( `\n// EXTERNAL MODULE: ${info.module.readableIdentifier( requestShortener )}\n` ); runtimeRequirements.add(RuntimeGlobals.require); const { runtimeCondition } = /** @type {ExternalModuleInfo | ReferenceToModuleInfo} */ (rawInfo); const condition = runtimeTemplate.runtimeConditionExpression({ chunkGraph, runtimeCondition, runtime, runtimeRequirements }); if (condition !== "true") { isConditional = true; result.add(`if (${condition}) {\n`); } result.add( `var ${info.name} = ${RuntimeGlobals.require}(${JSON.stringify( chunkGraph.getModuleId(info.module) )});` ); name = info.name; break; } default: // @ts-expect-error never is expected here throw new Error(`Unsupported concatenation entry type ${info.type}`); } if (info.interopNamespaceObjectUsed) { runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); result.add( `\nvar ${info.interopNamespaceObjectName} = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${name}, 2);` ); } if (info.interopNamespaceObject2Used) { runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); result.add( `\nvar ${info.interopNamespaceObject2Name} = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${name});` ); } if (info.interopDefaultAccessUsed) { runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport); result.add( `\nvar ${info.interopDefaultAccessName} = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${name});` ); } if (isConditional) { result.add("\n}"); } } const data = new Map(); if (chunkInitFragments.length > 0) data.set("chunkInitFragments", chunkInitFragments); data.set("topLevelDeclarations", topLevelDeclarations); /** @type {CodeGenerationResult} */ const resultEntry = { sources: new Map([["javascript", new CachedSource(result)]]), data, runtimeRequirements }; return resultEntry; } /** * @param {Map} modulesMap modulesMap * @param {ModuleInfo} info info * @param {DependencyTemplates} dependencyTemplates dependencyTemplates * @param {RuntimeTemplate} runtimeTemplate runtimeTemplate * @param {ModuleGraph} moduleGraph moduleGraph * @param {ChunkGraph} chunkGraph chunkGraph * @param {RuntimeSpec} runtime runtime * @param {CodeGenerationResults} codeGenerationResults codeGenerationResults */ _analyseModule( modulesMap, info, dependencyTemplates, runtimeTemplate, moduleGraph, chunkGraph, runtime, codeGenerationResults ) { if (info.type === "concatenated") { const m = info.module; try { // Create a concatenation scope to track and capture information const concatenationScope = new ConcatenationScope(modulesMap, info); // TODO cache codeGeneration results const codeGenResult = m.codeGeneration({ dependencyTemplates, runtimeTemplate, moduleGraph, chunkGraph, runtime, concatenationScope, codeGenerationResults, sourceTypes: JS_TYPES }); const source = /** @type {Source} */ ( codeGenResult.sources.get("javascript") ); const data = codeGenResult.data; const chunkInitFragments = data && data.get("chunkInitFragments"); const code = source.source().toString(); let ast; try { ast = JavascriptParser._parse(code, { sourceType: "module" }); } catch (_err) { const err = /** @type {TODO} */ (_err); if ( err.loc && typeof err.loc === "object" && typeof err.loc.line === "number" ) { const lineNumber = err.loc.line; const lines = code.split("\n"); err.message += `\n| ${lines .slice(Math.max(0, lineNumber - 3), lineNumber + 2) .join("\n| ")}`; } throw err; } const scopeManager = eslintScope.analyze(ast, { ecmaVersion: 6, sourceType: "module", optimistic: true, ignoreEval: true, impliedStrict: true }); const globalScope = /** @type {Scope} */ (scopeManager.acquire(ast)); const moduleScope = globalScope.childScopes[0]; const resultSource = new ReplaceSource(source); info.runtimeRequirements = /** @type {ReadOnlyRuntimeRequirements} */ (codeGenResult.runtimeRequirements); info.ast = ast; info.internalSource = source; info.source = resultSource; info.chunkInitFragments = chunkInitFragments; info.globalScope = globalScope; info.moduleScope = moduleScope; } catch (err) { /** @type {Error} */ (err).message += `\nwhile analyzing module ${m.identifier()} for concatenation`; throw err; } } } /** * @param {ModuleGraph} moduleGraph the module graph * @param {RuntimeSpec} runtime the runtime * @returns {[ModuleInfoOrReference[], Map]} module info items */ _getModulesWithInfo(moduleGraph, runtime) { const orderedConcatenationList = this._createConcatenationList( this.rootModule, this._modules, runtime, moduleGraph ); /** @type {Map} */ const map = new Map(); const list = orderedConcatenationList.map((info, index) => { let item = map.get(info.module); if (item === undefined) { switch (info.type) { case "concatenated": item = { type: "concatenated", module: info.module, index, ast: undefined, internalSource: undefined, runtimeRequirements: undefined, source: undefined, globalScope: undefined, moduleScope: undefined, internalNames: new Map(), exportMap: undefined, rawExportMap: undefined, namespaceExportSymbol: undefined, namespaceObjectName: undefined, interopNamespaceObjectUsed: false, interopNamespaceObjectName: undefined, interopNamespaceObject2Used: false, interopNamespaceObject2Name: undefined, interopDefaultAccessUsed: false, interopDefaultAccessName: undefined }; break; case "external": item = { type: "external", module: info.module, runtimeCondition: info.runtimeCondition, index, name: undefined, interopNamespaceObjectUsed: false, interopNamespaceObjectName: undefined, interopNamespaceObject2Used: false, interopNamespaceObject2Name: undefined, interopDefaultAccessUsed: false, interopDefaultAccessName: undefined }; break; default: throw new Error( `Unsupported concatenation entry type ${info.type}` ); } map.set( /** @type {ModuleInfo} */ (item).module, /** @type {ModuleInfo} */ (item) ); return /** @type {ModuleInfo} */ (item); } /** @type {ReferenceToModuleInfo} */ const ref = { type: "reference", runtimeCondition: info.runtimeCondition, target: item }; return ref; }); return [list, map]; } /** * @param {Hash} hash the hash used to track dependencies * @param {UpdateHashContext} context context * @returns {void} */ updateHash(hash, context) { const { chunkGraph, runtime } = context; for (const info of this._createConcatenationList( this.rootModule, this._modules, intersectRuntime(runtime, this._runtime), chunkGraph.moduleGraph )) { switch (info.type) { case "concatenated": info.module.updateHash(hash, context); break; case "external": hash.update(`${chunkGraph.getModuleId(info.module)}`); // TODO runtimeCondition break; } } super.updateHash(hash, context); } /** * @param {ObjectDeserializerContext} context context * @returns {ConcatenatedModule} ConcatenatedModule */ static deserialize(context) { const obj = new ConcatenatedModule({ identifier: /** @type {EXPECTED_ANY} */ (undefined), rootModule: /** @type {EXPECTED_ANY} */ (undefined), modules: /** @type {EXPECTED_ANY} */ (undefined), runtime: undefined, compilation: /** @type {EXPECTED_ANY} */ (undefined) }); obj.deserialize(context); return obj; } } makeSerializable(ConcatenatedModule, "webpack/lib/optimize/ConcatenatedModule"); module.exports = ConcatenatedModule;