Skip to content

Commit 509a060

Browse files
committed
fix quadratic evaluation performance of async modules
improve runtime code for async modules
1 parent 2738eeb commit 509a060

5 files changed

Lines changed: 60 additions & 63 deletions

File tree

lib/runtime/AsyncModuleRuntimeModule.js

Lines changed: 34 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ class AsyncModuleRuntimeModule extends HelperRuntimeModule {
2020
const { runtimeTemplate } = this.compilation;
2121
const fn = RuntimeGlobals.asyncModule;
2222
return Template.asString([
23-
'var webpackThen = typeof Symbol === "function" ? Symbol("webpack then") : "__webpack_then__";',
23+
'var webpackQueues = typeof Symbol === "function" ? Symbol("webpack queues") : "__webpack_queues__";',
2424
'var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "__webpack_exports__";',
2525
'var webpackError = typeof Symbol === "function" ? Symbol("webpack error") : "__webpack_error__";',
26-
`var completeQueue = ${runtimeTemplate.basicFunction("queue", [
27-
"if(queue) {",
26+
`var resolveQueue = ${runtimeTemplate.basicFunction("queue", [
27+
"if(queue && !queue.d) {",
2828
Template.indent([
29+
"queue.d = 1;",
2930
`queue.forEach(${runtimeTemplate.expressionFunction(
3031
"fn.r--",
3132
"fn"
@@ -37,91 +38,58 @@ class AsyncModuleRuntimeModule extends HelperRuntimeModule {
3738
]),
3839
"}"
3940
])}`,
40-
`var completeFunction = ${runtimeTemplate.expressionFunction(
41-
"!--fn.r && fn()",
42-
"fn"
43-
)};`,
44-
`var queueFunction = ${runtimeTemplate.expressionFunction(
45-
"queue ? queue.push(fn) : completeFunction(fn)",
46-
"queue, fn"
47-
)};`,
4841
`var wrapDeps = ${runtimeTemplate.returningFunction(
4942
`deps.map(${runtimeTemplate.basicFunction("dep", [
5043
'if(dep !== null && typeof dep === "object") {',
5144
Template.indent([
52-
"if(dep[webpackThen]) return dep;",
45+
"if(dep[webpackQueues]) return dep;",
5346
"if(dep.then) {",
5447
Template.indent([
5548
"var queue = [];",
49+
"queue.d = 0;",
5650
`dep.then(${runtimeTemplate.basicFunction("r", [
5751
"obj[webpackExports] = r;",
58-
"completeQueue(queue);",
59-
"queue = 0;"
52+
"resolveQueue(queue);"
6053
])}, ${runtimeTemplate.basicFunction("e", [
6154
"obj[webpackError] = e;",
62-
"completeQueue(queue);",
63-
"queue = 0;"
55+
"resolveQueue(queue);"
6456
])});`,
6557
"var obj = {};",
66-
`obj[webpackThen] = ${runtimeTemplate.expressionFunction(
67-
"queueFunction(queue, fn), dep['catch'](reject)",
68-
"fn, reject"
58+
`obj[webpackQueues] = ${runtimeTemplate.expressionFunction(
59+
`fn(queue)`,
60+
"fn"
6961
)};`,
7062
"return obj;"
7163
]),
7264
"}"
7365
]),
7466
"}",
7567
"var ret = {};",
76-
`ret[webpackThen] = ${runtimeTemplate.expressionFunction(
77-
"completeFunction(fn)",
78-
"fn"
79-
)};`,
68+
`ret[webpackQueues] = ${runtimeTemplate.emptyFunction()};`,
8069
"ret[webpackExports] = dep;",
8170
"return ret;"
8271
])})`,
8372
"deps"
8473
)};`,
8574
`${fn} = ${runtimeTemplate.basicFunction("module, body, hasAwait", [
86-
"var queue = hasAwait && [];",
75+
"var queue;",
76+
"hasAwait && ((queue = []).d = 1);",
77+
"if(queue) queue.moduleId = module.id;",
78+
"var depQueues = new Set();",
8779
"var exports = module.exports;",
8880
"var currentDeps;",
8981
"var outerResolve;",
9082
"var reject;",
91-
"var isEvaluating = true;",
92-
"var nested = false;",
93-
`var whenAll = ${runtimeTemplate.basicFunction(
94-
"deps, onResolve, onReject",
95-
[
96-
"if (nested) return;",
97-
"nested = true;",
98-
"onResolve.r += deps.length;",
99-
`deps.map(${runtimeTemplate.expressionFunction(
100-
"dep[webpackThen](onResolve, onReject)",
101-
"dep, i"
102-
)});`,
103-
"nested = false;"
104-
]
105-
)};`,
10683
`var promise = new Promise(${runtimeTemplate.basicFunction(
10784
"resolve, rej",
108-
[
109-
"reject = rej;",
110-
`outerResolve = ${runtimeTemplate.expressionFunction(
111-
"resolve(exports), completeQueue(queue), queue = 0"
112-
)};`
113-
]
85+
["reject = rej;", "outerResolve = resolve;"]
11486
)});`,
11587
"promise[webpackExports] = exports;",
116-
`promise[webpackThen] = ${runtimeTemplate.basicFunction(
117-
"fn, rejectFn",
118-
[
119-
"if (isEvaluating) { return completeFunction(fn); }",
120-
"if (currentDeps) whenAll(currentDeps, fn, rejectFn);",
121-
"queueFunction(queue, fn);",
122-
"promise['catch'](rejectFn);"
123-
]
88+
`promise[webpackQueues] = ${runtimeTemplate.expressionFunction(
89+
`queue && fn(queue), depQueues.forEach(fn), promise["catch"](${runtimeTemplate.emptyFunction()})`,
90+
"fn"
12491
)};`,
92+
"promise.moduleId = module.id;",
12593
"module.exports = promise;",
12694
`body(${runtimeTemplate.basicFunction("deps", [
12795
"currentDeps = wrapDeps(deps);",
@@ -133,21 +101,29 @@ class AsyncModuleRuntimeModule extends HelperRuntimeModule {
133101
])})`
134102
)}`,
135103
`var promise = new Promise(${runtimeTemplate.basicFunction(
136-
"resolve, reject",
104+
"resolve",
137105
[
138106
`fn = ${runtimeTemplate.expressionFunction(
139-
"resolve(getResult)"
107+
"resolve(getResult)",
108+
""
140109
)};`,
141110
"fn.r = 0;",
142-
"whenAll(currentDeps, fn, reject);"
111+
`var fnQueue = ${runtimeTemplate.expressionFunction(
112+
"q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)))",
113+
"q"
114+
)};`,
115+
`currentDeps.map(${runtimeTemplate.expressionFunction(
116+
"dep[webpackQueues](fnQueue)",
117+
"dep"
118+
)});`
143119
]
144120
)});`,
145121
"return fn.r ? promise : getResult();"
146122
])}, ${runtimeTemplate.expressionFunction(
147-
"err && reject(promise[webpackError] = err), outerResolve()",
123+
"(err ? reject(promise[webpackError] = err) : outerResolve(exports)), resolveQueue(queue)",
148124
"err"
149125
)});`,
150-
"isEvaluating = false;"
126+
"queue && (queue.d = 0);"
151127
])};`
152128
]);
153129
}

test/__snapshots__/StatsTestCases.basictest.js.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,8 +4611,8 @@ webpack x.x.x compiled with 1 warning in X ms"
46114611
`;
46124612
46134613
exports[`StatsTestCases should print correct stats for wasm-explorer-examples-sync 1`] = `
4614-
"assets by path *.js 22.2 KiB
4615-
asset bundle.js 16.7 KiB [emitted] (name: main)
4614+
"assets by path *.js 21.8 KiB
4615+
asset bundle.js 16.3 KiB [emitted] (name: main)
46164616
asset 325.bundle.js 3.9 KiB [emitted]
46174617
asset 795.bundle.js 557 bytes [emitted]
46184618
asset 526.bundle.js 366 bytes [emitted] (id hint: vendors)
@@ -4628,8 +4628,8 @@ assets by path *.wasm 1.37 KiB
46284628
asset 0301cb3f9f4151b567f5.module.wasm 120 bytes [emitted] [immutable]
46294629
chunk (runtime: main) 20.bundle.js 50 bytes (javascript) 531 bytes (webassembly) [rendered]
46304630
./duff.wasm 50 bytes (javascript) 531 bytes (webassembly) [built] [code generated]
4631-
chunk (runtime: main) bundle.js (main) 586 bytes (javascript) 9.49 KiB (runtime) [entry] [rendered]
4632-
runtime modules 9.49 KiB 11 modules
4631+
chunk (runtime: main) bundle.js (main) 586 bytes (javascript) 9.19 KiB (runtime) [entry] [rendered]
4632+
runtime modules 9.19 KiB 11 modules
46334633
./index.js 586 bytes [built] [code generated]
46344634
chunk (runtime: main) 189.bundle.js 50 bytes (javascript) 156 bytes (webassembly) [rendered]
46354635
./Q_rsqrt.wasm 50 bytes (javascript) 156 bytes (webassembly) [built] [code generated]
@@ -4643,7 +4643,7 @@ chunk (runtime: main) 526.bundle.js (id hint: vendors) 34 bytes [rendered] split
46434643
chunk (runtime: main) 795.bundle.js 110 bytes (javascript) 444 bytes (webassembly) [rendered]
46444644
./fact.wasm 50 bytes (javascript) 154 bytes (webassembly) [built] [code generated]
46454645
./fast-math.wasm 60 bytes (javascript) 290 bytes (webassembly) [built] [code generated]
4646-
runtime modules 9.49 KiB 11 modules
4646+
runtime modules 9.19 KiB 11 modules
46474647
cacheable modules 2.31 KiB (javascript) 1.37 KiB (webassembly)
46484648
webassembly modules 310 bytes (javascript) 1.37 KiB (webassembly)
46494649
./Q_rsqrt.wasm 50 bytes (javascript) 156 bytes (webassembly) [built] [code generated]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
await 1;
2+
export default 1;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
it("should not take too long to evaluate nested async modules", async () => {
2+
const start = Date.now();
3+
await import(/* webpackMode: "eager" */ "./loader.js?i=40!./loader.js");
4+
expect(Date.now() - start).toBeLessThan(100);
5+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** @type {import("../../../../").LoaderDefinition<{ i: string }>} */
2+
module.exports = function () {
3+
const options = this.getOptions();
4+
const i = +options.i;
5+
let src = `import n from "./async.js";\n`;
6+
if (i > 0) {
7+
src += `import a from "./loader.js?i=${i - 1}&a!./loader.js";\n`;
8+
src += `import b from "./loader.js?i=${i - 1}&b!./loader.js";\n`;
9+
src += `export default n + a + b;\n`;
10+
} else {
11+
src += `export default n;\n`;
12+
}
13+
return src;
14+
};

0 commit comments

Comments
 (0)