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.
264 lines (257 loc) • 9.41 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
;
const Template = require("../template/Template");
const HelperRuntimeModule = require("./HelperRuntimeModule");
const RuntimeGlobals = require("./RuntimeGlobals");
/** @import Compilation from "../Compilation" */
/** @import Chunk from "../graph/Chunk" */
class AsyncModuleRuntimeModule extends HelperRuntimeModule {
/**
* @param {boolean=} deferInterop if defer import is used.
* @param {boolean=} strictModuleErrorHandling if a recorded evaluation error is kept.
*/
constructor(deferInterop = false, strictModuleErrorHandling = false) {
super("async module");
/** @type {boolean} */
this._deferInterop = deferInterop;
/** @type {boolean} */
this._strictModuleErrorHandling = strictModuleErrorHandling;
}
/**
* Generates runtime code for this runtime module.
* @returns {string | null} runtime code
*/
generate() {
const compilation = /** @type {Compilation} */ (this.compilation);
const { runtimeTemplate } = compilation;
const fn = RuntimeGlobals.asyncModule;
const defer = this._deferInterop;
const exposeExports =
defer ||
compilation.chunkGraph
.getTreeRuntimeRequirements(/** @type {Chunk} */ (this.chunk))
.has(RuntimeGlobals.asyncModuleExportSymbol);
const strictErrors = this._strictModuleErrorHandling;
const moduleCache = this.receivesHotUpdates()
? RuntimeGlobals.moduleCache
: "__webpack_module_cache__";
const rejectCall = "reject(promise[webpackError] = err)";
const rejectExpression = strictErrors
? `(markCycle(err), ${rejectCall})`
: rejectCall;
const cst = runtimeTemplate.renderConst();
const lt = runtimeTemplate.renderLet();
const supportsSymbol = runtimeTemplate.supportsSymbol();
/** @type {(name: string, fallback: string) => string} */
const renderSymbol = (name, fallback) =>
supportsSymbol
? `Symbol("${name}")`
: `hasSymbol ? Symbol("${name}") : "${fallback}"`;
return Template.asString([
...(supportsSymbol
? []
: [`${cst} hasSymbol = typeof Symbol === "function";`]),
`${cst} webpackQueues = ${renderSymbol("webpack queues", "__webpack_queues__")};`,
`${cst} webpackExports = ${
exposeExports ? `${RuntimeGlobals.asyncModuleExportSymbol}= ` : ""
}${renderSymbol("webpack exports", RuntimeGlobals.exports)};`,
`${cst} webpackError = ${renderSymbol("webpack error", "__webpack_error__")};`,
...(strictErrors ? [`${cst} cycleGroups = {};`] : []),
defer
? Template.asString([
`${cst} webpackDone = ${RuntimeGlobals.asyncModuleDoneSymbol} = ${renderSymbol("webpack done", "__webpack_done__")};`,
`${cst} webpackDefer = ${RuntimeGlobals.deferredModuleAsyncTransitiveDependenciesSymbol} = ${renderSymbol("webpack defer", "__webpack_defer__")};`,
`${RuntimeGlobals.deferredModuleAsyncTransitiveDependencies} = ${runtimeTemplate.basicFunction(
"asyncDeps",
[
Template.indent([
`${cst} hasUnresolvedAsyncSubgraph = asyncDeps.some((id) => {`,
Template.indent([
`${cst} cache = ${moduleCache}[id];`,
"if (!cache) return true;",
// A cached entry no longer means finished, and a rejected
// one is done but its error must still reach the importer.
`${cst} deps = cache.exports;`,
"return deps[webpackDone] !== true || deps[webpackError] !== undefined;"
]),
"});",
"if (hasUnresolvedAsyncSubgraph) {",
Template.indent([
"return ({ then(onFulfilled, onRejected) { return Promise.all(asyncDeps.map(__webpack_require__)).then(onFulfilled, onRejected) } })"
]),
"}"
])
]
)}`
])
: "",
`${cst} resolveQueue = ${runtimeTemplate.basicFunction("queue", [
`if(${runtimeTemplate.optionalChaining("queue", "d < 1")}) {`,
Template.indent([
"queue.d = 1;",
`queue.forEach(${runtimeTemplate.expressionFunction(
"fn.r--",
"fn"
)});`,
`queue.forEach(${runtimeTemplate.expressionFunction(
"fn.r-- ? fn.r++ : fn()",
"fn"
)});`
]),
"}"
])}`,
`${cst} wrapDeps = ${runtimeTemplate.returningFunction(
`deps.map(${runtimeTemplate.basicFunction("dep", [
'if(dep !== null && typeof dep === "object") {',
Template.indent([
defer
? Template.asString([
"if(!dep[webpackQueues] && dep[webpackDefer]) {",
Template.indent([
`${cst} asyncDeps = ${RuntimeGlobals.deferredModuleAsyncTransitiveDependencies}(dep[webpackDefer]);`,
"if (asyncDeps) {",
Template.indent([
`${cst} d = dep;`,
"dep = {",
Template.indent([
"then(onFulfilled, onRejected) {",
Template.indent([
`asyncDeps.then(${runtimeTemplate.returningFunction(
"onFulfilled(d)"
)}, onRejected);`
]),
"}"
]),
"};"
]),
"} else return dep;"
]),
"}"
])
: "",
"if(dep[webpackQueues]) return dep;",
"if(dep.then) {",
Template.indent([
`${cst} queue = [];`,
"queue.d = 0;",
`dep.then(${runtimeTemplate.basicFunction("r", [
"obj[webpackExports] = r;",
"resolveQueue(queue);"
])}, ${runtimeTemplate.basicFunction("e", [
"obj[webpackError] = e;",
"resolveQueue(queue);"
])});`,
`${cst} obj = {};`,
defer ? "obj[webpackDefer] = false;" : "",
`obj[webpackQueues] = ${runtimeTemplate.expressionFunction(
"fn(queue)",
"fn"
)};`,
"return obj;"
]),
"}"
]),
"}",
`${cst} ret = {};`,
`ret[webpackQueues] = ${runtimeTemplate.emptyFunction()};`,
"ret[webpackExports] = dep;",
"return ret;"
])})`,
"deps"
)};`,
`${fn} = ${runtimeTemplate.basicFunction(
strictErrors
? "module, body, hasAwait, cycleGroup"
: "module, body, hasAwait",
[
`${lt} queue;`,
"hasAwait && ((queue = []).d = -1);",
`${cst} depQueues = new Set();`,
`${cst} exports = module.exports;`,
`${lt} currentDeps;`,
`${lt} outerResolve;`,
`${lt} reject;`,
`${cst} promise = new Promise(${runtimeTemplate.basicFunction(
"resolve, rej",
["reject = rej;", "outerResolve = resolve;"]
)});`,
"promise[webpackExports] = exports;",
`promise[webpackQueues] = ${runtimeTemplate.expressionFunction(
`queue && fn(queue), depQueues.forEach(fn), promise["catch"](${runtimeTemplate.emptyFunction()})`,
"fn"
)};`,
"module.exports = promise;",
`${cst} handle = ${runtimeTemplate.basicFunction("deps", [
"currentDeps = wrapDeps(deps);",
`${lt} fn;`,
`${cst} getResult = ${runtimeTemplate.returningFunction(
`currentDeps.map(${runtimeTemplate.basicFunction("d", [
defer ? "if(d[webpackDefer]) return d;" : "",
"if(d[webpackError]) throw d[webpackError];",
"return d[webpackExports];"
])})`
)}`,
`${cst} promise = new Promise(${runtimeTemplate.basicFunction(
"resolve",
[
`fn = ${runtimeTemplate.expressionFunction(
"resolve(getResult)",
""
)};`,
"fn.r = 0;",
`${cst} fnQueue = ${runtimeTemplate.expressionFunction(
"q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)))",
"q"
)};`,
`currentDeps.forEach(${runtimeTemplate.expressionFunction(
`${
defer ? "dep[webpackDefer]||" : ""
}dep[webpackQueues](fnQueue)`,
"dep"
)});`
]
)});`,
"return fn.r ? promise : getResult();"
])}`,
// One `[[EvaluationError]]` is recorded for a whole strongly-connected
// component, so a peer that already fulfilled must report it too.
...(strictErrors
? [
`${cst} group = cycleGroup === undefined ? undefined : (cycleGroups[cycleGroup] = cycleGroups[cycleGroup] || []);`,
// Ids, not module objects: hot replacement swaps a member for a
// new object under the same id, and the error belongs to that one.
"group && group.indexOf(module.id) < 0 && group.push(module.id);",
`${cst} markCycle = ${runtimeTemplate.expressionFunction(
`group && group.forEach(${runtimeTemplate.basicFunction(
"id",
[
`${cst} peer = ${moduleCache}[id];`,
"if (peer && peer.error === undefined) peer.error = err;"
]
)})`,
"err"
)};`
]
: []),
`${cst} done = ${runtimeTemplate.expressionFunction(
`(err ? ${rejectExpression} : outerResolve(exports)), resolveQueue(queue)${
defer
? ", promise[webpackDone] = true, module.evaluatingAsync = false"
: ""
}`,
"err"
)}`,
// The sync require wrapper clears `module.evaluating` the moment `.a`
// returns, so a deferred namespace reaching this module through a cycle
// needs `evaluatingAsync` to keep throwing until evaluation ends.
defer ? "module.evaluatingAsync = true;" : "",
"body(handle, done);",
`${runtimeTemplate.optionalChaining("queue", "d < 0")} && (queue.d = 0);`
]
)};`
]);
}
}
module.exports = AsyncModuleRuntimeModule;