Visitar URL original
perf: emit less chunk-loading runtime (#22094) · webpack/webpack@aff7c4c · GitHub
Skip to content

Commit aff7c4c

Browse files
perf: emit less chunk-loading runtime (#22094)
* perf: call the one chunk handler directly when a chunk carries one `__webpack_require__.e` walked every key of the handler map to collect promises, which is 78 minified bytes of dispatch for the single handler most chunks have. Read the keys the chunk's other runtime modules emit and call that one directly. The keys are read from what those modules generate rather than declared, so a handler stays counted as their conditions change. One that reads the compilation hash cannot be generated this early without caching it before the hash exists, so a chunk carrying one keeps the walk. * perf: emit less chunk-loading runtime `__webpack_require__.O` carried a priority queue — an insertion sort, a lowest seen priority and the gate reading it — for a priority only the prefetch startup callback passes, so a build without one now keeps insertion order. * perf: call the one chunk handler directly in every dispatch The single-handler specialization read a sibling's code to learn which handlers a chunk installs, and gave up whenever one was keyed on a hash. The module naming chunk files is keyed on one and sits in every build that loads a chunk by computed filename, so the specialization almost never fired there: 120 of the 259 single-handler assets still walked the map. Reading such a sibling through `generate()` rather than `getGeneratedCode()` answers the same question without pinning a placeholder into its cache. The prefetch dispatch and the deferred-startup gate fan out over a handler map the same way, so both now take the same direct call. A module that reads a map rather than installing onto one is skipped, otherwise the three would generate each other in a cycle. * fix: escape every regexp metacharacter in the handler map name The name was escaped for `$` and `.` alone, so one carrying a bracket or a backslash would have been read as regexp syntax rather than matched literally. The three globals it is called with are unaffected; CodeQL reports the gap. * test: record the smaller entry chunk in the stats snapshots `entryB.js` loses 18 bytes to the direct chunk handler call. These snapshots are inline in a `*.test.js`, so only the integration matrix runs them. * test: make the deferred priority case require the priority queue The case passes a priority to `__webpack_require__.O` but never asked for `hasChunkPriority`, so it was served the insertion-order queue and its assertion held either way. It now requests the flag and defers a loaded odd priority behind a blocked lower one, which insertion order runs instead. * perf: read a chunk's handler keys once instead of once per dispatch Each of the three dispatches scanned the chunk's runtime modules for its own map, so the module naming chunk files — the one whose output grows with chunk count — was generated once per dispatch. One pass now reads every map hanging off the require global and caches it per chunk: on a 62-chunk build that is 1 generate call rather than 3, and 9 KB scanned rather than 27 KB. * perf: let a hash-keyed runtime module state the handlers it installs Reading a module keyed on a hash means rendering it a second time, and the module naming chunk files is read on every runtime chunk. The six such modules now state which handlers they install, so none of them is rendered for the scan: a 20-entry build drops from 20 generate calls to none. Saying nothing still means the code is read, so a module that does not state its handlers keeps working. * perf: let every chunk handler installer state what it installs Replaces the per-module `_installedChunkHandlers` field with a `getInstalledChunkHandlers()` method, and declares it on all fifteen runtime modules that install onto a chunk handler map. `generate()` is no longer reached: the modules keyed on a hash, which were the only ones a scan had to render, now state their keys instead. A module that states nothing is still read, because an undeclared installer would otherwise be dropped from a specialized dispatch --- mini-css-extract- plugin installs `__webpack_require__.f.miniCss` from its own runtime module, and `configCases/custom-source-type/localization` does the same. * test: cover the handler a legacy main template hook installs `CompatRuntimeModule` emits whatever a plugin taps `requireExtensions` with, so it states that it cannot name what it installs and the dispatch stays general. Without that the case's handler is never called and the assertion on `global.__legacyHandlerCalled` fails, which is how a third-party handler would be dropped in a real build. * perf: state chunk handlers on every runtime module instead of reading code A dispatch can only be specialized when the whole key set is known, and that knowledge has two possible sources: every contributing module states it, or webpack reads what they emit. Nothing else answers it -- a runtime requirement marker cannot speak for an installer that adds none, a registry cannot tell "nobody installs" from "someone installs silently", and the dispatcher renders before the installers because it emits the map they assign onto. So "said nothing" now means unknown rather than none, which makes every runtime module answer. `HelperRuntimeModule` answers for the seventeen helpers under it, leaving thirty classes to state that they install nothing, and `getOnlyHandlerKey` no longer renders or reads anything. Across `configCases` this costs 29 gzip bytes in one case, whose fixture installs a handler from a runtime module of its own and so keeps the general dispatch -- which is what a third-party installer now gets too. * fix: do not call the handler declaration on a module that lacks it A runtime module built against an older webpack extends a base class from that copy, so the instance has no `getInstalledChunkHandlers` and calling it threw `module.getInstalledChunkHandlers is not a function` mid-build. Such a module is read as unknown now, which keeps the general dispatch and its handler with it. `Array.isArray` rather than a `null` check, so a module answering something else is unknown as well. The check goes away in webpack 6, where every runtime module answers. * test: type the foreign runtime module case against the public entry `tsc -p tsconfig.types.test.json` reads a test's webpack imports as the generated types, so importing `RuntimeModule` from `lib/` made the instance unassignable to `addRuntimeModule`. Shadowing the method on the instance rather than defining it away on the prototype types cleanly too.
1 parent e954b81 commit aff7c4c

71 files changed

Lines changed: 1006 additions & 94 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
Emit less chunk-loading runtime.

‎lib/RuntimeGlobals.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ module.exports.hasCssModules = "has css modules";
247247
*/
248248
module.exports.hasFetchPriority = "has fetch priority";
249249

250+
/**
251+
* a flag when a deferred startup callback carries a priority
252+
*/
253+
module.exports.hasChunkPriority = "has chunk priority";
254+
250255
/**
251256
* the shorthand for Object.prototype.hasOwnProperty
252257
* using of it decreases the compiled bundle size

‎lib/RuntimeModule.js‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,17 @@ class RuntimeModule extends Module {
150150
super.updateHash(hash, context);
151151
}
152152

153+
/**
154+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
155+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
156+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
157+
*/
158+
getInstalledChunkHandlers() {
159+
// Answering `null` rather than "none" keeps a module that never heard of this
160+
// out of a dispatch that would skip the handler it installs.
161+
return null;
162+
}
163+
153164
/**
154165
* Returns the source types this module can generate.
155166
* @returns {SourceTypes} types available (do not mutate)
@@ -242,6 +253,13 @@ class RuntimeModule extends Module {
242253
}
243254
}
244255

256+
/**
257+
* The answer a runtime module gives when it installs no chunk handler at all
258+
*/
259+
RuntimeModule.NO_CHUNK_HANDLERS =
260+
/** @type {[string, string][]} */
261+
([]);
262+
245263
/**
246264
* Runtime modules without any dependencies to other runtime modules
247265
*/

‎lib/RuntimePlugin.js‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ const SIMPLE_TREE_RUNTIME_MODULES = [
233233
[RuntimeGlobals.shareScopeMap, getShareRuntimeModule],
234234
[RuntimeGlobals.relativeUrl, getRelativeUrlRuntimeModule],
235235
[RuntimeGlobals.worker, getWorkerRuntimeModule],
236-
[RuntimeGlobals.onChunksLoaded, getOnChunksLoadedRuntimeModule],
237236
[RuntimeGlobals.scriptNonce, getNonceRuntimeModule],
238237
[RuntimeGlobals.toBinary, getToBinaryRuntimeModule]
239238
];
@@ -320,6 +319,16 @@ class RuntimePlugin {
320319
return true;
321320
});
322321
}
322+
compilation.hooks.runtimeRequirementInTree
323+
.for(RuntimeGlobals.onChunksLoaded)
324+
.tap(PLUGIN_NAME, (chunk, runtimeRequirement) => {
325+
compilation.addLazyRuntimeModule(
326+
chunk,
327+
getOnChunksLoadedRuntimeModule,
328+
(Ctor) => new Ctor(runtimeRequirement)
329+
);
330+
return true;
331+
});
323332
compilation.hooks.runtimeRequirementInTree
324333
.for(RuntimeGlobals.definePropertyGetters)
325334
.tap(PLUGIN_NAME, (chunk, runtimeRequirement) => {

‎lib/container/RemoteRuntimeModule.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ class RemoteRuntimeModule extends RuntimeModule {
2020
super("remotes loading");
2121
}
2222

23+
/**
24+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
25+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
26+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
27+
*/
28+
getInstalledChunkHandlers() {
29+
return [[RuntimeGlobals.ensureChunkHandlers, "remotes"]];
30+
}
31+
2332
/**
2433
* Generates runtime code for this runtime module.
2534
* @returns {string | null} runtime code

‎lib/css/CssInjectStyleRuntimeModule.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ class CssInjectStyleRuntimeModule extends RuntimeModule {
3030
this._runtimeRequirements = runtimeRequirements;
3131
}
3232

33+
/**
34+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
35+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
36+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
37+
*/
38+
getInstalledChunkHandlers() {
39+
return RuntimeModule.NO_CHUNK_HANDLERS;
40+
}
41+
3342
/**
3443
* Generates runtime code for this runtime module.
3544
* @returns {string | null} runtime code

‎lib/css/CssLoadingRuntimeModule.js‎

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const createHooksRegistry = require("../util/createHooksRegistry");
1515
const { chunkHasCss } = require("./CssModulesPlugin");
1616

1717
/** @import Chunk, { ChunkId } from "../Chunk" */
18+
/** @import ChunkGraph from "../ChunkGraph" */
1819
/** @import { ReadOnlyRuntimeRequirements } from "../Module" */
1920

2021
const createCompilationHooks = () => ({
@@ -56,26 +57,14 @@ class CssLoadingRuntimeModule extends RuntimeModule {
5657
}
5758

5859
/**
59-
* Generates runtime code for this runtime module.
60-
* @returns {string | null} runtime code
60+
* The initial chunks carrying a stylesheet, and the matcher for the ones the
61+
* handler can still be asked to load.
62+
* @private
63+
* @returns {{ initialChunkIds: Set<ChunkId>, hasCssMatcher: ReturnType<typeof compileBooleanMatcher> }} loadable chunks
6164
*/
62-
generate() {
63-
const { _runtimeRequirements } = this;
64-
const compilation = /** @type {Compilation} */ (this.compilation);
65+
_getLoadableChunks() {
6566
const chunk = /** @type {Chunk} */ (this.chunk);
66-
const {
67-
chunkGraph,
68-
runtimeTemplate,
69-
outputOptions: {
70-
crossOriginLoading,
71-
uniqueName,
72-
chunkLoadTimeout: loadTimeout,
73-
charset,
74-
resourceHints
75-
}
76-
} = compilation;
77-
const dedupePrefetch = Boolean(resourceHints && resourceHints.dedupe);
78-
const fn = RuntimeGlobals.ensureChunkHandlers;
67+
const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
7968
/** @type {Set<ChunkId>} */
8069
const initialChunkIds = new Set();
8170
for (const c of chunk.getAllInitialChunks()) {
@@ -96,6 +85,75 @@ class CssLoadingRuntimeModule extends RuntimeModule {
9685
compileBooleanMatcher(loadableConditionMap) === false
9786
? false
9887
: compileBooleanMatcher(conditionMap);
88+
return { initialChunkIds, hasCssMatcher };
89+
}
90+
91+
/**
92+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
93+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
94+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
95+
*/
96+
getInstalledChunkHandlers() {
97+
const compilation = /** @type {Compilation} */ (this.compilation);
98+
const chunk = /** @type {Chunk} */ (this.chunk);
99+
const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph);
100+
const runtimeRequirements = this._runtimeRequirements;
101+
const withLoading =
102+
runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
103+
this._getLoadableChunks().hasCssMatcher !== false;
104+
// Nothing at all is emitted without either, so nothing is installed either.
105+
if (
106+
!withLoading &&
107+
!runtimeRequirements.has(RuntimeGlobals.hmrDownloadUpdateHandlers)
108+
) {
109+
return [];
110+
}
111+
const withHint =
112+
compilation.outputOptions.environment.document ||
113+
compilation.runtimeTemplate.isNeutralPlatform();
114+
/** @type {[string, string][]} */
115+
const handlers = [];
116+
if (withLoading) handlers.push([RuntimeGlobals.ensureChunkHandlers, "css"]);
117+
if (
118+
withHint &&
119+
runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) &&
120+
chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasCss)
121+
) {
122+
handlers.push([RuntimeGlobals.prefetchChunkHandlers, "s"]);
123+
}
124+
if (
125+
withHint &&
126+
runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) &&
127+
(chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasCss) ||
128+
chunk.hasChildByOrder(chunkGraph, "cssPreload", true, chunkHasCss))
129+
) {
130+
handlers.push([RuntimeGlobals.preloadChunkHandlers, "s"]);
131+
}
132+
return handlers;
133+
}
134+
135+
/**
136+
* Generates runtime code for this runtime module.
137+
* @returns {string | null} runtime code
138+
*/
139+
generate() {
140+
const { _runtimeRequirements } = this;
141+
const compilation = /** @type {Compilation} */ (this.compilation);
142+
const chunk = /** @type {Chunk} */ (this.chunk);
143+
const {
144+
chunkGraph,
145+
runtimeTemplate,
146+
outputOptions: {
147+
crossOriginLoading,
148+
uniqueName,
149+
chunkLoadTimeout: loadTimeout,
150+
charset,
151+
resourceHints
152+
}
153+
} = compilation;
154+
const dedupePrefetch = Boolean(resourceHints && resourceHints.dedupe);
155+
const fn = RuntimeGlobals.ensureChunkHandlers;
156+
const { initialChunkIds, hasCssMatcher } = this._getLoadableChunks();
99157

100158
const withLoading =
101159
_runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&

‎lib/css/CssServerStylesRuntimeModule.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ class CssServerStylesRuntimeModule extends RuntimeModule {
2626
return false;
2727
}
2828

29+
/**
30+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
31+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
32+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
33+
*/
34+
getInstalledChunkHandlers() {
35+
return RuntimeModule.NO_CHUNK_HANDLERS;
36+
}
37+
2938
/**
3039
* Generates runtime code for this runtime module.
3140
* @returns {string | null} runtime code

‎lib/dependencies/AMDRuntimeModules.js‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ class AMDDefineRuntimeModule extends RuntimeModule {
2525
return false;
2626
}
2727

28+
/**
29+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
30+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
31+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
32+
*/
33+
getInstalledChunkHandlers() {
34+
return RuntimeModule.NO_CHUNK_HANDLERS;
35+
}
36+
2837
/**
2938
* Generates runtime code for this runtime module.
3039
* @returns {string | null} runtime code
@@ -58,6 +67,15 @@ class AMDOptionsRuntimeModule extends RuntimeModule {
5867
this.options = options;
5968
}
6069

70+
/**
71+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
72+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
73+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
74+
*/
75+
getInstalledChunkHandlers() {
76+
return RuntimeModule.NO_CHUNK_HANDLERS;
77+
}
78+
6179
/**
6280
* Generates runtime code for this runtime module.
6381
* @returns {string | null} runtime code

‎lib/dependencies/CommonJsPlugin.js‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ class HarmonyModuleDecoratorRuntimeModule extends RuntimeModule {
283283
return false;
284284
}
285285

286+
/**
287+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
288+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
289+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
290+
*/
291+
getInstalledChunkHandlers() {
292+
return RuntimeModule.NO_CHUNK_HANDLERS;
293+
}
294+
286295
/**
287296
* Generates runtime code for this runtime module.
288297
* @returns {string | null} runtime code
@@ -324,6 +333,15 @@ class NodeModuleDecoratorRuntimeModule extends RuntimeModule {
324333
return false;
325334
}
326335

336+
/**
337+
* The `[handlerMap, key]` pairs this module installs onto a chunk handler map
338+
* such as `__webpack_require__.f`, or `null` where it cannot name them.
339+
* @returns {[string, string][] | null} installed chunk handlers (do not mutate)
340+
*/
341+
getInstalledChunkHandlers() {
342+
return RuntimeModule.NO_CHUNK_HANDLERS;
343+
}
344+
327345
/**
328346
* Generates runtime code for this runtime module.
329347
* @returns {string | null} runtime code

0 commit comments

Comments
 (0)