webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
1,545 lines (1,469 loc) • 88.4 kB
JavaScript
/*
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 Dependency = require("../Dependency");
const { UsageState } = require("../ExportsInfo");
const Module = require("../Module");
const {
JAVASCRIPT_TYPE,
JAVASCRIPT_TYPES
} = require("../ModuleSourceTypeConstants");
const { JAVASCRIPT_MODULE_TYPE_ESM } = require("../ModuleTypeConstants");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const { DEFAULTS } = require("../config/defaults");
const { ImportPhaseUtils } = require("../dependencies/ImportPhase");
const { isCommonJsWrapped } = require("../javascript/JavascriptGenerator");
const JavascriptParser = require("../javascript/JavascriptParser");
const {
getDeferredCycleModuleIds,
getDeferredCycleModules,
getMakeDeferredNamespaceModeFromExportsType,
getOptimizedDeferredModule
} = require("../runtime/MakeDeferredNamespaceObjectRuntime");
const { equals } = require("../util/ArrayHelpers");
const LazySet = require("../util/LazySet");
const { concatComparators } = require("../util/comparators");
const {
RESERVED_NAMES,
addScopeSymbols,
findNewName,
getAllReferences,
getPathInAst,
getUsedNamesInScopeInfo
} = require("../util/concatenate");
const createHash = require("../util/createHash");
const createHooksRegistry = require("../util/createHooksRegistry");
const { makePathsRelative } = require("../util/identifier");
const makeSerializable = require("../util/makeSerializable");
const { propertyAccess, propertyName } = require("../util/property");
const {
filterRuntime,
intersectRuntime,
mergeRuntimeCondition,
mergeRuntimeConditionNonFalse,
runtimeConditionToString,
subtractRuntimeCondition
} = require("../util/runtime");
const { InlinedUsedName } = require("./InlineExports");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../dependencies/ModuleDependency")} ModuleDependency */
/** @typedef {import("../dependencies/HarmonyImportDependency")} HarmonyImportDependency */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
/** @typedef {import("../Module").BuildCallback} BuildCallback */
/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").FileSystemDependencies} FileSystemDependencies */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/** @typedef {import("../Module").ExportsType} ExportsType */
/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */
/** @typedef {import("../Module").CodeGenerationResultData} CodeGenerationResultData */
/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */
/** @typedef {import("../Module").LibIdent} LibIdent */
/** @typedef {import("../Module").NameForCondition} NameForCondition */
/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
/** @typedef {import("../Module").SourceTypes} SourceTypes */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
/** @typedef {import("../RequestShortener")} RequestShortener */
/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("../javascript/JavascriptModule").JavascriptModuleBuildInfo} JavascriptModuleBuildInfo */
/** @typedef {import("../javascript/JavascriptModule").JavascriptModuleBuildMeta} JavascriptModuleBuildMeta */
/** @typedef {import("../javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */
/** @typedef {import("../javascript/JavascriptModulesPlugin").Scope} Scope */
/** @typedef {import("../javascript/JavascriptModulesPlugin").Reference} Reference */
/** @typedef {import("../javascript/JavascriptModulesPlugin").Variable} Variable */
/** @typedef {import("../javascript/JavascriptParser").Program} Program */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("estree").Identifier} Identifier */
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
/** @typedef {import("../util/Hash")} Hash */
/** @typedef {import("../util/Hash").HashFunction} HashFunction */
/** @typedef {import("../util/concatenate").UsedNames} UsedNames */
/** @typedef {import("../util/concatenate").UsedNamesInScopeInfo} UsedNamesInScopeInfo */
/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("../util/identifier").AssociatedObjectForCache} AssociatedObjectForCache */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/**
* @template T
* @typedef {import("../InitFragment")<T>} InitFragment
*/
/**
* @template T
* @typedef {import("../util/comparators").Comparator<T>} Comparator
*/
// fix eslint-scope to support class properties correctly
// cspell:word Referencer
const ReferencerClass = Referencer;
if (!ReferencerClass.prototype.PropertyDefinition) {
ReferencerClass.prototype.PropertyDefinition =
ReferencerClass.prototype.Property;
}
/** @typedef {RawBinding | SymbolBinding} Binding */
/** @typedef {string[]} ExportName */
/**
* @typedef {object} RawBinding
* @property {ModuleInfo} info
* @property {string} rawName
* @property {string=} comment
* @property {ExportName} ids
* @property {ExportName} exportName
*/
/**
* @typedef {object} SymbolBinding
* @property {ConcatenatedModuleInfo} info
* @property {string} name
* @property {string=} comment
* @property {ExportName} ids
* @property {ExportName} exportName
*/
/** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo} ModuleInfo */
/** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo | ReferenceToModuleInfo} ModuleInfoOrReference */
/** @typedef {Map<string, string>} ExportMap */
/**
* @typedef {object} ConcatenatedModuleInfo
* @property {"concatenated"} type
* @property {Module} module
* @property {number} index
* @property {boolean=} cjsWrapped a "weird" CommonJS module executed via the CJS wrapper runtime helper with real module/exports objects
* @property {Program | undefined} ast
* @property {Source | undefined} internalSource
* @property {ReplaceSource | undefined} source
* @property {InitFragment<ChunkRenderContext>[]=} chunkInitFragments
* @property {ReadOnlyRuntimeRequirements | undefined} runtimeRequirements
* @property {Scope | undefined} globalScope
* @property {Scope | undefined} moduleScope
* @property {Map<string, string>} internalNames
* @property {ExportMap | undefined} exportMap
* @property {ExportMap | undefined} rawExportMap
* @property {string=} namespaceExportSymbol
* @property {string | undefined} namespaceObjectName
* @property {string | undefined} escapeNamespaceObjectName decoupled namespace object that keeps original export names when the exports are mangled
* @property {ConcatenationScope | undefined} concatenationScope
* @property {boolean} interopNamespaceObjectUsed "default-with-named" namespace
* @property {string | undefined} interopNamespaceObjectName "default-with-named" namespace
* @property {boolean} interopNamespaceObject2Used "default-only" namespace
* @property {string | undefined} interopNamespaceObject2Name "default-only" namespace
* @property {boolean} interopDefaultAccessUsed runtime namespace object that detects "__esModule"
* @property {string | undefined} interopDefaultAccessName runtime namespace object that detects "__esModule"
* @property {ExportsType=} exportsTypeStrict memoized getExportsType(strict=true)
* @property {ExportsType=} exportsTypeNonStrict memoized getExportsType(strict=false)
*/
/**
* @typedef {object} ExternalModuleInfo
* @property {"external"} type
* @property {Module} module
* @property {RuntimeSpec | boolean} runtimeCondition
* @property {NonDeferAccess} nonDeferAccess
* @property {number} index
* @property {string | undefined} name module.exports / harmony namespace object
* @property {string | undefined} escapeNamespaceObjectName decoupled namespace object that keeps original export names when the exports are mangled
* @property {string | undefined} deferredName deferred module.exports / harmony namespace object
* @property {boolean} deferred the module is deferred at least once
* @property {boolean} deferredNamespaceObjectUsed deferred namespace object that being used in a not-analyzable way so it must be materialized
* @property {string | undefined} deferredNamespaceObjectName deferred namespace object that being used in a not-analyzable way so it must be materialized
* @property {boolean} interopNamespaceObjectUsed "default-with-named" namespace
* @property {string | undefined} interopNamespaceObjectName "default-with-named" namespace
* @property {boolean} interopNamespaceObject2Used "default-only" namespace
* @property {string | undefined} interopNamespaceObject2Name "default-only" namespace
* @property {boolean} interopDefaultAccessUsed runtime namespace object that detects "__esModule"
* @property {string | undefined} interopDefaultAccessName runtime namespace object that detects "__esModule"
* @property {ExportsType=} exportsTypeStrict memoized getExportsType(strict=true)
* @property {ExportsType=} exportsTypeNonStrict memoized getExportsType(strict=false)
*/
/**
* @typedef {object} ReferenceToModuleInfo
* @property {"reference"} type
* @property {RuntimeSpec | boolean} runtimeCondition
* @property {NonDeferAccess} nonDeferAccess
* @property {ModuleInfo} target
*/
/**
* @template T
* @param {string} property property
* @param {(a: T[keyof T], b: T[keyof T]) => 0 | 1 | -1} comparator comparator
* @returns {Comparator<T>} 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<string>} 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 {boolean} NonDeferAccess */
/**
* @param {NonDeferAccess} a a
* @param {NonDeferAccess} b b
* @returns {NonDeferAccess} merged
*/
const mergeNonDeferAccess = (a, b) => a || b;
/**
* @param {NonDeferAccess} a first
* @param {NonDeferAccess} b second
* @returns {NonDeferAccess} first - second
*/
const subtractNonDeferAccess = (a, b) => a && !b;
/**
* @typedef {object} ConcatenationEntry
* @property {"concatenated" | "external"} type
* @property {Module} module
* @property {RuntimeSpec | boolean} runtimeCondition
* @property {NonDeferAccess} nonDeferAccess
*/
/** @typedef {Set<ConcatenatedModuleInfo>} NeededNamespaceObjects */
/** @typedef {Map<Module, ModuleInfo>} ModuleToInfoMap */
/**
* getExportsType memoized on the info, which lives for one codeGeneration.
* The "dynamic" case walks the module graph, and is queried once per reference.
* @param {ModuleGraph} moduleGraph the module graph
* @param {ModuleInfo} info module info
* @param {boolean | undefined} strict strict harmony module (undefined is treated as non-strict)
* @returns {ExportsType} the exports type
*/
const getExportsType = (moduleGraph, info, strict) => {
if (strict) {
if (info.exportsTypeStrict === undefined) {
info.exportsTypeStrict = info.module.getExportsType(moduleGraph, true);
}
return info.exportsTypeStrict;
}
if (info.exportsTypeNonStrict === undefined) {
info.exportsTypeNonStrict = info.module.getExportsType(moduleGraph, false);
}
return info.exportsTypeNonStrict;
};
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {ModuleInfo} info module info
* @param {ExportName} exportName exportName
* @param {ModuleToInfoMap} moduleToInfoMap moduleToInfoMap
* @param {RuntimeSpec} runtime for which runtime
* @param {RequestShortener} requestShortener the request shortener
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @param {NeededNamespaceObjects} neededNamespaceObjects modules for which a namespace object should be generated
* @param {boolean} asCall asCall
* @param {boolean} depDeferred the dependency is deferred
* @param {boolean | undefined} strictHarmonyModule strictHarmonyModule
* @param {boolean | undefined} asiSafe asiSafe
* @param {Set<ExportInfo>=} alreadyVisited alreadyVisited
* @returns {Binding} the final variable
*/
const getFinalBinding = (
moduleGraph,
info,
exportName,
moduleToInfoMap,
runtime,
requestShortener,
runtimeTemplate,
neededNamespaceObjects,
asCall,
depDeferred,
strictHarmonyModule,
asiSafe,
alreadyVisited
) => {
const exportsType = getExportsType(moduleGraph, info, strictHarmonyModule);
const moduleDeferred =
info.type === "external" &&
info.deferred &&
!(/** @type {BuildMeta} */ (info.module.buildMeta).async);
const deferred = depDeferred && moduleDeferred;
if (exportName.length === 0) {
switch (exportsType) {
case "default-only":
// a concatenated CommonJS module has no runtime exports object;
// its fake namespace is built over the generated namespace object
if (info.type === "concatenated") neededNamespaceObjects.add(info);
if (deferred) info.deferredNamespaceObjectUsed = true;
else info.interopNamespaceObject2Used = true;
return {
info,
rawName: /** @type {string} */ (
deferred
? info.deferredNamespaceObjectName
: info.interopNamespaceObject2Name
),
ids: exportName,
exportName
};
case "default-with-named":
if (info.type === "concatenated") neededNamespaceObjects.add(info);
if (deferred) info.deferredNamespaceObjectUsed = true;
else info.interopNamespaceObjectUsed = true;
return {
info,
rawName: /** @type {string} */ (
deferred
? info.deferredNamespaceObjectName
: 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);
if (deferred) {
// `ns.default` for a deferred default-with-named external
// module must read through the optimized `.a` getter
// (which lazily evaluates the module and returns its
// exports), not the proxy namespace itself — otherwise
// `typeof ns.default` / `ns.default instanceof X`
// observe the proxy instead of the actual default.
return {
info,
rawName: `${info.deferredName}.a`,
ids: exportName,
exportName
};
}
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
};
}
if (deferred) {
// As with default-with-named above, `ns.default` for a
// deferred default-only external must read through the
// optimized `.a` getter so that `typeof` / `instanceof`
// observe the actual default value rather than the proxy.
return {
info,
rawName: `${info.deferredName}.a`,
ids: exportName,
exportName
};
}
break;
}
case "dynamic":
switch (exportName[0]) {
case "default": {
exportName = exportName.slice(1);
if (deferred) {
return {
info,
rawName: `${info.deferredName}.a`,
ids: exportName,
exportName
};
}
if (moduleDeferred) {
return {
info,
rawName: /** @type {string} */ (info.name),
ids: exportName,
exportName
};
}
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<ConcatenatedModuleInfo["namespaceObjectName"]>} */
(info.namespaceObjectName),
ids: exportName,
exportName
};
case "external":
if (deferred) {
info.deferredNamespaceObjectUsed = true;
return {
info,
rawName: /** @type {string} */ (info.deferredNamespaceObjectName),
ids: exportName,
exportName
};
}
return {
info,
rawName:
/** @type {NonNullable<ExternalModuleInfo["name"]>} */
(info.name),
ids: exportName,
exportName
};
}
}
const exportsInfo = moduleGraph.getExportsInfo(info.module);
const exportInfo = exportsInfo.getExportInfo(exportName[0]);
// Lazily allocate: only the reexport-following recursion below needs it,
// most calls return before reaching this point.
if (alreadyVisited === undefined) alreadyVisited = new Set();
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 = exportsInfo.getUsedName(exportName, runtime);
if (!usedName) {
return {
info,
rawName: "/* unused export */ undefined",
ids: exportName.slice(1),
exportName
};
}
if (usedName instanceof InlinedUsedName) {
return {
info,
// Render the inlined literal only (e.g. `"str"`), not its property
// suffix: that suffix is returned in `ids` and appended once by
// getFinalName. Using `usedName.render()` would emit it here too,
// duplicating the access (e.g. `"str".a.a`).
rawName: usedName.render(
Template.toNormalComment(
`inlined export ${propertyAccess(exportName)}`
)
),
ids: usedName.suffix,
exportName
};
}
return {
info,
name: directExport,
ids: /** @type {ExportName} */ (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) {
// Source module was removed because all its exports were inlined;
// we render the inlined value here instead of binding to the now-absent module.
const target = exportInfo.getTarget(moduleGraph);
if (target && target.export) {
const usedName = moduleGraph
.getExportsInfo(target.module)
.getUsedName([...target.export, ...exportName.slice(1)], runtime);
if (usedName instanceof InlinedUsedName) {
return {
info,
// Literal only; suffix is appended once via `ids` (see directExport branch).
rawName: usedName.render(
Template.toNormalComment(
`inlined export ${propertyAccess(exportName)}`
)
),
ids: usedName.suffix,
exportName
};
}
}
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,
reexport.deferred,
/** @type {BuildMeta} */
(info.module.buildMeta).strictHarmonyModule,
asiSafe,
alreadyVisited
);
}
// A wrapped module resolves every export through its live exports
// alias (namespaceObjectName), same as namespaceExportSymbol modules.
if (info.namespaceExportSymbol || info.cjsWrapped) {
const usedName = /** @type {ExportName} */ (
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 = exportsInfo.getUsedName(exportName, runtime);
if (!used) {
return {
info,
rawName: "/* unused export */ undefined",
ids: exportName.slice(1),
exportName
};
}
if (used instanceof InlinedUsedName) {
return {
info,
// Literal only; suffix is appended once via `ids` (see directExport branch).
rawName: used.render(
Template.toNormalComment(
`inlined export ${propertyAccess(exportName)}`
)
),
ids: used.suffix,
exportName
};
}
const usedName = /** @type {ExportName} */ (used);
const comment = equals(usedName, exportName)
? ""
: Template.toNormalComment(`${exportName.join(".")}`);
return {
info,
rawName:
(deferred ? info.deferredName : info.name) +
(deferred ? ".a" : "") +
comment,
ids: usedName,
exportName
};
}
}
};
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {ModuleInfo} info module info
* @param {ExportName} exportName exportName
* @param {ModuleToInfoMap} moduleToInfoMap moduleToInfoMap
* @param {RuntimeSpec} runtime for which runtime
* @param {RequestShortener} requestShortener the request shortener
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @param {NeededNamespaceObjects} neededNamespaceObjects modules for which a namespace object should be generated
* @param {boolean} asCall asCall
* @param {boolean} depDeferred the dependency is deferred
* @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,
depDeferred,
callContext,
strictHarmonyModule,
asiSafe
) => {
const binding = getFinalBinding(
moduleGraph,
info,
exportName,
moduleToInfoMap,
runtime,
requestShortener,
runtimeTemplate,
neededNamespaceObjects,
asCall,
depDeferred,
strictHarmonyModule,
asiSafe
);
{
const { ids, comment } = binding;
/** @type {string} */
let reference;
/** @type {boolean} */
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<[ConcatenatedModule, RuntimeSpec[], string, Record<string, string>], boolean>} onDemandExportsGeneration
* @property {SyncBailHook<[Partial<ConcatenatedModuleInfo>, ConcatenatedModuleInfo], boolean | void>} concatenatedModuleInfo
*/
/** @typedef {BuildInfo["topLevelDeclarations"]} TopLevelDeclarations */
/**
* Defines the build info properties specific to concatenated modules.
* @typedef {object} KnownConcatenatedModuleBuildInfo
* @property {FileSystemDependencies=} fileDependencies
* @property {FileSystemDependencies=} contextDependencies
* @property {FileSystemDependencies=} missingDependencies
* @property {boolean=} needCreateRequire collected from the inner modules
* @property {boolean=} inlineExports taken over from the root module
*/
/** @typedef {BuildInfo & KnownConcatenatedModuleBuildInfo} ConcatenatedModuleBuildInfo */
class ConcatenatedModule extends Module {
/**
* @param {Module} rootModule the root module of the concatenation
* @param {Set<Module>} modules all modules in the concatenation (including the root module)
* @param {RuntimeSpec} runtime the runtime
* @param {Compilation} compilation the compilation
* @param {AssociatedObjectForCache=} associatedObjectForCache object for caching
* @param {HashFunction=} hashFunction hash function to use
* @returns {ConcatenatedModule} the module
*/
static create(
rootModule,
modules,
runtime,
compilation,
associatedObjectForCache,
hashFunction = DEFAULTS.HASH_FUNCTION
) {
const identifier = ConcatenatedModule._createIdentifier(
rootModule,
modules,
associatedObjectForCache,
hashFunction
);
return new ConcatenatedModule({
identifier,
rootModule,
modules,
runtime,
compilation
});
}
/**
* @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<Module>} 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);
// Redeclared with the concatenated module specific shape
/** @type {ConcatenatedModuleBuildInfo | undefined} */
this.buildInfo = undefined;
/** @type {JavascriptModuleBuildMeta | undefined} */
this.buildMeta = undefined;
// Info from Factory
/** @type {string} */
this._identifier = identifier;
/** @type {Module} */
this.rootModule = rootModule;
/** @type {Set<Module>} */
this._modules = modules;
/** @type {RuntimeSpec} */
this._runtime = runtime;
this.factoryMeta = rootModule && rootModule.factoryMeta;
/** @type {Compilation} */
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 the source types this module can generate.
* @returns {SourceTypes} types available (do not mutate)
*/
getSourceTypes() {
return JAVASCRIPT_TYPES;
}
get modules() {
return [...this._modules];
}
/**
* Returns the unique identifier used to reference this module.
* @returns {string} a unique identifier of the module
*/
identifier() {
return this._identifier;
}
/**
* Returns a human-readable identifier for this module.
* @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`;
}
/**
* Gets the library identifier.
* @param {LibIdentOptions} options options
* @returns {LibIdent | null} an identifier for library inclusion
*/
libIdent(options) {
return this.rootModule.libIdent(options);
}
/**
* Returns the path used when matching this module against rule conditions.
* @returns {NameForCondition | null} absolute path which should be used for condition matching (usually the resource path)
*/
nameForCondition() {
return this.rootModule.nameForCondition();
}
/**
* Gets side effects connection state.
* @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);
}
/**
* Builds the module using the provided compilation context.
* @param {WebpackOptions} options webpack options
* @param {Compilation} compilation the compilation
* @param {ResolverWithOptions} resolver the resolver
* @param {InputFileSystem} fs the file system
* @param {BuildCallback} callback callback function
* @returns {void}
*/
build(options, compilation, resolver, fs, callback) {
const { rootModule } = this;
const { moduleArgument, exportsArgument } =
/** @type {BuildInfo} */
(rootModule.buildInfo);
/** @type {ConcatenatedModuleBuildInfo} */
this.buildInfo = {
strict: true,
cacheable: true,
moduleArgument,
exportsArgument,
fileDependencies: new LazySet(),
contextDependencies: new LazySet(),
missingDependencies: new LazySet(),
topLevelDeclarations: new Set(),
assets: undefined,
inlineExports: /** @type {JavascriptModuleBuildInfo} */ (
rootModule.buildInfo
).inlineExports
};
this.buildMeta = rootModule.buildMeta;
this.clearDependenciesAndBlocks();
this.clearWarningsAndErrors();
for (const m of this._modules) {
// populate cacheable
const { cacheable, notCacheableReasons } = /** @type {BuildInfo} */ (
m.buildInfo
);
if (!cacheable) {
this.buildInfo.cacheable = false;
if (notCacheableReasons) {
const reasons =
this.buildInfo.notCacheableReasons ||
(this.buildInfo.notCacheableReasons = []);
for (const reason of notCacheableReasons) {
if (!reasons.includes(reason)) reasons.push(reason);
}
}
}
// populate dependencies — keep only deps that leave the concat set
for (const d of m.dependencies) {
if (
!Dependency.canConcatenate(d) ||
!this._modules.has(
/** @type {Module} */
(compilation.moduleGraph.getModule(d))
)
) {
this.dependencies.push(d);
}
}
// populate codeGenerationDependencies — the inner modules'
// templates are applied during ConcatenatedModule.codeGeneration,
// so the referenced modules must have been code-generated by then.
// Skip references that point back into the concat set itself.
if (m.codeGenerationDependencies !== undefined) {
for (const d of m.codeGenerationDependencies) {
const referenced =
/** @type {Module} */
(compilation.moduleGraph.getModule(d));
if (!this._modules.has(referenced)) {
this.addCodeGenerationDependency(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, needCreateRequire } =
/** @type {JavascriptModuleBuildInfo} */ (m.buildInfo);
const buildInfo = this.buildInfo;
// populate topLevelDeclarations
if (topLevelDeclarations) {
const mergedTopLevelDeclarations = buildInfo.topLevelDeclarations;
if (mergedTopLevelDeclarations !== undefined) {
for (const decl of topLevelDeclarations) {
mergedTopLevelDeclarations.add(decl);
}
}
} else {
buildInfo.topLevelDeclarations = undefined;
}
// populate needCreateRequire
if (needCreateRequire) {
this.buildInfo.needCreateRequire = true;
}
// populate assets
if (assets) {
if (buildInfo.assets === undefined) {
buildInfo.assets = Object.create(null);
}
Object.assign(
/** @type {NonNullable<BuildInfo["assets"]>} */
(buildInfo.assets),
assets
);
}
if (assetsInfo) {
if (buildInfo.assetsInfo === undefined) {
buildInfo.assetsInfo = new Map();
}
for (const [key, value] of assetsInfo) {
buildInfo.assetsInfo.set(key, value);
}
}
}
callback();
}
/**
* Returns the estimated size for the requested source type.
* @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<Module>} 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<Module, { runtimeCondition: RuntimeSpec | true, nonDeferAccess: NonDeferAccess }>} */
const existingEntries = new Map();
/**
* @param {Module} module a module
* @returns {Iterable<{ connection: ModuleGraphConnection, runtimeCondition: RuntimeSpec | true, nonDeferAccess: NonDeferAccess }>} imported modules in order
*/
const getConcatenatedImports = (module) => {
const connections = [...moduleGraph.getOutgoingConnections(module)];
if (module === rootModule) {
for (const c of moduleGraph.getOutgoingConnections(this)) {
connections.push(c);
}
}
/**
* @type {{ connection: ModuleGraphConnection, sourceOrder: number, rangeStart: number | undefined, defer?: boolean }[]}
*/
const references = connections
.filter((connection) => {
if (
!connection.dependency ||
!Dependency.canConcatenate(connection.dependency)
) {
return false;
}
if (
!Module.getSourceBasicTypes(connection.module).has(JAVASCRIPT_TYPE)
) {
return false;
}
return (
connection &&
connection.resolvedOriginModule === module &&
connection.module &&
connection.isTargetActive(runtime)
);
})
.map((connection) => {
const dep =
/** @type {HarmonyImportDependency} */
(connection.dependency);
return {
connection,
sourceOrder: /** @type {number} */ (dep.sourceOrder),
rangeStart: dep.range && dep.range[0],
defer: ImportPhaseUtils.isDefer(dep.phase)
};
});
/**
* 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<Module, { connection: ModuleGraphConnection, runtimeCondition: RuntimeSpec | true, nonDeferAccess: NonDeferAccess }>} */
const referencesMap = new Map();
for (const { connection, defer } of references) {
const runtimeCondition = filterRuntime(runtime, (r) =>
connection.isTargetActive(r)
);
if (runtimeCondition === false) continue;
const nonDeferAccess = !defer;
const module = connection.module;
const entry = referencesMap.get(module);
if (entry === undefined) {
referencesMap.set(module, {
connection,
runtimeCondition,
nonDeferAccess
});
continue;
}
entry.runtimeCondition = mergeRuntimeConditionNonFalse(
entry.runtimeCondition,
runtimeCondition,
runtime
);
entry.nonDeferAccess = mergeNonDeferAccess(
entry.nonDeferAccess,
nonDeferAccess
);
}
return referencesMap.values();
};
/**
* @param {ModuleGraphConnection} connection graph connection
* @param {RuntimeSpec | true} runtimeCondition runtime condition
* @param {NonDeferAccess} nonDeferAccess non-defer access
* @returns {void}
*/
const enterModule = (connection, runtimeCondition, nonDeferAccess) => {
const module = connection.module;
if (!module) return;
const existingEntry = existingEntries.get(module);
if (
existingEntry &&
existingEntry.runtimeCondition === true &&
existingEntry.nonDeferAccess === true
) {
return;
}
if (modulesSet.has(module)) {
existingEntries.set(module, {
runtimeCondition: true,
nonDeferAccess: 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.`
);
}
if (nonDeferAccess !== true) {
throw new Error(
`Cannot deferred concatenate a module (${module.identifier()} in ${this.rootModule.identifier()}. This should not happen.`
);
}
const imports = getConcatenatedImports(module);
for (const {
connection,
runtimeCondition,
nonDeferAccess
} of imports) {
enterModule(connection, runtimeCondition, nonDeferAccess);
}
list.push({
type: "concatenated",
module: connection.module,
runtimeCondition,
nonDeferAccess
});
} else {
/** @type {RuntimeSpec | boolean} */
let reducedRuntimeCondition;
/** @type {NonDeferAccess} */
let reducedNonDeferAccess;
if (existingEntry !== undefined) {
reducedRuntimeCondition = subtractRuntimeCondition(
runtimeCondition,
existingEntry.runtimeCondition,
runtime
);
reducedNonDeferAccess = subtractNonDeferAccess(
nonDeferAccess,
existingEntry.nonDeferAccess
);
if (
reducedRuntimeCondition === false &&
reducedNonDeferAccess === false
) {
return;
}
if (reducedRuntimeCondition !== false) {
existingEntry.runtimeCondition = mergeRuntimeConditionNonFalse(
existingEntry.runtimeCondition,
reducedRuntimeCondition,
runtime
);
}
if (reducedNonDeferAccess !== false) {
existingEntry.nonDeferAccess = mergeNonDeferAccess(
existingEntry.nonDeferAccess,
reducedNonDeferAccess
);
}
} else {
reducedRuntimeCondition = runtimeCondition;
reducedNonDeferAccess = nonDeferAccess;
existingEntries.set(connection.module, {
runtimeCondition,
nonDeferAccess
});
}
if (list.length > 0) {
const lastItem = list[list.length - 1];
if (
lastItem.type === "external" &&
lastItem.module === connection.module
) {
lastItem.runtimeCondition = mergeRuntimeCondition(
lastItem.runtimeCondition,
reducedRuntimeCondition,
runtime
);
lastItem.nonDeferAccess = mergeNonDeferAccess(
lastItem.nonDeferAccess,
reducedNonDeferAccess
);
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: reducedRuntimeCondition,
nonDeferAccess: reducedNonDeferAccess
});
}
};
existingEntries.set(rootModule, {
runtimeCondition: true,
nonDeferAccess: true
});
const imports = getConcatenatedImports(rootModule);
for (const { connection, runtimeCondition, nonDeferAccess } of imports) {
enterModule(connection, runtimeCondition, nonDeferAccess);
}
list.push({
type: "concatenated",
module: rootModule,
runtimeCondition: true,
nonDeferAccess: true
});
return list;
}
/**
* @param {Module} rootModule the root module of the concatenation
* @param {Set<Module>} modules all modules in the concatenation (including the root module)
* @param {AssociatedObjectForCache=} associatedObjectForCache object for caching
* @param {HashFunction=} hashFunction hash function to use
* @returns {string} the identifier
*/
static _createIdentifier(
rootModule,
modules,
associatedObjectForCache,
hashFunction = DEFAULTS.HASH_FUNCTION
) {
const cachedMakePathsRelative = makePathsRelative.bindContextCache(
/** @type {string} */ (rootModule.context),
associatedObjectForCache
);
/** @type {string[]} */
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")}`;
}
/**
* Adds the provided file dependencies to the module.
* @param {FileSystemDependencies} fileDependencies set where file dependencies are added to
* @param {FileSystemDependencies} contextDependencies set where context dependencies are added to
* @param {FileSystemDependencies} missingDependencies set where missing dependencies are added to
* @param {FileSystemDependencies} 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
);
}
}
/**
* Generates code and runtime requirements for this module.
* @param {CodeGenerationContext} context context for code generation
* @returns {CodeGenerationResult} result
*/
codeGeneration({
dependencyTemplates,
runtimeTemplate,
moduleGraph,
chunkGraph,
runtime: generationRuntime,
runtimes,
codeGenerationResults
}) {
const { concatenatedModuleInfo } = ConcatenatedModule.getCompilationHooks(
this.compilation
);
/** @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 {NeededNamespaceObjects} */
const neededNamespaceObjects = new Set();
// Set with modules whose mangled namespace escapes as a whole value and
// therefore needs a decoupled namespace object keyed by the original names
/** @type {Set<ConcatenatedModuleInfo | ExternalModuleInfo>} */
const neededEscapeNamespaceObjects = new Set();
// List of all used names to avoid conflicts
const allUsedNames = new Set(RESERVED_NAMES);
// 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,
runtimes,
/** @type {CodeGenerationResults} */
(codeGenerationResults),
allUsedNames
);
}
// Updated Top level declarations are created by renaming
/** @type {TopLevelDeclarations} */
const topLevelDeclarations = new Set();
// Free names remaining in the rendered source (runtime globals are
// tracked by runtimeRequirements instead)
/** @type {Set<string>} */
const freeNames = new Set();
// List of additional names in scope for module references
/** @type {UsedNamesInScopeInfo} */
const usedNamesInScopeInfo = new Map();
// Set of already checked scopes
/** @type {Set<Scope>} */
const ignoredScopes = new Set();
/**
* Lazily allocates a decoupled namespace object for a concatenated module
* whose exports are mangled, so an escaping whole-namespace value still
* exposes the original export names. Returns undefined when no export is
* mangled (the regular namespace object can be used as-is).
* @param {ConcatenatedModuleInfo | ExternalModuleInfo} info module info
* @returns {string | undefined} the escape namespace object name
*/
const getEscapeNamespaceObjectName = (info) => {
if (info.type === "concatenated" && info.namespaceExportSymbol) {
return undefined;
}
// Deferred external modules keep their special deferred namespace object
// and are rendered through a different path that wouldn't emit ours.
if (info.type === "external" && info.deferred) return undefined;
// Only real ES module namespaces are decoupled; non-harmony modules use
// their interop/fake namespace object as before.
const buildMeta = /** @type {BuildMeta} */ (info.module.buildMeta);
if (!buildMeta || buildMeta.exportsType !== "namespace") return undefined;
if (info.escapeNamespaceObjectName !== undefined) {
return info.escapeNamespaceObjectName;
}
const exportsInfo = moduleGraph.getExportsInfo(info.module);
let mangled = false;
for (const exportInfo of exportsInfo.orderedExports) {
if (exportInfo.provided === false) continue;
const usedName = exportInfo.getUsedName(undefined, runtime);
if (!usedName || usedName instanceof InlinedUsedName) continue;
if (usedName[usedName.length - 1] !== exportInfo.name) {
mangled = true;
break;
}
}
if (!mangled) return undefined;
const name = findNewName(
"namespaceObject",
allUsedNames,
/** @type {UsedNames} */ (new Set()),
info.module.readableIdentifier(requestShortener)
);
allUsedNames.add(name);
topLevelDeclarations.add(name);
info.escapeNamespaceObjectName = name;
neededEscapeNamespaceObjects.add(info);
return name;
};
// get all global names
for (const info of modulesWithInfo) {
if (info.type === "concatenated" && !info.cjsWrapped) {
// ignore sym