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,494 lines (1,414 loc) • 78.2 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const vm = require("vm");
const eslintScope = require("eslint-scope");
const { SyncBailHook, SyncHook, SyncWaterfallHook } = require("tapable");
const {
CachedSource,
ConcatSource,
OriginalSource,
PrefixSource,
RawSource,
ReplaceSource
} = require("webpack-sources");
/** @typedef {import("../Compilation")} Compilation */
const HotUpdateChunk = require("../HotUpdateChunk");
const InitFragment = require("../InitFragment");
const { JAVASCRIPT_TYPE } = require("../ModuleSourceTypeConstants");
const {
JAVASCRIPT_MODULE_TYPE_AUTO,
JAVASCRIPT_MODULE_TYPE_DYNAMIC,
JAVASCRIPT_MODULE_TYPE_ESM,
WEBPACK_MODULE_TYPE_RUNTIME
} = require("../ModuleTypeConstants");
const NormalModule = require("../NormalModule");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const { tryRunOrWebpackError } = require("../errors/HookWebpackError");
const { last, someInIterable } = require("../util/IterableHelpers");
const StringXor = require("../util/StringXor");
const { compareModulesByFullName } = 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 { digestNonNumericOnlyWithFull } = require("../util/nonNumericOnlyHash");
const removeBOM = require("../util/removeBOM");
const { intersectRuntime } = require("../util/runtime");
const { getAllChunks } = require("./ChunkHelpers");
const JavascriptGenerator = require("./JavascriptGenerator");
const JavascriptModule = require("./JavascriptModule");
const JavascriptParser = require("./JavascriptParser");
/** @typedef {import("estree").Program} Program */
/** @typedef {import("estree").Node} Node */
/** @typedef {import("estree").Identifier} Identifier */
/** @typedef {import("estree").CatchClause} CatchClause */
/** @typedef {import("estree").ClassDeclaration} ClassDeclaration */
/** @typedef {import("estree").ClassExpression} ClassExpression */
/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */
/** @typedef {import("estree").FunctionExpression} FunctionExpression */
/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */
/** @typedef {import("estree").VariableDeclarator} VariableDeclarator */
/** @typedef {import("estree").VariableDeclaration} VariableDeclaration */
/** @typedef {import("estree").ImportDeclaration} ImportDeclaration */
/** @typedef {import("estree").ImportSpecifier} ImportSpecifier */
/** @typedef {import("estree").ImportDefaultSpecifier} ImportDefaultSpecifier */
/** @typedef {import("estree").ImportNamespaceSpecifier} ImportNamespaceSpecifier */
/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */
/** @typedef {import("estree").ForInStatement} ForInStatement */
/** @typedef {import("estree").ForOfStatement} ForOfStatement */
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../config/defaults").OutputNormalizedWithDefaults} OutputOptions */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../ChunkGraph").EntryModuleWithChunkGroup} EntryModuleWithChunkGroup */
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */
/** @typedef {import("../Compilation").ExecuteModuleObject} ExecuteModuleObject */
/** @typedef {import("../Compilation").WebpackRequire} WebpackRequire */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */
/** @typedef {import("../Entrypoint")} Entrypoint */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").CodeGenerationResultData} CodeGenerationResultData */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("../Chunk").ChunkFilenameTemplate} ChunkFilenameTemplate */
/** @typedef {import("../errors/WebpackError")} WebpackError */
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
/** @typedef {import("../util/Hash")} Hash */
/** @typedef {import("../util/concatenate").ScopeSet} ScopeSet */
/** @typedef {import("../util/concatenate").UsedNamesInScopeInfo} UsedNamesInScopeInfo */
// TODO remove these types when we will update `eslint-scope` to the latest version and import them from `eslint-scope`
/**
* @typedef {object} Scope
* @property {"block" | "catch" | "class" | "class-field-initializer" | "class-static-block" | "for" | "function" | "function-expression-name" | "global" | "module" | "switch" | "with" | "TDZ"} type
* @property {boolean} isStrict
* @property {Scope | null} upper
* @property {Scope[]} childScopes
* @property {Scope} variableScope
* @property {Node} block
* @property {Variable[]} variables
* @property {Map<string, Variable>} set
* @property {Reference[]} references
* @property {Reference[]} through
* @property {boolean} functionExpressionScope
* @property {{ variables: Variable[], set: Map<string, Variable> }=} implicit
*/
/**
* @typedef {
* | { type: "CatchClause", node: CatchClause, parent: null }
* | { type: "ClassName", node: ClassDeclaration | ClassExpression, parent: null }
* | { type: "FunctionName", node: FunctionDeclaration | FunctionExpression, parent: null }
* | { type: "ImplicitGlobalVariable", node: AssignmentExpression | ForInStatement | ForOfStatement, parent: null }
* | { type: "ImportBinding", node: ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier, parent: ImportDeclaration }
* | { type: "Parameter", node: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression, parent: null }
* | { type: "TDZ", node: any, parent: null }
* | { type: "Variable", node: VariableDeclarator, parent: VariableDeclaration }
* } DefinitionType
*/
/** @typedef {DefinitionType & { name: Identifier }} Definition */
/**
* @typedef {object} Variable
* @property {string} name
* @property {Scope} scope
* @property {Identifier[]} identifiers
* @property {Reference[]} references
* @property {Definition[]} defs
*/
/**
* @typedef {object} Reference
* @property {Identifier} identifier
* @property {Scope} from
* @property {Variable | null} resolved
* @property {Node | null} writeExpr
* @property {boolean} init
* @property {() => boolean} isWrite
* @property {() => boolean} isRead
* @property {() => boolean} isWriteOnly
* @property {() => boolean} isReadOnly
* @property {() => boolean} isReadWrite
*/
/** @type {WeakMap<ChunkGraph, WeakMap<Chunk, boolean>>} */
const chunkHasJsCache = new WeakMap();
/**
* Returns true, when a JS file is needed for this chunk.
* @param {Chunk} chunk a chunk
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {boolean} true, when a JS file is needed for this chunk
*/
const _chunkHasJs = (chunk, chunkGraph) => {
if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) {
if (chunkGraph.getModuleSourceTypes(module).has(JAVASCRIPT_TYPE)) {
return true;
}
}
}
return Boolean(
chunkGraph.getChunkModulesIterableBySourceType(chunk, JAVASCRIPT_TYPE)
);
};
/**
* Returns true, when a JS file is needed for this chunk.
* @param {Chunk} chunk a chunk
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {boolean} true, when a JS file is needed for this chunk
*/
const chunkHasJs = (chunk, chunkGraph) => {
let innerCache = chunkHasJsCache.get(chunkGraph);
if (innerCache === undefined) {
innerCache = new WeakMap();
chunkHasJsCache.set(chunkGraph, innerCache);
}
const cachedResult = innerCache.get(chunk);
if (cachedResult !== undefined) {
return cachedResult;
}
const result = _chunkHasJs(chunk, chunkGraph);
innerCache.set(chunk, result);
return result;
};
/**
* Chunk has runtime or js.
* @param {Chunk} chunk a chunk
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {boolean} true, when a JS file is needed for this chunk
*/
const chunkHasRuntimeOrJs = (chunk, chunkGraph) => {
if (chunkHasJs(chunk, chunkGraph)) {
return true;
}
if (
chunkGraph.getChunkModulesIterableBySourceType(
chunk,
WEBPACK_MODULE_TYPE_RUNTIME
)
) {
for (const chunkGroup of chunk.groupsIterable) {
for (const c of chunkGroup.chunks) {
if (chunkHasJs(c, chunkGraph)) return true;
}
}
return false;
}
return false;
};
/**
* Print generated code for stack.
* @param {Module} module a module
* @param {string} code the code
* @returns {string} generated code for the stack
*/
const printGeneratedCodeForStack = (module, code) => {
const lines = code.split("\n");
const n = `${lines.length}`.length;
return `\n\nGenerated code for ${module.identifier()}\n${lines
.map(
/**
* Handles the callback logic for this hook.
* @param {string} line the line
* @param {number} i the index
* @param {string[]} _lines the lines
* @returns {string} the line with line number
*/
(line, i, _lines) => {
const iStr = `${i + 1}`;
return `${" ".repeat(n - iStr.length)}${iStr} | ${line}`;
}
)
.join("\n")}`;
};
/**
* Defines the render context type used by this module.
* @typedef {object} RenderContext
* @property {Chunk} chunk the chunk
* @property {DependencyTemplates} dependencyTemplates the dependency templates
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {ModuleGraph} moduleGraph the module graph
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {CodeGenerationResults} codeGenerationResults results of code generation
* @property {boolean | undefined} strictMode rendering in strict context
*/
/**
* Defines the main render context type used by this module.
* @typedef {object} MainRenderContext
* @property {Chunk} chunk the chunk
* @property {DependencyTemplates} dependencyTemplates the dependency templates
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {ModuleGraph} moduleGraph the module graph
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {CodeGenerationResults} codeGenerationResults results of code generation
* @property {string} hash hash to be used for render call
* @property {boolean | undefined} strictMode rendering in strict context
*/
/**
* Defines the chunk render context type used by this module.
* @typedef {object} ChunkRenderContext
* @property {Chunk} chunk the chunk
* @property {DependencyTemplates} dependencyTemplates the dependency templates
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {ModuleGraph} moduleGraph the module graph
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {CodeGenerationResults} codeGenerationResults results of code generation
* @property {InitFragment<ChunkRenderContext>[]} chunkInitFragments init fragments for the chunk
* @property {boolean | undefined} strictMode rendering in strict context
* @property {Module=} inlinedEntryModule entry module inlined at the chunk top level instead of the registry
* @property {Source=} inlinedEntrySource rendered body of the inlined entry module
*/
/**
* Defines the render bootstrap context type used by this module.
* @typedef {object} RenderBootstrapContext
* @property {Chunk} chunk the chunk
* @property {CodeGenerationResults} codeGenerationResults results of code generation
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {ModuleGraph} moduleGraph the module graph
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {string} hash hash to be used for render call
*/
/**
* Defines the startup render context type used by this module.
* @typedef {object} StartupRenderContext
* @property {Chunk} chunk the chunk
* @property {DependencyTemplates} dependencyTemplates the dependency templates
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {ModuleGraph} moduleGraph the module graph
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {CodeGenerationResults} codeGenerationResults results of code generation
* @property {boolean | undefined} strictMode rendering in strict context
* @property {boolean=} inlined inlined
* @property {boolean=} inlinedInIIFE the inlined entry module is wrapped in an IIFE
* @property {boolean=} needExportsDeclaration whether the top-level exports declaration needs to be generated
*/
/**
* Defines the module render context type used by this module.
* @typedef {object} ModuleRenderContext
* @property {Chunk} chunk the chunk
* @property {DependencyTemplates} dependencyTemplates the dependency templates
* @property {RuntimeTemplate} runtimeTemplate the runtime template
* @property {ModuleGraph} moduleGraph the module graph
* @property {ChunkGraph} chunkGraph the chunk graph
* @property {CodeGenerationResults} codeGenerationResults results of code generation
* @property {InitFragment<ChunkRenderContext>[]} chunkInitFragments init fragments for the chunk
* @property {boolean | undefined} strictMode rendering in strict context
* @property {boolean} factory true: renders as factory method, false: pure module content
* @property {boolean=} inlinedInIIFE the inlined entry module is wrapped in an IIFE, existing only when `factory` is set to false
* @property {boolean=} renderInObject render module in object container
*/
/**
* Defines the compilation hooks type used by this module.
* @typedef {object} CompilationHooks
* @property {SyncWaterfallHook<[Source, Module, ModuleRenderContext]>} renderModuleContent
* @property {SyncWaterfallHook<[Source, Module, ModuleRenderContext]>} renderModuleContainer
* @property {SyncWaterfallHook<[Source, Module, ModuleRenderContext]>} renderModulePackage
* @property {SyncWaterfallHook<[Source, RenderContext]>} renderChunk
* @property {SyncWaterfallHook<[Source, RenderContext]>} renderMain
* @property {SyncWaterfallHook<[Source, RenderContext]>} renderContent
* @property {SyncWaterfallHook<[Source, RenderContext]>} render
* @property {SyncWaterfallHook<[Source, Module, StartupRenderContext]>} renderStartup
* @property {SyncWaterfallHook<[string, RenderBootstrapContext]>} renderRequire
* @property {SyncBailHook<[Module, Partial<RenderBootstrapContext>], string | void>} inlineInRuntimeBailout
* @property {SyncBailHook<[Module, RenderContext], string | void>} embedInRuntimeBailout
* @property {SyncBailHook<[RenderContext], string | void>} strictRuntimeBailout
* @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash
* @property {SyncBailHook<[Chunk, RenderContext], boolean | void>} useSourceMap
*/
/**
* Renames an inlined module's top-level declaration (and all its references) when
* its name is already taken in the shared startup scope, recording the chosen name
* in `allUsedNames`. Lets inlined modules be emitted without a per-entry/per-chunk
* IIFE by resolving name collisions instead of isolating each module.
* @param {InstanceType<typeof import("webpack-sources").ReplaceSource>} source the module source to edit
* @param {Program} ast the parsed module AST
* @param {Variable} variable the top-level variable to check/rename
* @param {string} moduleIdentifier the module identifier (for scope caching)
* @param {string} readableIdentifier the readable module identifier (for new names)
* @param {Set<string>} allUsedNames names already used in the shared scope (mutated)
* @returns {void}
*/
const renameInlinedModuleVariable = (
source,
ast,
variable,
moduleIdentifier,
readableIdentifier,
allUsedNames
) => {
/** @type {UsedNamesInScopeInfo} */
const usedNamesInScopeInfo = new Map();
/** @type {ScopeSet} */
const ignoredScopes = new Set();
const name = variable.name;
const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo(
usedNamesInScopeInfo,
moduleIdentifier,
name
);
if (allUsedNames.has(name) || usedNames.has(name)) {
const references = getAllReferences(variable);
const allIdentifiers = new Set([
...references.map((r) => r.identifier),
...variable.identifiers
]);
for (const ref of references) {
addScopeSymbols(ref.from, usedNames, alreadyCheckedScopes, ignoredScopes);
}
const newName = findNewName(
name,
allUsedNames,
usedNames,
readableIdentifier
);
allUsedNames.add(newName);
for (const identifier of allIdentifiers) {
const r = /** @type {Range} */ (identifier.range);
const path = getPathInAst(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);
}
}
allUsedNames.add(name);
};
const PLUGIN_NAME = "JavascriptModulesPlugin";
/** @typedef {{ header: string[], beforeStartup: string[], startup: string[], afterStartup: string[], allowInlineStartup: boolean }} Bootstrap */
class JavascriptModulesPlugin {
constructor(options = {}) {
this.options = options;
/** @type {WeakMap<Source, { source: Source, needModule: boolean, needExports: boolean, needRequire: boolean, needThisAsExports: boolean, needStrict: boolean | undefined, renderShorthand: boolean }>} */
this._moduleFactoryCache = new WeakMap();
}
/**
* Applies the plugin by registering its hooks on the compiler.
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
PLUGIN_NAME,
(compilation, { normalModuleFactory }) => {
const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation);
for (const type of [
JAVASCRIPT_MODULE_TYPE_AUTO,
JAVASCRIPT_MODULE_TYPE_DYNAMIC,
JAVASCRIPT_MODULE_TYPE_ESM
]) {
normalModuleFactory.hooks.createModuleClass
.for(type)
.tap(
PLUGIN_NAME,
(createData, _resolveData) => new JavascriptModule(createData)
);
normalModuleFactory.hooks.createParser
.for(type)
.tap(PLUGIN_NAME, (options) => {
switch (type) {
case JAVASCRIPT_MODULE_TYPE_AUTO: {
return new JavascriptParser("auto", {
parse: options.parse,
typescript: options.typescript,
importPhases: Boolean(
options.deferImport || options.sourceImport
),
strictModeViolations: options.strictModeViolations
});
}
case JAVASCRIPT_MODULE_TYPE_DYNAMIC: {
return new JavascriptParser("script", {
parse: options.parse,
typescript: options.typescript,
importPhases: Boolean(
options.deferImport || options.sourceImport
),
strictModeViolations: options.strictModeViolations
});
}
case JAVASCRIPT_MODULE_TYPE_ESM: {
return new JavascriptParser("module", {
parse: options.parse,
typescript: options.typescript,
importPhases: Boolean(
options.deferImport || options.sourceImport
),
strictModeViolations: options.strictModeViolations
});
}
}
});
normalModuleFactory.hooks.createGenerator
.for(type)
.tap(PLUGIN_NAME, () => new JavascriptGenerator());
NormalModule.getCompilationHooks(compilation).processResult.tap(
PLUGIN_NAME,
(result, module) => {
if (module.type === type) {
const [source, ...rest] = result;
return [removeBOM(source), ...rest];
}
return result;
}
);
}
compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => {
const {
hash,
chunk,
chunkGraph,
moduleGraph,
runtimeTemplate,
dependencyTemplates,
outputOptions,
codeGenerationResults
} = options;
const hotUpdateChunk = chunk instanceof HotUpdateChunk ? chunk : null;
const filenameTemplate =
JavascriptModulesPlugin.getChunkFilenameTemplate(
chunk,
outputOptions
);
/** @type {() => Source} */
let render;
if (hotUpdateChunk) {
render = () =>
this.renderChunk(
{
chunk,
dependencyTemplates,
runtimeTemplate,
moduleGraph,
chunkGraph,
codeGenerationResults,
strictMode: runtimeTemplate.isModule()
},
hooks
);
} else if (chunk.hasRuntime()) {
if (!chunkHasRuntimeOrJs(chunk, chunkGraph)) {
return result;
}
render = () =>
this.renderMain(
{
hash,
chunk,
dependencyTemplates,
runtimeTemplate,
moduleGraph,
chunkGraph,
codeGenerationResults,
strictMode: runtimeTemplate.isModule()
},
hooks,
compilation
);
} else {
if (!chunkHasJs(chunk, chunkGraph)) {
return result;
}
render = () =>
this.renderChunk(
{
chunk,
dependencyTemplates,
runtimeTemplate,
moduleGraph,
chunkGraph,
codeGenerationResults,
strictMode: runtimeTemplate.isModule()
},
hooks
);
}
result.push({
render,
filenameTemplate,
pathOptions: {
hash,
runtime: chunk.runtime,
chunk,
contentHashType: "javascript"
},
info: {
javascriptModule: compilation.runtimeTemplate.isModule()
},
identifier: hotUpdateChunk
? `hotupdatechunk${chunk.id}`
: `chunk${chunk.id}`,
hash: chunk.contentHash.javascript
});
return result;
});
compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => {
hooks.chunkHash.call(chunk, hash, context);
if (chunk.hasRuntime()) {
this.updateHashWithBootstrap(
hash,
{
hash: "0000",
chunk,
codeGenerationResults: context.codeGenerationResults,
chunkGraph: context.chunkGraph,
moduleGraph: context.moduleGraph,
runtimeTemplate: context.runtimeTemplate
},
hooks
);
}
});
compilation.hooks.contentHash.tap(PLUGIN_NAME, (chunk) => {
const {
chunkGraph,
moduleGraph,
runtimeTemplate,
outputOptions: {
hashSalt,
hashDigest,
hashDigestLength,
hashFunction
}
} = compilation;
const codeGenerationResults =
/** @type {CodeGenerationResults} */
(compilation.codeGenerationResults);
const hash = createHash(hashFunction);
if (hashSalt) hash.update(hashSalt);
if (chunk.hasRuntime()) {
this.updateHashWithBootstrap(
hash,
{
hash: "0000",
chunk,
codeGenerationResults,
chunkGraph: compilation.chunkGraph,
moduleGraph: compilation.moduleGraph,
runtimeTemplate: compilation.runtimeTemplate
},
hooks
);
} else {
hash.update(`${chunk.id} `);
hash.update(chunk.ids ? chunk.ids.join(",") : "");
}
hooks.chunkHash.call(chunk, hash, {
chunkGraph,
codeGenerationResults,
moduleGraph,
runtimeTemplate
});
const modules = chunkGraph.getChunkModulesIterableBySourceType(
chunk,
JAVASCRIPT_TYPE
);
if (modules) {
const xor = new StringXor();
for (const m of modules) {
xor.add(chunkGraph.getModuleHash(m, chunk.runtime));
}
xor.updateHash(hash);
}
const runtimeModules = chunkGraph.getChunkModulesIterableBySourceType(
chunk,
WEBPACK_MODULE_TYPE_RUNTIME
);
if (runtimeModules) {
const xor = new StringXor();
for (const m of runtimeModules) {
xor.add(chunkGraph.getModuleHash(m, chunk.runtime));
}
xor.updateHash(hash);
}
[chunk.contentHash.javascript, chunk.contentHashFull.javascript] =
digestNonNumericOnlyWithFull(hash, hashDigest, hashDigestLength);
});
compilation.hooks.additionalTreeRuntimeRequirements.tap(
PLUGIN_NAME,
(chunk, set, { chunkGraph }) => {
if (
!set.has(RuntimeGlobals.startupNoDefault) &&
chunkGraph.hasChunkEntryDependentChunks(chunk)
) {
set.add(RuntimeGlobals.onChunksLoaded);
set.add(RuntimeGlobals.exports);
set.add(RuntimeGlobals.require);
}
}
);
compilation.hooks.executeModule.tap(PLUGIN_NAME, (options, context) => {
const source =
options.codeGenerationResult.sources.get(JAVASCRIPT_TYPE);
if (source === undefined) return;
const { module } = options;
const code = source.source();
/** @type {(this: ExecuteModuleObject["exports"], exports: ExecuteModuleObject["exports"], moduleObject: ExecuteModuleObject, webpackRequire: WebpackRequire) => void} */
const fn = vm.runInThisContext(
`(function(${module.moduleArgument}, ${module.exportsArgument}, ${RuntimeGlobals.require}) {\n${code}\n/**/})`,
{
filename: module.identifier(),
lineOffset: -1
}
);
const moduleObject =
/** @type {ExecuteModuleObject} */
(options.moduleObject);
try {
fn.call(
moduleObject.exports,
moduleObject,
moduleObject.exports,
/** @type {WebpackRequire} */
(context.__webpack_require__)
);
} catch (err) {
/** @type {Error} */
(err).stack += printGeneratedCodeForStack(
options.module,
/** @type {string} */ (code)
);
throw err;
}
});
compilation.hooks.executeModule.tap(PLUGIN_NAME, (options, context) => {
const source = options.codeGenerationResult.sources.get("runtime");
if (source === undefined) return;
let code = source.source();
if (typeof code !== "string") code = code.toString();
/** @type {(this: null, webpackRequire: WebpackRequire) => void} */
const fn = vm.runInThisContext(
`(function(${RuntimeGlobals.require}) {\n${code}\n/**/})`,
{
filename: options.module.identifier(),
lineOffset: -1
}
);
try {
// eslint-disable-next-line no-useless-call
fn.call(
null,
/** @type {WebpackRequire} */
(context.__webpack_require__)
);
} catch (err) {
/** @type {Error} */
(err).stack += printGeneratedCodeForStack(options.module, code);
throw err;
}
});
}
);
}
/**
* Gets chunk filename template.
* @param {Chunk} chunk chunk
* @param {OutputOptions} outputOptions output options
* @returns {ChunkFilenameTemplate} used filename template
*/
static getChunkFilenameTemplate(chunk, outputOptions) {
if (chunk.filenameTemplate) {
return chunk.filenameTemplate;
} else if (chunk instanceof HotUpdateChunk) {
return outputOptions.hotUpdateChunkFilename;
}
const entryOptions = chunk.getEntryOptions();
// A worker entry (`new Worker` or `entry.worker`) uses workerChunkFilename;
// chunks it loads internally keep `chunkFilename`
if (entryOptions !== undefined && entryOptions.worker) {
return outputOptions.workerChunkFilename;
} else if (chunk.canBeInitial()) {
return outputOptions.filename;
}
return outputOptions.chunkFilename;
}
/**
* Renders the newly generated source from rendering.
* @param {Module} module the rendered module
* @param {ModuleRenderContext} renderContext options object
* @param {CompilationHooks} hooks hooks
* @returns {Source | null} the newly generated source from rendering
*/
renderModule(module, renderContext, hooks) {
const {
chunk,
chunkGraph,
runtimeTemplate,
codeGenerationResults,
strictMode,
factory,
renderInObject
} = renderContext;
try {
const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
const moduleSource = codeGenResult.sources.get(JAVASCRIPT_TYPE);
if (!moduleSource) return null;
if (codeGenResult.data !== undefined) {
const chunkInitFragments = codeGenResult.data.get("chunkInitFragments");
if (chunkInitFragments) {
for (const i of chunkInitFragments) {
renderContext.chunkInitFragments.push(i);
}
}
}
const moduleSourcePostContent = tryRunOrWebpackError(
() =>
hooks.renderModuleContent.call(moduleSource, module, renderContext),
"JavascriptModulesPlugin.getCompilationHooks().renderModuleContent"
);
/** @type {Source} */
let moduleSourcePostContainer;
if (factory) {
const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements(
module,
chunk.runtime
);
const needModule = runtimeRequirements.has(RuntimeGlobals.module);
const needExports = runtimeRequirements.has(RuntimeGlobals.exports);
const needRequire =
runtimeRequirements.has(RuntimeGlobals.require) ||
runtimeRequirements.has(RuntimeGlobals.requireScope);
const needThisAsExports = runtimeRequirements.has(
RuntimeGlobals.thisAsExports
);
const needStrict =
/** @type {BuildInfo} */
(module.buildInfo).strict && !strictMode;
const cacheEntry = this._moduleFactoryCache.get(
moduleSourcePostContent
);
const renderShorthand =
renderInObject === true && runtimeTemplate.supportsMethodShorthand();
/** @type {Source} */
let source;
if (
cacheEntry &&
cacheEntry.needModule === needModule &&
cacheEntry.needExports === needExports &&
cacheEntry.needRequire === needRequire &&
cacheEntry.needThisAsExports === needThisAsExports &&
cacheEntry.needStrict === needStrict &&
cacheEntry.renderShorthand === renderShorthand
) {
source = cacheEntry.source;
} else {
const factorySource = new ConcatSource();
/** @type {string[]} */
const args = [];
if (needExports || needRequire || needModule) {
args.push(
needModule
? module.moduleArgument
: `__unused_webpack_${module.moduleArgument}`
);
}
if (needExports || needRequire) {
args.push(
needExports
? module.exportsArgument
: `__unused_webpack_${module.exportsArgument}`
);
}
if (needRequire) args.push(RuntimeGlobals.require);
if (renderShorthand) {
// we can optimize function to methodShorthand if render module factory in object
factorySource.add(`(${args.join(", ")}) {\n\n`);
} else if (
!needThisAsExports &&
runtimeTemplate.supportsArrowFunction()
) {
factorySource.add(`/***/ ((${args.join(", ")}) => {\n\n`);
} else {
factorySource.add(`/***/ (function(${args.join(", ")}) {\n\n`);
}
if (needStrict) {
factorySource.add('"use strict";\n');
}
factorySource.add(moduleSourcePostContent);
factorySource.add(`\n\n/***/ }${renderShorthand ? "" : ")"}`);
source = new CachedSource(factorySource);
this._moduleFactoryCache.set(moduleSourcePostContent, {
source,
needModule,
needExports,
needRequire,
needThisAsExports,
needStrict,
renderShorthand
});
}
moduleSourcePostContainer = tryRunOrWebpackError(
() => hooks.renderModuleContainer.call(source, module, renderContext),
"JavascriptModulesPlugin.getCompilationHooks().renderModuleContainer"
);
} else {
moduleSourcePostContainer = moduleSourcePostContent;
}
return tryRunOrWebpackError(
() =>
hooks.renderModulePackage.call(
moduleSourcePostContainer,
module,
renderContext
),
"JavascriptModulesPlugin.getCompilationHooks().renderModulePackage"
);
} catch (err) {
/** @type {WebpackError} */
(err).module = module;
throw err;
}
}
/**
* Renders the rendered source.
* @param {RenderContext} renderContext the render context
* @param {CompilationHooks} hooks hooks
* @returns {Source} the rendered source
*/
renderChunk(renderContext, hooks) {
const { chunk, chunkGraph, runtimeTemplate } = renderContext;
const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType(
chunk,
JAVASCRIPT_TYPE,
compareModulesByFullName(runtimeTemplate.compilation.compiler)
);
const allModules = modules ? [...modules] : [];
/** @type {undefined | string} */
let strictHeader;
let allStrict = renderContext.strictMode;
if (
!allStrict &&
allModules.every((m) => /** @type {BuildInfo} */ (m.buildInfo).strict)
) {
const strictBailout = hooks.strictRuntimeBailout.call(renderContext);
strictHeader = strictBailout
? `// runtime can't be in strict mode because ${strictBailout}.\n`
: '"use strict";\n';
if (!strictBailout) allStrict = true;
}
/** @type {ChunkRenderContext} */
const chunkRenderContext = {
...renderContext,
chunkInitFragments: [],
strictMode: allStrict
};
// ESM entry chunk whose runtime lives in a separate chunk: inline the
// single entry module at the top level (instead of the module registry)
// so its own top-level declarations stay live ESM bindings for consumers.
const inlinedEntryModule = this._getEsmEntryModuleToInline(
renderContext,
allModules
);
let registryModules = allModules;
if (inlinedEntryModule) {
const inlinedEntrySource = this.renderModule(
inlinedEntryModule,
{ ...chunkRenderContext, factory: false },
hooks
);
if (inlinedEntrySource) {
registryModules = allModules.filter((m) => m !== inlinedEntryModule);
chunkRenderContext.inlinedEntryModule = inlinedEntryModule;
chunkRenderContext.inlinedEntrySource = inlinedEntrySource;
}
}
const moduleSources =
Template.renderChunkModules(
chunkRenderContext,
registryModules,
(module, renderInObject) =>
this.renderModule(
module,
{ ...chunkRenderContext, factory: true, renderInObject },
hooks
)
) || new RawSource("{}");
let source = tryRunOrWebpackError(
() => hooks.renderChunk.call(moduleSources, chunkRenderContext),
"JavascriptModulesPlugin.getCompilationHooks().renderChunk"
);
source = tryRunOrWebpackError(
() => hooks.renderContent.call(source, chunkRenderContext),
"JavascriptModulesPlugin.getCompilationHooks().renderContent"
);
if (!source) {
throw new Error(
"JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderContent plugins should return something"
);
}
source = InitFragment.addToSource(
source,
chunkRenderContext.chunkInitFragments,
chunkRenderContext
);
source = tryRunOrWebpackError(
() => hooks.render.call(source, chunkRenderContext),
"JavascriptModulesPlugin.getCompilationHooks().render"
);
if (!source) {
throw new Error(
"JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().render plugins should return something"
);
}
chunk.rendered = true;
return strictHeader
? new ConcatSource(strictHeader, source, ";")
: renderContext.runtimeTemplate.isModule()
? source
: new ConcatSource(source, ";");
}
/**
* Returns the chunk's single entry module when it can be inlined at the top
* level of an ESM output chunk (instead of the module registry), which keeps
* its exports as live ESM bindings. Only a self-contained ESM entry whose
* top-level declarations can be lifted into the chunk scope qualifies;
* otherwise `undefined` is returned and the registry path is used.
* @param {RenderContext} renderContext render context
* @param {Module[]} allModules all javascript modules of the chunk
* @returns {Module | undefined} the entry module to inline, if any
*/
_getEsmEntryModuleToInline(renderContext, allModules) {
const {
chunk,
chunkGraph,
moduleGraph,
runtimeTemplate,
codeGenerationResults
} = renderContext;
// Only a single ESM entry whose runtime lives in a separate chunk.
if (
!runtimeTemplate.isModule() ||
chunk.hasRuntime() ||
chunkGraph.getNumberOfEntryModules(chunk) !== 1
) {
return undefined;
}
const [entryEntry] =
chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk);
const [entryModule, entrypoint] = /** @type {[Module, Entrypoint]} */ (
entryEntry
);
// Module-local safety: the body is lifted verbatim, so it must use the
// standard exports binding (not `module`/`this`) and expose top-level
// declarations whose names can't collide with runtime/startup identifiers.
const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements(
entryModule,
chunk.runtime
);
if (
entryModule.exportsArgument !== RuntimeGlobals.exports ||
runtimeRequirements.has(RuntimeGlobals.module) ||
runtimeRequirements.has(RuntimeGlobals.thisAsExports)
) {
return undefined;
}
const topLevelDeclarations =
codeGenerationResults.getData(
entryModule,
chunk.runtime,
"topLevelDeclarations"
) ||
(entryModule.buildInfo && entryModule.buildInfo.topLevelDeclarations);
if (!topLevelDeclarations || topLevelDeclarations.size === 0) {
return undefined;
}
for (const name of topLevelDeclarations) {
if (RESERVED_NAMES.has(name) || name.startsWith("__webpack_")) {
return undefined;
}
}
// Self-contained: the entry must not be imported by another module (which
// needs it in the registry), and must not require other chunks or
// `dependOn` parents to execute first.
if (
entrypoint.getParents().length > 0 ||
getAllChunks(entrypoint, chunk, entrypoint.getRuntimeChunk()).size > 0
) {
return undefined;
}
const referenced = someInIterable(
moduleGraph.getIncomingConnectionsByOriginModule(entryModule),
([originModule, connections]) =>
originModule &&
originModule !== entryModule &&
connections.some((c) => c.isTargetActive(chunk.runtime)) &&
someInIterable(
chunkGraph.getModuleRuntimes(originModule),
(runtime) => intersectRuntime(runtime, chunk.runtime) !== undefined
)
);
return referenced ? undefined : entryModule;
}
/**
* Renders the newly generated source from rendering.
* @param {MainRenderContext} renderContext options object
* @param {CompilationHooks} hooks hooks
* @param {Compilation} compilation the compilation
* @returns {Source} the newly generated source from rendering
*/
renderMain(renderContext, hooks, compilation) {
const { chunk, chunkGraph, runtimeTemplate } = renderContext;
const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk);
const iife = runtimeTemplate.isIIFE();
const bootstrap = this.renderBootstrap(renderContext, hooks);
const useSourceMap = hooks.useSourceMap.call(chunk, renderContext);
/** @type {Module[]} */
const allModules = [
...(chunkGraph.getOrderedChunkModulesIterableBySourceType(
chunk,
JAVASCRIPT_TYPE,
compareModulesByFullName(runtimeTemplate.compilation.compiler)
) || [])
];
const hasEntryModules = chunkGraph.getNumberOfEntryModules(chunk) > 0;
/** @type {Set<Module> | undefined} */
let inlinedModules;
if (bootstrap.allowInlineStartup && hasEntryModules) {
inlinedModules = new Set(chunkGraph.getChunkEntryModulesIterable(chunk));
}
const source = new ConcatSource();
/** @type {string} */
let prefix;
if (iife) {
if (runtimeTemplate.supportsArrowFunction()) {
source.add("/******/ (() => { // webpackBootstrap\n");
} else {
source.add("/******/ (function() { // webpackBootstrap\n");
}
prefix = "/******/ \t";
} else {
prefix = "/******/ ";
}
let allStrict = renderContext.strictMode;
if (
!allStrict &&
allModules.every((m) => /** @type {BuildInfo} */ (m.buildInfo).strict)
) {
const strictBailout = hooks.strictRuntimeBailout.call(renderContext);
if (strictBailout) {
source.add(
`${prefix}// runtime can't be in strict mode because ${strictBailout}.\n`
);
} else {
allStrict = true;
source.add(`${prefix}"use strict";\n`);
}
}
/** @type {ChunkRenderContext} */
const chunkRenderContext = {
...renderContext,
chunkInitFragments: [],
strictMode: allStrict
};
const chunkModules = Template.renderChunkModules(
chunkRenderContext,
inlinedModules
? allModules.filter(
(m) => !(/** @type {Set<Module>} */ (inlinedModules).has(m))
)
: allModules,
(module, renderInObject) =>
this.renderModule(
module,
{ ...chunkRenderContext, factory: true, renderInObject },
hooks
),
prefix
);
if (
chunkModules ||
runtimeRequirements.has(RuntimeGlobals.moduleFactories) ||
runtimeRequirements.has(RuntimeGlobals.moduleFactoriesAddOnly) ||
runtimeRequirements.has(RuntimeGlobals.require)
) {
source.add(`${prefix}var __webpack_modules__ = (`);
source.add(chunkModules || "{}");
source.add(");\n");
source.add(
"/************************************************************************/\n"
);
}
if (bootstrap.header.length > 0) {
const header = `${Template.asString(bootstrap.header)}\n`;
source.add(
new PrefixSource(
prefix,
useSourceMap
? new OriginalSource(header, "webpack/bootstrap")
: new RawSource(header)
)
);
source.add(
"/************************************************************************/\n"
);
}
const runtimeModules =
renderContext.chunkGraph.getChunkRuntimeModulesInOrder(chunk);
if (runtimeModules.length > 0) {
source.add(
new PrefixSource(
prefix,
Template.renderRuntimeModules(runtimeModules, chunkRenderContext)
)
);
source.add(
"/************************************************************************/\n"
);
// runtimeRuntimeModules calls codeGeneration
for (const module of runtimeModules) {
compilation.codeGeneratedModules.add(module);
}
}
if (inlinedModules) {
if (bootstrap.beforeStartup.length > 0) {
const beforeStartup = `${Template.asString(bootstrap.beforeStartup)}\n`;
source.add(
new PrefixSource(
prefix,
useSourceMap
? new OriginalSource(beforeStartup, "webpack/before-startup")
: new RawSource(beforeStartup)
)
);
}
const lastInlinedModule = /** @type {Module} */ (last(inlinedModules));
const startupSource = new ConcatSource();
const avoidEntryIife = compilation.options.optimization.avoidEntryIife;
/** @type {Map<Module, Source> | false} */
let renamedInlinedModule = false;
let inlinedInIIFE = false;
if (avoidEntryIife) {
renamedInlinedModule = this._getRenamedInlineModule(
compilation,
allModules,
renderContext,
inlinedModules,
chunkRenderContext,
hooks,
allStrict,
Boolean(chunkModules)
);
}
for (const m of inlinedModules) {
const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements(
m,
chunk.runtime
);
const exports = runtimeRequirements.has(RuntimeGlobals.exports);
const webpackExports =
exports && m.exportsArgument === RuntimeGlobals.exports;
const innerStrict =
!allStrict && /** @type {BuildInfo} */ (m.buildInfo).strict;
const iife = innerStrict
? "it needs to be in strict mode."
: inlinedModules.size > 1 && !renamedInlinedModule
? "it needs to be isolated against other entry modules."
: chunkModules && !renamedInlinedModule
? "it needs to be isolated against other modules in the chunk."
: exports && !webpackExports
? `it uses a non-standard name for the exports (${m.exportsArgument}).`
: hooks.embedInRuntimeBailout.call(m, renderContext);
if (iife) {
inlinedInIIFE = true;
}
const renderedModule = renamedInlinedModule
? renamedInlinedModule.get(m)
: this.renderModule(
m,
{
...chunkRenderContext,
factory: false,
inlinedInIIFE
},
hooks
);
if (renderedModule) {
/** @type {string} */
let footer;
if (iife !== undefined) {
startupSource.add(
`// This entry needs to be wrapped in an IIFE because ${iife}\n`
);
const arrow = runtimeTemplate.supportsArrowFunction();
if (arrow) {
startupSource.add("(() => {\n");
footer = "\n})();\n\n";
} else {
startupSource.add("!function() {\n");
footer = "\n}();\n";
}
if (innerStrict) startupSource.add('"use strict";\n');
} else {
footer = "\n";
}
if (exports) {
if (m !== lastInlinedModule) {
startupSource.add(
`${runtimeTemplate.renderLet()} ${m.exportsArgument} = {};\n`
);
} else if (m.exportsArgument !== RuntimeGlobals.exports) {
startupSource.add(
`${runtimeTemplate.renderLet()} ${m.exportsArgument} = ${
RuntimeGlobals.exports
};\n`
);
}
}
startupSource.add(renderedModule);
startupSource.add(footer);
}
}
if (runtimeRequirements.has(RuntimeGlobals.onChunksLoaded)) {
startupSource.add(
`${RuntimeGlobals.exports} = ${RuntimeGlobals.onChunksLoaded}(${RuntimeGlobals.exports});\n`
);
}
/** @type {StartupRenderContext} */
const startupRenderContext = {
...renderContext,
inlined: true,
inlinedInIIFE,
needExportsDeclaration: runtimeRequirements.has(RuntimeGlobals.exports)
};
let renderedStartup = hooks.renderStartup.call(
startupSource,
lastInlinedModule,
startupRenderContext
);
const lastInlinedModuleRequirements =
chunkGraph.getModuleRuntimeRequirements(
lastInlinedModule,
chunk.runtime
);
if (
// `onChunksLoaded` reads and reassigns `__webpack_exports__`
runtimeRequirements.has(RuntimeGlobals.onChunksLoaded) ||
// Top-level `__webpack_exports__` will be returned
runtimeRequirements.has(RuntimeGlobals.returnExportsFromRuntime) ||
// Custom exports argument aliases from `__webpack_exports__`
(lastInlinedModuleRequirements.has(RuntimeGlobals.exports) &&
lastInlinedModule.exportsArgument !== RuntimeGlobals.exports)
) {
startupRenderContext.needExportsDeclaration = true;
}
if (startupRenderContext.needExportsDeclaration) {
renderedStartup = new ConcatSource(
`${runtimeTemplate.renderLet()} ${RuntimeGlobals.exports} = {};\n`,
renderedStartup
);
}
source.add(renderedStartup);
if (bootstrap.afterStartup.length > 0) {
const afterStartup = `${Template.asString(bootstrap.afterStartup)}\n`;
source.add(
new PrefixSource(
prefix,
useSourceMap
? new OriginalSource(afterStartup, "webpack/after-startup")
: new RawSource(afterStartup)
)
);
}
} else {
const lastEntryModule =
/** @type {Module} */
(last(chunkGraph.getChunkEntryModulesIterable(chunk)));
/** @type {(content: string[], name: string) => Source} */
const toSource = useSourceMap
? (content, name) =>
new OriginalSource(Template.asString(content), name)
: (content) => new RawSource(Template.asString(content));
source.add(
new PrefixSource(
prefix,
new ConcatSource(
toSource(bootstrap.beforeStartup, "webpack/before-startup"),
"\n",
hooks.renderStartup.call(
toSource([...bootstrap.startup, ""], "webpack/startup"),
lastEntryModule,
{
...renderContext,
inlined: false,
needExportsDeclaration: true
}
),
toSource(bootstrap.afterStartup, "webpack/after-startup"),
"\n"
)
)
);
}
if (
hasEntryModules &&
runtimeRequirements.has(RuntimeGlobals.returnExportsFromRuntime)
) {
source.add(`${prefix}return ${RuntimeGlobals.exports};\n`);
}
if (iife) {
source.add("/******/ })()\n");
}
/** @type {Source} */
let finalSource = tryRunOrWebpackError(
() => hooks.renderMain.call(source, renderContext),
"JavascriptModulesPlugin.getCompilationHooks().renderMain"
);
if (!finalSource) {
throw new Error(
"JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderMain plugins should return something"
);
}
finalSource = tryRunOrWebpackError(
() => hooks.renderContent.call(finalSource, renderContext),
"JavascriptModulesPlugin.getCompilationHooks().renderContent"
);
if (!finalSource) {
throw new Error(
"JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderContent plugins should return something"
);
}
finalSource = InitFragment.addToSource(
finalSource,
chunkRenderContext.chunkInitFragments,
chunkRenderContext
);
finalSource = tryRunOrWebpackError(
() => hooks.render.call(finalSource, renderContext),
"JavascriptModulesPlugin.getCompilationHooks().render"
);
if (!finalSource) {
throw new Error(
"JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().render plugins should return something"
);
}
chunk.rendered = true;
return iife ? new ConcatSource(finalSource, ";") : finalSource;
}
/**
* Updates hash with bootstrap.
* @param {Hash} hash the hash to be updated
* @param {RenderBootstrapContext} renderContext options object
* @param {CompilationHooks} hooks hooks
*/
updateHashWithBootstrap(hash, renderContext, hooks) {
const bootstrap = this.renderBootstrap(renderContext, hooks);
for (const _k of Object.keys(bootstrap)) {
const key = /** @type {keyof Bootstrap} */ (_k);
hash.update(key);
if (Array.isArray(bootstrap[key])) {
for (const line of bootstrap[key]) {
hash