webpack/lib/optimize/ConcatenatedModule.js

1874 lines
54 KiB
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 {
CachedSource,
ConcatSource,
ReplaceSource
} = require("webpack-sources");
const ConcatenationScope = require("../ConcatenationScope");
const { UsageState } = require("../ExportsInfo");
const Module = require("../Module");
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, keepOriginalOrder } = require("../util/comparators");
const createHash = require("../util/createHash");
const { makePathsRelative } = require("../util/identifier");
const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess");
const {
filterRuntime,
intersectRuntime,
mergeRuntimeCondition,
mergeRuntimeConditionNonFalse,
runtimeConditionToString,
subtractRuntimeCondition
} = require("../util/runtime");
/** @typedef {import("eslint-scope").Scope} Scope */
/** @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 */
/** @template T @typedef {import("../InitFragment")<T>} InitFragment */
/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */
/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */
/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */
/** @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("../util/Hash")} Hash */
/** @typedef {typeof import("../util/Hash")} HashConstructor */
/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
// 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 {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 {Object} ast
* @property {Source} internalSource
* @property {ReplaceSource} source
* @property {InitFragment<ChunkRenderContext>[]=} chunkInitFragments
* @property {Iterable<string>} runtimeRequirements
* @property {Scope} globalScope
* @property {Scope} moduleScope
* @property {Map<string, string>} internalNames
* @property {Map<string, string>} exportMap
* @property {Map<string, string>} rawExportMap
* @property {string=} namespaceExportSymbol
* @property {string} namespaceObjectName
* @property {boolean} interopNamespaceObjectUsed
* @property {string} interopNamespaceObjectName
* @property {boolean} interopNamespaceObject2Used
* @property {string} interopNamespaceObject2Name
* @property {boolean} interopDefaultAccessUsed
* @property {string} interopDefaultAccessName
*/
/**
* @typedef {Object} ExternalModuleInfo
* @property {"external"} type
* @property {Module} module
* @property {RuntimeSpec | boolean} runtimeCondition
* @property {number} index
* @property {string} name
* @property {boolean} interopNamespaceObjectUsed
* @property {string} interopNamespaceObjectName
* @property {boolean} interopNamespaceObject2Used
* @property {string} interopNamespaceObject2Name
* @property {boolean} interopDefaultAccessUsed
* @property {string} interopDefaultAccessName
*/
/**
* @typedef {Object} ReferenceToModuleInfo
* @property {"reference"} type
* @property {RuntimeSpec | boolean} runtimeCondition
* @property {ConcatenatedModuleInfo | ExternalModuleInfo} target
*/
const RESERVED_NAMES = new Set(
[
// internal names (should always be renamed)
ConcatenationScope.DEFAULT_EXPORT,
ConcatenationScope.NAMESPACE_OBJECT_EXPORT,
// keywords
"abstract,arguments,async,await,boolean,break,byte,case,catch,char,class,const,continue",
"debugger,default,delete,do,double,else,enum,eval,export,extends,false,final,finally,float",
"for,function,goto,if,implements,import,in,instanceof,int,interface,let,long,native,new,null",
"package,private,protected,public,return,short,static,super,switch,synchronized,this,throw",
"throws,transient,true,try,typeof,var,void,volatile,while,with,yield",
// commonjs/amd
"module,__dirname,__filename,exports,require,define",
// js globals
"Array,Date,eval,function,hasOwnProperty,Infinity,isFinite,isNaN,isPrototypeOf,length,Math",
"NaN,name,Number,Object,prototype,String,toString,undefined,valueOf",
// browser globals
"alert,all,anchor,anchors,area,assign,blur,button,checkbox,clearInterval,clearTimeout",
"clientInformation,close,closed,confirm,constructor,crypto,decodeURI,decodeURIComponent",
"defaultStatus,document,element,elements,embed,embeds,encodeURI,encodeURIComponent,escape",
"event,fileUpload,focus,form,forms,frame,innerHeight,innerWidth,layer,layers,link,location",
"mimeTypes,navigate,navigator,frames,frameRate,hidden,history,image,images,offscreenBuffering",
"open,opener,option,outerHeight,outerWidth,packages,pageXOffset,pageYOffset,parent,parseFloat",
"parseInt,password,pkcs11,plugin,prompt,propertyIsEnum,radio,reset,screenX,screenY,scroll",
"secure,select,self,setInterval,setTimeout,status,submit,taint,text,textarea,top,unescape",
"untaint,window",
// window events
"onblur,onclick,onerror,onfocus,onkeydown,onkeypress,onkeyup,onmouseover,onload,onmouseup,onmousedown,onsubmit"
]
.join(",")
.split(",")
);
const bySourceOrder = (a, b) => {
const aOrder = a.sourceOrder;
const bOrder = b.sourceOrder;
if (isNaN(aOrder)) {
if (!isNaN(bOrder)) {
return 1;
}
} else {
if (isNaN(bOrder)) {
return -1;
}
if (aOrder !== bOrder) {
return aOrder < bOrder ? -1 : 1;
}
}
return 0;
};
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<Module, ModuleInfo>} moduleToInfoMap moduleToInfoMap
* @param {RuntimeSpec} runtime for which runtime
* @param {RequestShortener} requestShortener the request shortener
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @param {Set<ConcatenatedModuleInfo>} neededNamespaceObjects modules for which a namespace object should be generated
* @param {boolean} asCall asCall
* @param {boolean} 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,
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: info.interopNamespaceObject2Name,
ids: exportName,
exportName
};
case "default-with-named":
info.interopNamespaceObjectUsed = true;
return {
info,
rawName: 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: info.namespaceObjectName,
ids: exportName,
exportName
};
case "external":
return { info, rawName: 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: 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,
refInfo,
reexport.export
? [...reexport.export, ...exportName.slice(1)]
: exportName.slice(1),
moduleToInfoMap,
runtime,
requestShortener,
runtimeTemplate,
neededNamespaceObjects,
asCall,
info.module.buildMeta.strictHarmonyModule,
asiSafe,
alreadyVisited
);
}
if (info.namespaceExportSymbol) {
const usedName = /** @type {string[]} */ (
exportsInfo.getUsedName(exportName, runtime)
);
return {
info,
rawName: 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<Module, ModuleInfo>} moduleToInfoMap moduleToInfoMap
* @param {RuntimeSpec} runtime for which runtime
* @param {RequestShortener} requestShortener the request shortener
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @param {Set<ConcatenatedModuleInfo>} neededNamespaceObjects modules for which a namespace object should be generated
* @param {boolean} asCall asCall
* @param {boolean} callContext callContext
* @param {boolean} 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;
}
};
const addScopeSymbols = (s, nameSet, scopeSet1, scopeSet2) => {
let scope = s;
while (scope) {
if (scopeSet1.has(scope)) break;
if (scopeSet2.has(scope)) break;
scopeSet1.add(scope);
for (const variable of scope.variables) {
nameSet.add(variable.name);
}
scope = scope.upper;
}
};
const getAllReferences = variable => {
let set = variable.references;
// Look for inner scope variables too (like in class Foo { t() { Foo } })
const identifiers = new Set(variable.identifiers);
for (const scope of variable.scope.childScopes) {
for (const innerVar of scope.variables) {
if (innerVar.identifiers.some(id => identifiers.has(id))) {
set = set.concat(innerVar.references);
break;
}
}
}
return set;
};
const getPathInAst = (ast, node) => {
if (ast === node) {
return [];
}
const nr = node.range;
const enterNode = n => {
if (!n) return undefined;
const r = n.range;
if (r) {
if (r[0] <= nr[0] && r[1] >= nr[1]) {
const path = getPathInAst(n, node);
if (path) {
path.push(n);
return path;
}
}
}
return undefined;
};
if (Array.isArray(ast)) {
for (let i = 0; i < ast.length; i++) {
const enterResult = enterNode(ast[i]);
if (enterResult !== undefined) return enterResult;
}
} else if (ast && typeof ast === "object") {
const keys = Object.keys(ast);
for (let i = 0; i < keys.length; i++) {
const value = ast[keys[i]];
if (Array.isArray(value)) {
const pathResult = getPathInAst(value, node);
if (pathResult !== undefined) return pathResult;
} else if (value && typeof value === "object") {
const enterResult = enterNode(value);
if (enterResult !== undefined) return enterResult;
}
}
}
};
const TYPES = new Set(["javascript"]);
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 {Object=} associatedObjectForCache object for caching
* @param {string | HashConstructor=} hashFunction hash function to use
* @returns {ConcatenatedModule} the module
*/
static create(
rootModule,
modules,
runtime,
associatedObjectForCache,
hashFunction = "md4"
) {
const identifier = ConcatenatedModule._createIdentifier(
rootModule,
modules,
associatedObjectForCache,
hashFunction
);
return new ConcatenatedModule({
identifier,
rootModule,
modules,
runtime
});
}
/**
* @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
*/
constructor({ identifier, rootModule, modules, runtime }) {
super("javascript/esm", null, rootModule && rootModule.layer);
// Info from Factory
/** @type {string} */
this._identifier = identifier;
/** @type {Module} */
this.rootModule = rootModule;
/** @type {Set<Module>} */
this._modules = modules;
this._runtime = runtime;
this.factoryMeta = rootModule && rootModule.factoryMeta;
}
/**
* 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 {Set<string>} types available (do not mutate)
*/
getSourceTypes() {
return 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;
this.buildInfo = {
strict: true,
cacheable: true,
moduleArgument: rootModule.buildInfo.moduleArgument,
exportsArgument: rootModule.buildInfo.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 (!m.buildInfo.cacheable) {
this.buildInfo.cacheable = false;
}
// populate dependencies
for (const d of m.dependencies.filter(
dep =>
!(dep instanceof HarmonyImportDependency) ||
!this._modules.has(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);
}
}
// populate topLevelDeclarations
if (m.buildInfo.topLevelDeclarations) {
const topLevelDeclarations = this.buildInfo.topLevelDeclarations;
if (topLevelDeclarations !== undefined) {
for (const decl of m.buildInfo.topLevelDeclarations) {
// reserved names will always be renamed
if (RESERVED_NAMES.has(decl)) continue;
// TODO actually this is incorrect since with renaming there could be more
// We should do the renaming during build
topLevelDeclarations.add(decl);
}
}
} else {
this.buildInfo.topLevelDeclarations = undefined;
}
// populate assets
if (m.buildInfo.assets) {
if (this.buildInfo.assets === undefined) {
this.buildInfo.assets = Object.create(null);
}
Object.assign(this.buildInfo.assets, m.buildInfo.assets);
}
if (m.buildInfo.assetsInfo) {
if (this.buildInfo.assetsInfo === undefined) {
this.buildInfo.assetsInfo = new Map();
}
for (const [key, value] of m.buildInfo.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<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, RuntimeSpec | true>} */
const existingEntries = new Map();
/**
* @param {Module} module a module
* @returns {Iterable<{ connection: ModuleGraphConnection, runtimeCondition: RuntimeSpec | true }>} imported modules in order
*/
const getConcatenatedImports = module => {
let connections = Array.from(moduleGraph.getOutgoingConnections(module));
if (module === rootModule) {
for (const c of moduleGraph.getOutgoingConnections(this))
connections.push(c);
}
const references = connections
.filter(connection => {
if (!(connection.dependency instanceof HarmonyImportDependency))
return false;
return (
connection &&
connection.resolvedOriginModule === module &&
connection.module &&
connection.isTargetActive(runtime)
);
})
.map(connection => ({
connection,
sourceOrder: /** @type {HarmonyImportDependency} */ (
connection.dependency
).sourceOrder
}));
references.sort(
concatComparators(bySourceOrder, keepOriginalOrder(references))
);
/** @type {Map<Module, { connection: ModuleGraphConnection, runtimeCondition: RuntimeSpec | true }>} */
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<Module>} 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(
rootModule.context,
associatedObjectForCache
);
let 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<string>} fileDependencies set where file dependencies are added to
* @param {LazySet<string>} contextDependencies set where context dependencies are added to
* @param {LazySet<string>} missingDependencies set where missing dependencies are added to
* @param {LazySet<string>} 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 {Set<string>} */
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<ConcatenatedModuleInfo>} */
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,
codeGenerationResults
);
}
// List of all used names to avoid conflicts
const allUsedNames = new Set(RESERVED_NAMES);
// List of additional names in scope for module references
/** @type {Map<string, { usedNames: Set<string>, alreadyCheckedScopes: Set<TODO> }>} */
const usedNamesInScopeInfo = new Map();
/**
* @param {string} module module identifier
* @param {string} id export id
* @returns {{ usedNames: Set<string>, alreadyCheckedScopes: Set<TODO> }} info
*/
const getUsedNamesInScopeInfo = (module, id) => {
const key = `${module}-${id}`;
let info = usedNamesInScopeInfo.get(key);
if (info === undefined) {
info = {
usedNames: new Set(),
alreadyCheckedScopes: new Set()
};
usedNamesInScopeInfo.set(key, info);
}
return 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();
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,
info.module.buildMeta.strictHarmonyModule,
true
);
if (!binding.ids) continue;
const { usedNames, alreadyCheckedScopes } =
getUsedNamesInScopeInfo(
binding.info.module.identifier(),
"name" in binding ? binding.name : ""
);
for (const expr of getSuperClassExpressions(reference.from)) {
if (
expr.range[0] <= reference.identifier.range[0] &&
expr.range[1] >= 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(
info.module.identifier(),
""
);
switch (info.type) {
case "concatenated": {
for (const variable of info.moduleScope.variables) {
const name = variable.name;
const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo(
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 = this.findNewName(
name,
allUsedNames,
usedNames,
info.module.readableIdentifier(requestShortener)
);
allUsedNames.add(newName);
info.internalNames.set(name, newName);
const source = info.source;
const allIdentifiers = new Set(
references.map(r => r.identifier).concat(variable.identifiers)
);
for (const identifier of allIdentifiers) {
const r = identifier.range;
const path = getPathInAst(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);
}
}
let namespaceObjectName;
if (info.namespaceExportSymbol) {
namespaceObjectName = info.internalNames.get(
info.namespaceExportSymbol
);
} else {
namespaceObjectName = this.findNewName(
"namespaceObject",
allUsedNames,
namespaceObjectUsedNames,
info.module.readableIdentifier(requestShortener)
);
allUsedNames.add(namespaceObjectName);
}
info.namespaceObjectName = namespaceObjectName;
break;
}
case "external": {
const externalName = this.findNewName(
"",
allUsedNames,
namespaceObjectUsedNames,
info.module.readableIdentifier(requestShortener)
);
allUsedNames.add(externalName);
info.name = externalName;
break;
}
}
if (info.module.buildMeta.exportsType !== "namespace") {
const externalNameInterop = this.findNewName(
"namespaceObject",
allUsedNames,
namespaceObjectUsedNames,
info.module.readableIdentifier(requestShortener)
);
allUsedNames.add(externalNameInterop);
info.interopNamespaceObjectName = externalNameInterop;
}
if (
info.module.buildMeta.exportsType === "default" &&
info.module.buildMeta.defaultObject !== "redirect"
) {
const externalNameInterop = this.findNewName(
"namespaceObject2",
allUsedNames,
namespaceObjectUsedNames,
info.module.readableIdentifier(requestShortener)
);
allUsedNames.add(externalNameInterop);
info.interopNamespaceObject2Name = externalNameInterop;
}
if (
info.module.buildMeta.exportsType === "dynamic" ||
!info.module.buildMeta.exportsType
) {
const externalNameInterop = this.findNewName(
"default",
allUsedNames,
namespaceObjectUsedNames,
info.module.readableIdentifier(requestShortener)
);
allUsedNames.add(externalNameInterop);
info.interopDefaultAccessName = externalNameInterop;
}
}
// Find and replace references to modules
for (const info of moduleToInfoMap.values()) {
if (info.type === "concatenated") {
for (const reference of info.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,
info.module.buildMeta.strictHarmonyModule,
match.asiSafe
);
const r = reference.identifier.range;
const source = 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<string, function(RequestShortener): string>} */
const exportsMap = new Map();
// Set with all root exposed unused exports
/** @type {Set<string>} */
const unusedExports = new Set();
const rootInfo = /** @type {ConcatenatedModuleInfo} */ (
moduleToInfoMap.get(this.rootModule)
);
const strictHarmonyModule = rootInfo.module.buildMeta.strictHarmonyModule;
const exportsInfo = moduleGraph.getExportsInfo(rootInfo.module);
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
);
return `/* ${
exportInfo.isReexport() ? "reexport" : "binding"
} */ ${finalName}`;
} catch (e) {
e.message += `\nwhile generating the root export '${name}' (used name: '${used}')`;
throw e;
}
});
}
const result = new ConcatSource();
// add harmony compatibility flag (must be first because of possible circular dependencies)
if (
moduleGraph.getExportsInfo(this).otherExportsInfo.getUsed(runtime) !==
UsageState.Unused
) {
result.add(`// ESM COMPAT FLAG\n`);
result.add(
runtimeTemplate.defineEsModuleFlagStatement({
exportsArgument: this.exportsArgument,
runtimeRequirements
})
);
}
// define exports
if (exportsMap.size > 0) {
runtimeRequirements.add(RuntimeGlobals.exports);
runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
const definitions = [];
for (const [key, value] of exportsMap) {
definitions.push(
`\n ${JSON.stringify(key)}: ${runtimeTemplate.returningFunction(
value(requestShortener)
)}`
);
}
result.add(`\n// EXPORTS\n`);
result.add(
`${RuntimeGlobals.definePropertyGetters}(${
this.exportsArgument
}, {${definitions.join(",")}\n});\n`
);
}
// 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,
info.module.buildMeta.strictHarmonyModule,
true
);
nsObj.push(
`\n ${JSON.stringify(
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;// CONCATENATED MODULE: ${info.module.readableIdentifier(
requestShortener
)}\n`
);
result.add(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} = __webpack_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);
/** @type {CodeGenerationResult} */
const resultEntry = {
sources: new Map([["javascript", new CachedSource(result)]]),
data,
runtimeRequirements
};
return resultEntry;
}
/**
* @param {Map<Module, ModuleInfo>} 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
});
const 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) {
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 = scopeManager.acquire(ast);
const moduleScope = globalScope.childScopes[0];
const resultSource = new ReplaceSource(source);
info.runtimeRequirements = codeGenResult.runtimeRequirements;
info.ast = ast;
info.internalSource = source;
info.source = resultSource;
info.chunkInitFragments = chunkInitFragments;
info.globalScope = globalScope;
info.moduleScope = moduleScope;
} catch (err) {
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, ModuleInfo>]} module info items
*/
_getModulesWithInfo(moduleGraph, runtime) {
const orderedConcatenationList = this._createConcatenationList(
this.rootModule,
this._modules,
runtime,
moduleGraph
);
/** @type {Map<Module, ModuleInfo>} */
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(item.module, item);
return item;
} else {
/** @type {ReferenceToModuleInfo} */
const ref = {
type: "reference",
runtimeCondition: info.runtimeCondition,
target: item
};
return ref;
}
});
return [list, map];
}
findNewName(oldName, usedNamed1, usedNamed2, extraInfo) {
let name = oldName;
if (name === ConcatenationScope.DEFAULT_EXPORT) {
name = "";
}
if (name === ConcatenationScope.NAMESPACE_OBJECT_EXPORT) {
name = "namespaceObject";
}
// Remove uncool stuff
extraInfo = extraInfo.replace(
/\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules/g,
""
);
const splittedInfo = extraInfo.split("/");
while (splittedInfo.length) {
name = splittedInfo.pop() + (name ? "_" + name : "");
const nameIdent = Template.toIdentifier(name);
if (
!usedNamed1.has(nameIdent) &&
(!usedNamed2 || !usedNamed2.has(nameIdent))
)
return nameIdent;
}
let i = 0;
let nameWithNumber = Template.toIdentifier(`${name}_${i}`);
while (
usedNamed1.has(nameWithNumber) ||
(usedNamed2 && usedNamed2.has(nameWithNumber))
) {
i++;
nameWithNumber = Template.toIdentifier(`${name}_${i}`);
}
return nameWithNumber;
}
/**
* @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);
}
static deserialize(context) {
const obj = new ConcatenatedModule({
identifier: undefined,
rootModule: undefined,
modules: undefined,
runtime: undefined
});
obj.deserialize(context);
return obj;
}
}
makeSerializable(ConcatenatedModule, "webpack/lib/optimize/ConcatenatedModule");
module.exports = ConcatenatedModule;