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.
2,342 lines • 76 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const vm = require("vm");
const { SyncBailHook, SyncHook, SyncWaterfallHook } = require("tapable");
const {
CachedSource,
ConcatSource,
OriginalSource,
PrefixSource,
RawSource,
ReplaceSource
} = require("webpack-sources");
/**
* @import Compilation, {
* ChunkHashContext,
* ExecuteModuleObject,
* WebpackRequire
* } from "../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 {
CHUNK_RUNTIME_DECLARATIONS,
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 { removeBOMFromResult } = require("../util/removeBOM");
const { intersectRuntime } = require("../util/runtime");
const { getAllChunks } = require("./ChunkHelpers");
const JavascriptGenerator = require("./JavascriptGenerator");
const JavascriptModule = require("./JavascriptModule");
const JavascriptParser = require("./JavascriptParser");
const analyzeScope = require("./ScopeAnalyzer");
/**
* @import {
* Program,
* Node,
* Identifier,
* CatchClause,
* ClassDeclaration,
* ClassExpression,
* FunctionDeclaration,
* FunctionExpression,
* ArrowFunctionExpression,
* VariableDeclarator,
* VariableDeclaration,
* ImportDeclaration,
* ImportSpecifier,
* ImportDefaultSpecifier,
* ImportNamespaceSpecifier,
* AssignmentExpression,
* ForInStatement,
* ForOfStatement
* } from "estree"
*/
/** @import { Source } from "webpack-sources" */
/**
* @import {
* OutputNormalizedWithDefaults as OutputOptions
* } from "../config/defaults"
*/
/** @import Chunk, { ChunkFilenameTemplate } from "../Chunk" */
/** @import ChunkGraph, { EntryModuleWithChunkGroup } from "../ChunkGraph" */
/** @import CodeGenerationResults from "../CodeGenerationResults" */
/** @import Compiler from "../Compiler" */
/** @import DependencyTemplates from "../DependencyTemplates" */
/** @import Entrypoint from "../Entrypoint" */
/** @import Module, { BuildInfo, CodeGenerationResultData } from "../Module" */
/** @import { RuntimeSpec } from "../util/runtime" */
/** @import ModuleGraph from "../ModuleGraph" */
/** @import RuntimeTemplate from "../RuntimeTemplate" */
/** @import WebpackError from "../errors/WebpackError" */
/** @import { Range } from "../javascript/JavascriptParser" */
/** @import Hash from "../util/Hash" */
/** @import { ScopeSet, UsedNamesInScopeInfo } from "../util/concatenate" */
/** @typedef {import("./ScopeAnalyzer").Scope} Scope */
/** @typedef {import("./ScopeAnalyzer").Variable} Variable */
/** @typedef {import("./ScopeAnalyzer").Reference} Reference */
/** @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
*/
const createCompilationHooks = () => ({
/**
* @type {SyncWaterfallHook<[Source, Module, ModuleRenderContext]>}
*/
renderModuleContent: new SyncWaterfallHook([
"source",
"module",
"moduleRenderContext"
]),
/**
* @type {SyncWaterfallHook<[Source, Module, ModuleRenderContext]>}
*/
renderModuleContainer: new SyncWaterfallHook([
"source",
"module",
"moduleRenderContext"
]),
/**
* @type {SyncWaterfallHook<[Source, Module, ModuleRenderContext]>}
*/
renderModulePackage: new SyncWaterfallHook([
"source",
"module",
"moduleRenderContext"
]),
/**
* @type {SyncWaterfallHook<[Source, RenderContext]>}
*/
render: new SyncWaterfallHook(["source", "renderContext"]),
/**
* @type {SyncWaterfallHook<[Source, RenderContext]>}
* @since 5.41.0
*/
renderContent: new SyncWaterfallHook(["source", "renderContext"]),
/**
* @type {SyncWaterfallHook<[Source, Module, StartupRenderContext]>}
* @since 5.22.0
*/
renderStartup: new SyncWaterfallHook([
"source",
"module",
"startupRenderContext"
]),
/**
* @type {SyncWaterfallHook<[Source, RenderContext]>}
*/
renderChunk: new SyncWaterfallHook(["source", "renderContext"]),
/**
* @type {SyncWaterfallHook<[Source, RenderContext]>}
*/
renderMain: new SyncWaterfallHook(["source", "renderContext"]),
/**
* @type {SyncWaterfallHook<[string, RenderBootstrapContext]>}
*/
renderRequire: new SyncWaterfallHook(["code", "renderContext"]),
/**
* @type {SyncBailHook<[Module, Partial<RenderBootstrapContext>], string | void>}
* @since 5.22.0
*/
inlineInRuntimeBailout: new SyncBailHook(["module", "renderContext"]),
/**
* Offers a module's rendered export-definition call to a consumer that can place
* it better — a library whose entry exports the same bindings natively takes it
* over and emits it only where the module is wrapped. Return true to take it.
* @type {SyncBailHook<[Module, RuntimeSpec, string, boolean], boolean | void>}
*/
onDemandExportsGeneration: new SyncBailHook([
"module",
"runtime",
"source",
"placeAtEnd"
]),
/**
* @type {SyncBailHook<[Module, RenderContext], string | void>}
* @since 5.22.0
*/
embedInRuntimeBailout: new SyncBailHook(["module", "renderContext"]),
/**
* @type {SyncBailHook<[RenderContext], string | void>}
* @since 5.26.1
*/
strictRuntimeBailout: new SyncBailHook(["renderContext"]),
/**
* @type {SyncHook<[Chunk, Hash, ChunkHashContext]>}
*/
chunkHash: new SyncHook(["chunk", "hash", "context"]),
/**
* @type {SyncBailHook<[Chunk, RenderContext], boolean | void>}
* @since 5.2.1
*/
useSourceMap: new SyncBailHook(["chunk", "renderContext"])
});
/**
* @typedef {ReturnType<typeof createCompilationHooks>} CompilationHooks
*/
/**
* 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) =>
module.type === type ? removeBOMFromResult(result) : 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)
) {
// a non-arrow bootstrap IIFE is called without a receiver, so in strict
// mode the `this` that `output.globalObject` reads would be undefined
const globalObjectNeedsThis =
iife &&
!runtimeTemplate.supportsArrowFunction() &&
runtimeTemplate.globalObjectUsesThis();
const strictBailout =
hooks.strictRuntimeBailout.call(renderContext) ||
(globalObjectNeedsThis
? "'output.globalObject' reads 'this'"
: undefined);
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.update(line);
}
} else {
hash.update(JSON.stringify(bootstrap[key]));
}
}
}
/**
* Renders the generated source of the bootstrap code.
* @param {RenderBootstrapContext} renderContext options object
* @param {CompilationHooks} hooks hooks
* @returns {Bootstrap} the generated source of the bootstrap code
*/
renderBootstrap(renderContext, hooks) {
const {
chunkGraph,
codeGenerationResults,
moduleGraph,
chunk,
runtimeTemplate
} = renderContext;
const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk);
const requireFunction = runtimeRequirements.has(RuntimeGlobals.require);
const moduleCache = runtimeRequirements.has(RuntimeGlobals.moduleCache);
const moduleFactories = runtimeRequirements.has(
RuntimeGlobals.moduleFactories
);
const moduleUsed = runtimeRequirements.has(RuntimeGlobals.module);
const requireScopeUsed = runtimeRequirements.has(
RuntimeGlobals.requireScope
);
const interceptModuleExecution = runtimeRequirements.has(
RuntimeGlobals.interceptModuleExecution
);
const useRequire =
requireFunction || interceptModuleExecution || moduleUsed;
/**
* @type {{ startup: string[], beforeStartup: string[], header: string[], afterStartup: string[], allowInlineStartup: boolean }}
*/
const result = {
header: [],
beforeStartup: [],
startup: [],
afterStartup: [],
allowInlineStartup: true
};
const { header: buf, startup, beforeStartup, afterStartup } = result;
if (result.allowInlineStartup && moduleFactories) {
startup.push(
"// module factories are used so entry inlining is disabled"
);
result.allowInlineStartup = false;
}
if (result.allowInlineStartup && moduleCache) {
startup.push("// module cache are used so entry inlining is disabled");
result.allowInlineStartup = false;
}
if (result.allowInlineStartup && interceptModuleExecution) {
startup.push(
"// module execution is intercepted so entry inlining is disabled"
);
result.allowInlineStartup = false;
}
const bootstrapCst = runtimeTemplate.renderConst();
if (useRequire || moduleCache) {
buf.push("// The module cache");
buf.push(`${bootstrapCst} __webpack_module_cache__ = {};`);
buf.push("");
}
if (runtimeRequirements.has(RuntimeGlobals.makeDeferredNamespaceObject)) {
// in order to optimize of DeferredNamespaceObject, we remove all proxy handlers after the module initialize
// (see MakeDeferredNamespaceObjectRuntimeModule)
// This requires all deferred imports to a module can get the module export object before the module
// is evaluated.
buf.push("// The deferred module cache");
buf.push(`${bootstrapCst} __webpack_module_deferred_exports__ = {};`);
// Per the TC39 import-defer spec, every defer-import call site for
// the same module must yield the same Deferred Module Namespace
// Exotic Object (and a distinct one from any eager namespace).
// Cache the deferred namespace proxy here so calls from different
// files share identity.
buf.push("// The deferred namespace cache");
buf.push(
`${bootstrapCst} __webpack_module_deferred_namespace_cache__ = {};`
);
buf.push("");
}
if (useRequire) {
buf.push("// The require function");
buf.push(`function ${RuntimeGlobals.require}(moduleId) {`);
buf.push(Template.indent(this.renderRequire(renderContext, hooks)));
buf.push("}");
buf.push("");
} else if (runtimeRequirements.has(RuntimeGlobals.requireScope)) {
buf.push("// The require scope");
buf.push(`${bootstrapCst} ${RuntimeGlobals.require} = {};`);
buf.push("");
}
if (
moduleFactories ||
runtimeRequirements.has(RuntimeGlobals.moduleFactoriesAddOnly)
) {
buf.push("// expose the modules object (__webpack_modules__)");
buf.push(`${RuntimeGlobals.moduleFactories} = __webpack_modules__;`);
buf.push("");
}
if (moduleCache) {
buf.push("// expose the module cache");
buf.push(`${RuntimeGlobals.moduleCache} = __webpack_module_cache__;`);
buf.push("");
}
if (interceptModuleExecution) {
buf.push("// expose the module execution interceptor");
buf.push(`${RuntimeGlobals.interceptModuleExecution} = [];`);
buf.push("");
}
if (!runtimeRequirements.has(RuntimeGlobals.startupNoDefault)) {
if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
/** @type {string[]} */
const buf2 = [];
const runtimeRequirements =
chunkGraph.getTreeRuntimeRequirements(chunk);
buf2.push("// Load entry module and return exports");
/** @type {EntryModuleWithChunkGroup[]} */
const jsEntries = [];
for (const [
entryModule,
entrypoint
] of chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)) {
if (
chunkGraph.getModuleSourceTypes(entryModule).has(JAVASCRIPT_TYPE)
) {
jsEntries.push([entryModule, entrypoint]);
continue;
}
}
// An async entry is not awaited here: that needs top-level `await`,
// gated on an option held back from the patch release (revert #21881).
let i = jsEntries.length;
for (const [entryModule, entrypoint] of jsEntries) {
const chunks =
/** @type {Entrypoint} */
(entrypoint).chunks.filter((c) => c !== chunk);
if (result.allowInlineStartup && chunks.length > 0) {
buf2.push(
"// This entry module depends on other loaded chunks and execution need to be delayed"
);
result.allowInlineStartup = false;
}
if (
result.allowInlineStartup &&
someInIterable(
moduleGraph.getIncomingConnectionsByOriginModule(entryModule),
([originModule, connections]) =>
originModule &&
connections.some((c) => c.isTargetActive(chunk.runtime)) &&
someInIterable(
chunkGraph.getModuleRuntimes(originModule),
(runtime) =>
intersectRuntime(runtime, chunk.runtime) !== undefined
)
)
) {
buf2.push(
"// This entry module is referenced by other modules so it can't be inlined"
);
result.allowInlineStartup = false;
}
/** @type {undefined | CodeGenerationResultData} */
let data;
if (codeGenerationResults.has(entryModule, chunk.runtime)) {
const result = codeGenerationResults.get(
entryModule,
chunk.runtime
);
data = result.data;
}
if (
result.allowInlineStartup &&
(!data || !data.get("topLevelDeclarations")) &&
(!entryModule.buildInfo ||
!entryModule.buildInfo.topLevelDeclarations)
) {
buf2.push(
"// This entry module doesn't tell about it's top-level declarations so it can't be inlined"
);
result.allowInlineStartup = false;
}
if (result.allowInlineStartup) {
// the bail-out above already established that one of the two
// sources carries them
const topLevelDeclarations =
(data && data.get("topLevelDeclarations")) ||
/** @type {BuildInfo} */ (entryModule.buildInfo)
.topLevelDeclarations;
for (const name of CHUNK_RUNTIME_DECLARATIONS) {
if (/** @type {Set<string>} */ (topLevelDeclarations).has(name)) {
buf2.push(
`// This entry module declares '${name}' on top-level, which the runtime also declares, so it can't be inlined`
);
result.allowInlineStartup = false;
break;
}
}
}
if (result.allowInlineStartup) {
const bailout = hooks.inlineInRuntimeBailout.call(
entryModule,
renderContext
);
if (bailout !== undefined) {
buf2.push(
`// This entry module can't be inlined because ${bailout}`
);
result.allowInlineStartup = false;
}
}
i--;
const moduleId = chunkGraph.getModuleId(entryModule);
const entryRuntimeRequirements =
chunkGraph.getModuleRuntimeRequirements(entryModule, chunk.runtime);
let moduleIdExpr = JSON.stringify(moduleId);
if (runtimeRequirements.has(RuntimeGlobals.entryModuleId)) {
moduleIdExpr = `${RuntimeGlobals.entryModuleId} = ${moduleIdExpr}`;
}
if (
result.allowInlineStartup &&
entryRuntimeRequirements.has(RuntimeGlobals.module)
) {
result.allowInlineStartup = false;
buf2.push(
"// This entry module used 'module' so it can't be inlined"
);
}
if (
result.allowInlineStartup &&
entryRuntimeRequirements.has(RuntimeGlobals.thisAsExports)
) {
buf2.push(
"// This entry module used `this` as exports so it can't be inlined"
);
result.allowInlineStartup = false;
}
// `__webpack_exports__` may be reassigned later — by the
// `onChunksLoaded` wrapper below, by an inlined entry
// module that uses top-level `await` (the emitted
// bridge reassigns `__webpack_exports__` to the awaited
// value), or by other downstream codegen. Always declare
// with `let` (or `var` if `let` is unsupported) so
// reassignment is allowed.
const exportsDecl = runtimeTemplate.renderLet();
if (chunks.length > 0) {
buf2.push(
`${i === 0 ? `${exportsDecl} ${RuntimeGlobals.exports} = ` : ""}${
RuntimeGlobals.onChunksLoaded
}(undefined, ${JSON.stringify(
chunks.map((c) => c.id)
)}, ${runtimeTemplate.returningFunction(
`${RuntimeGlobals.require}(${moduleIdExpr})`
)})`
);
} else if (useRequire) {
buf2.push(
`${i === 0 ? `${exportsDecl} ${RuntimeGlobals.exports} = ` : ""}${
RuntimeGlobals.require
}(${moduleIdExpr});`
);
} else {
if (i === 0) {
buf2.push(`${exportsDecl} ${RuntimeGlobals.exports} = {};`);
}
const needThisAsExports = entryRuntimeRequirements.has(
RuntimeGlobals.thisAsExports
);
/** @type {string[]} */
const args = [];
if (
requireScopeUsed ||
entryRuntimeRequirements.has(RuntimeGlobals.exports)
) {
const exportsArg = i === 0 ? RuntimeGlobals.exports : "{}";
args.push("0", exportsArg);
if (requireScopeUsed) {
args.push(RuntimeGlobals.require);
}
}
buf2.push(
Template.asString(
(() => {
if (needThisAsExports) {
const comma = args.length ? "," : "";
return `__webpack_modules__[${moduleIdExpr}].call(${
RuntimeGlobals.exports
}${comma}${args.join(",")});`;
}
return `__webpack_modules__[${moduleIdExpr}](${args.join(
","
)});`;
})()
)
);
}
}
if (runtimeRequirements.has(RuntimeGlobals.onChunksLoaded)) {
buf2.push(
`${RuntimeGlobals.exports} = ${RuntimeGlobals.onChunksLoaded}(${RuntimeGlobals.exports});`
);
}
if (
runtimeRequirements.has(RuntimeGlobals.startup) ||
(runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) &&
runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter))
) {
result.allowInlineStartup = false;
buf.push("// the startup function");
buf.push(
`${RuntimeGlobals.startup} = ${runtimeTemplate.basicFunction("", [
...buf2,
`return ${RuntimeGlobals.exports};`
])};`
);
buf.push("");
startup.push("// run startup");
startup.push(
`var ${RuntimeGlobals.exports} = ${RuntimeGlobals.startup}();`
);
} else if (runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore)) {
buf.push("// the startup function");
buf.push(
`${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};`
);
beforeStartup.push("// run runtime startup");
beforeStartup.push(`${RuntimeGlobals.startup}();`);
startup.push("// startup");
startup.push(Template.asString(buf2));
} else if (runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter)) {
buf.push("// the startup function");
buf.push(
`${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};`
);
startup.push("// startup");
startup.push(Template.asString(buf2));
afterStartup.push("// run runtime startup");
afterStartup.push(`${RuntimeGlobals.startup}();`);
} else {
startup.push("// startup");
startup.push(Template.asString(buf2));
}
} else if (
runtimeRequirements.has(RuntimeGlobals.startup) ||
runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) ||
runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter)
) {
buf.push(
"// the startup function",
"// It's empty as no entry modules are in this chunk",
`${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};`,
""
);
}
} else if (
runtimeRequirements.has(RuntimeGlobals.startup) ||
runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) ||
runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter)
) {
result.allowInlineStartup = false;
buf.push(
"// the startup function",
"// It's empty as some runtime module handles the default behavior",
`${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};`
);
startup.push("// run startup");
startup.push(
`var ${RuntimeGlobals.exports} = ${RuntimeGlobals.startup}();`
);
}
return result;
}
/**
* Renders the generated source of the require function.
* @param {RenderBootstrapContext} renderContext options object
* @param {CompilationHooks} hooks hooks
* @returns {string} the generated source of the require function
*/
renderRequire(renderContext, hooks) {
const { chunk, chunkGraph, runtimeTemplate } = renderContext;
const { outputOptions } = runtimeTemplate;
const cst = runtimeTemplate.renderConst();
const lt = runtimeTemplate.renderLet();
const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk);
/**
* Renders missing module error.
* @param {string} condition guard expression
* @returns {string[]} source
*/
const renderMissingModuleError = (condition) =>
outputOptions.strictModuleResolution
? [
`if (${condition}) {`,
Template.indent([
"delete __webpack_module_cache__[moduleId];",
`${cst} e = new Error("Cannot find module '" + moduleId + "'");`,
"e.code = 'MODULE_NOT_FOUND';",
"throw e;"
]),
"}"
]
: [];
const moduleExecution = runtimeRequirements.has(
RuntimeGlobals.interceptModuleExecution
)
? Template.asString([
`${cst} execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: ${RuntimeGlobals.require} };`,
`${RuntimeGlobals.interceptModuleExecution}.forEach(function(handler) { handler(execOptions); });`,
...renderMissingModuleError("!execOptions.factory"),
"module = execOptions.module;",
"execOptions.factory.call(module.exports, module, module.exports, execOptions.require);"
])
: runtimeRequirements.has(RuntimeGlobals.thisAsExports)
? Template.asString([
...renderMissingModuleError("!(moduleId in __webpack_modules__)"),
`__webpack_modules__[moduleId].call(module.exports, module, module.exports, ${RuntimeGlobals.require});`
])
: Template.asString([
...renderMissingModuleError("!(moduleId in __webpack_modules__)"),
`__webpack_modules__[moduleId](module, module.exports, ${RuntimeGlobals.require});`
]);
const needModuleId = runtimeRequirements.has(RuntimeGlobals.moduleId);
const needModuleLoaded = runtimeRequirements.has(
RuntimeGlobals.moduleLoaded
);
const needModuleDefer = runtimeRequirements.has(
RuntimeGlobals.makeDeferredNamespaceObject
);
// Both deferred-namespace runtimes re-invoke `__webpack_require__` on
// access, so a module's evaluation error must be retained and re-thrown
// (a Cyclic Module Record's [[EvaluationError]]); the optimized variant
// does not use `__webpack_module_deferred_exports__`, so it is tracked
// separately from `needModuleDefer`.
const needModuleDeferAny =
needModuleDefer ||
runtimeRequirements.has(
RuntimeGlobals.makeOptimizedDeferredNamespaceObject
);
const needModuleErrorCaching =
outputOptions.strictModuleErrorHandling || needModuleDeferAny;
// `module` is reassigned by the interceptModuleExecution path; use `let`.
const moduleDecl = runtimeRequirements.has(
RuntimeGlobals.interceptModuleExecution
)
? lt
: cst;
const content = Template.asString([
"// Check if module is in cache",
`${cst} cachedModule = __webpack_module_cache__[moduleId];`,
"if (cachedModule !== undefined) {",
needModuleErrorCaching
? Template.indent([
"if (cachedModule.error !== undefined) throw cachedModule.error;",
"return cachedModule.exports;"
])
: Template.indent("return cachedModule.exports;"),
"}",
"// Create a new module (and put it into the cache)",
`${moduleDecl} module = __webpack_module_cache__[moduleId] = {`,
Template.indent([
needModuleId ? "id: moduleId," : "// no module.id needed",
needModuleLoaded ? "loaded: false," : "// no module.loaded needed",
needModuleDefer
? "exports: __webpack_module_deferred_exports__[moduleId] || {}"
: "exports: {}"
]),
"};",
"",
outputOptions.strictModuleExceptionHandling && !needModuleErrorCaching
? Template.asString([
"// Execute the module function",
`${lt} threw = true;`,
"try {",
Template.indent([
moduleExecution,
"threw = false;",
...(needModuleDefer
? ["delete __webpack_module_deferred_exports__[moduleId];"]
: [])
]),
"} finally {",
Template.indent([
"if(threw) delete __webpack_module_cache__[moduleId];"
]),
"}"
])
: needModuleErrorCaching
? Template.asString([
"// Execute the module function",
// Mark the module as evaluating so a deferred namespace that
// forces evaluation of an already-evaluating module can throw
// instead of exposing its partial exports.
...(needModuleDeferAny ? ["module.evaluating = true;"] : []),
"try {",
Template.indent([
moduleExecution,
...(needModuleDefer
? ["delete __webpack_module_deferred_exports__[moduleId];"]
: []),
...(needModuleDeferAny ? ["module.evaluating = false;"] : [])
]),
"} catch(e) {",
Template.indent([
...(needModuleDeferAny ? ["module.evaluating = false;"] : []),
"module.error = e;",
"throw e;"
]),
"}"
])
: Template.asString([
"// Execute the module function",
moduleExecution,
...(needModuleDefer
? ["delete __webpack_module_deferred_exports__[moduleId];"]
: [])
]),
needModuleLoaded
? Template.asString([
"",
"// Flag the module as loaded",
`${RuntimeGlobals.moduleLoaded} = true;`,
""
])
: "",
"// Return the exports of the module",
"return module.exports;"
]);
return tryRunOrWebpackError(
() => hooks.renderRequire.call(content, renderContext),
"JavascriptModulesPlugin.getCompilationHooks().renderRequire"
);
}
/**
* Get renamed inline module.
* @param {Compilation} compilation compilation
* @param {Module[]} allModules allModules
* @param {MainRenderContext} renderContext renderContext
* @param {Set<Module>} inlinedModules inlinedModules
* @param {ChunkRenderContext} chunkRenderContext chunkRenderContext
* @param {CompilationHooks} hooks hooks
* @param {boolean | undefined} allStrict allStrict
* @param {boolean} hasChunkModules hasChunkModules
* @returns {Map<Module, Source> | false} renamed inlined modules
*/
_getRenamedInlineModule(
compilation,
allModules,
renderContext,
inlinedModules,
chunkRenderContext,
hooks,
allStrict,
hasChunkModules
) {
const innerStrict =
!allStrict &&
allModules.every((m) => /** @type {BuildInfo} */ (m.buildInfo).strict);
const isMultipleEntries = inlinedModules.size > 1;
const singleEntryWithModules = inlinedModules.size === 1 && hasChunkModules;
// This step is before the IIFE reason calculation; it only runs for the
// cases whose IIFE reason it can remove: multiple inlined entry modules, or a
// single inlined entry alongside other chunk modules. The remaining reason
// ('it uses a non-standard name for the exports') is not handled here.
if (innerStrict || (!isMultipleEntries && !singleEntryWithModules)) {
return false;
}
/** @type {Map<Module, Source>} */
const renamedInlinedModules = new Map();
const { runtimeTemplate } = renderContext;
/** @typedef {{ source: Source, module: Module, ast: Program, variables: Set<Variable>, through: Set<Reference>, usedInNonInlined: Set<Variable>, moduleScope: Scope }} Info */
/** @type {Map<Module, Info>} */
const inlinedModulesToInfo = new Map();
/** @type {Set<string>} */
const nonInlinedModuleThroughIdentifiers = new Set();
/** @type {Map<Module, Source>} */
const inlinedModuleSources = new Map();
for (const m of allModules) {
const isInlinedModule = inlinedModules && inlinedModules.has(m);
const moduleSource = this.renderModule(
m,
{
...chunkRenderContext,
factory: !isInlinedModule,
inlinedInIIFE: false
},
hooks
);
if (!moduleSource) continue;
if (isInlinedModule) {
// Parsing the inlined entry (often the whole concatenated app) is
// expensive; defer it until we know renaming is actually needed.
inlinedModuleSources.set(m, moduleSource);
continue;
}
const code = /** @type {string} */ (moduleSource.source());
const { experiments } = compilation.options;
const { ast } = JavascriptParser._parse(
code,
{
sourceType: "auto",
ranges: true,
// generated code contains phase imports when the experiments are on
importPhases: Boolean(
experiments.deferImport || experiments.sourceImport
)
},
JavascriptParser._getModuleParseFunction(compilation, m)
);
const analysis = analyzeScope(ast);
for (const ref of analysis.unresolvedReferences) {
nonInlinedModuleThroughIdentifiers.add(ref.identifier.name);
}
}
// Fast path: codegen reports each inlined entry's rendered top-level
// declarations and free names (post-concatenation), so when no declaration
// collides with a free name of any other module, another entry's
// declarations, or a reserved name, no renaming is needed and the entries
// don't have to be parsed at all.
fastPath: {
const { codeGenerationResults, chunk } = renderContext;
/** @type {Set<string>} */
const usedNames = new Set(nonInlinedModuleThroughIdentifiers);
for (const name of RESERVED_NAMES) usedNames.add(name);
/** @type {Set<string>[]} */
const declarationsList = [];
for (const m of inlinedModuleSources.keys()) {
if (!codeGenerationResults.has(m, chunk.runtime)) break fastPath;
const result = codeGenerationResults.get(m, chunk.runtime);
const data = result.data;
if (!data) break fastPath;
const declarations = data.get("topLevelDeclarations");
if (!declarations) break fastPath;
declarationsList.push(declarations);
if (!isMultipleEntries) continue;
// Entries share the startup scope: an entry's free names and runtime
// globals must not be shadowed by another entry's declarations.
const freeNames = data.get("freeNames");
if (!freeNames) break fastPath;
for (const name of freeNames) usedNames.add(name);
if (result.runtimeRequirements) {
for (const req of result.runtimeRequirements) {
const dot = req.indexOf(".");
usedNames.add(dot === -1 ? req : req.slice(0, dot));
}
}
}
for (const declarations of declarationsList) {
for (const name of declarations) {
if (usedNames.has(name)) break fastPath;
}
// earlier entries' declarations join the used set so later entries
// are checked against them
for (const name of declarations) usedNames.add(name);
}
for (const [m, moduleSource] of inlinedModuleSources) {
renamedInlinedModules.set(m, moduleSource);
}
return renamedInlinedModules;
}
for (const [m, moduleSource] of inlinedModuleSources) {
const code = /** @type {string} */ (moduleSource.source());
const { experiments } = compilation.options;
const { ast } = JavascriptParser._parse(
code,
{
sourceType: "auto",
ranges: true,
// generated code contains phase imports when the experiments are on
importPhases: Boolean(
experiments.deferImport || experiments.sourceImport
)
},
JavascriptParser._getModuleParseFunction(compilation, m)
);
const analysis = analyzeScope(ast);
const moduleScope = analysis.moduleScope;
inlinedModulesToInfo.set(m, {
source: moduleSource,
ast,
module: m,
variables: new Set(moduleScope.variables),
through: new Set(analysis.unresolvedReferences),
usedInNonInlined: new Set(),
moduleScope
});
}
for (const [, { variables, usedInNonInlined }] of inlinedModulesToInfo) {
for (const variable of variables) {
if (
nonInlinedModuleThroughIdentifiers.has(variable.name) ||
RESERVED_NAMES.has(variable.name)
) {
usedInNonInlined.add(variable);
}
}
}
// Multiple inlined entry modules share the chunk's startup scope. Rename any
// top-level declaration that would collide with another inlined module's
// declaration, with any inlined or non-inlined module's free (global)
// reference, or with a reserved name, so the modules can be emitted without a
// per-entry IIFE.
if (isMultipleEntries) {
/** @type {Set<string>} */
const allUsedNames = new Set(nonInlinedModuleThroughIdentifiers);
for (const name of RESERVED_NAMES) allUsedNames.add(name);
for (const { through } of inlinedModulesToInfo.values()) {
for (const ref of through) allUsedNames.add(ref.identifier.name);
}
for (const [m, moduleInfo] of inlinedModulesToInfo) {
const { ast, source: _source } = moduleInfo;
const source = new ReplaceSource(_source);
for (const variable of moduleInfo.variables) {
renameInlinedModuleVariable(
source,
ast,
variable,
moduleInfo.module.identifier(),
m.readableIdentifier(runtimeTemplate.requestShortener),
allUsedNames
);
}
renamedInlinedModules.set(m, source);
}
return renamedInlinedModules;
}
for (const [m, moduleInfo] of inlinedModulesToInfo) {
const { ast, source: _source, usedInNonInlined } = moduleInfo;
const source = new ReplaceSource(_source);
if (usedInNonInlined.size === 0) {
renamedInlinedModules.set(m, source);
continue;
}
const info = /** @type {Info} */ (inlinedModulesToInfo.get(m));
const allUsedNames = new Set(
Array.from(info.through, (v) => v.identifier.name)
);
for (const variable of usedInNonInlined) {
allUsedNames.add(variable.name);
}
for (const variable of info.variables) {
renameInlinedModuleVariable(
source,
ast,
variable,
info.module.identifier(),
m.readableIdentifier(runtimeTemplate.requestShortener),
allUsedNames
);
}
renamedInlinedModules.set(m, source);
}
return renamedInlinedModules;
}
}
JavascriptModulesPlugin.getCompilationHooks = createHooksRegistry(
createCompilationHooks
);
JavascriptModulesPlugin.chunkHasJs = chunkHasJs;
module.exports = JavascriptModulesPlugin;