webpack 5.107.0 → 5.107.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/lib/BannerPlugin.js +3 -4
  2. package/lib/Chunk.js +21 -25
  3. package/lib/ChunkGroup.js +57 -15
  4. package/lib/Compilation.js +33 -11
  5. package/lib/Compiler.js +27 -3
  6. package/lib/ContextModuleFactory.js +45 -38
  7. package/lib/EvalSourceMapDevToolPlugin.js +0 -1
  8. package/lib/ExportsInfo.js +30 -34
  9. package/lib/ExternalModule.js +15 -11
  10. package/lib/ExternalModuleFactoryPlugin.js +2 -1
  11. package/lib/Module.js +1 -1
  12. package/lib/ModuleNotFoundError.js +10 -0
  13. package/lib/ModuleSourceTypeConstants.js +24 -22
  14. package/lib/MultiCompiler.js +14 -0
  15. package/lib/NormalModule.js +531 -53
  16. package/lib/NormalModuleFactory.js +38 -26
  17. package/lib/ProgressPlugin.js +1 -1
  18. package/lib/RuntimePlugin.js +1 -1
  19. package/lib/SourceMapDevToolPlugin.js +335 -57
  20. package/lib/Template.js +1 -1
  21. package/lib/TemplatedPathPlugin.js +22 -4
  22. package/lib/asset/AssetBytesGenerator.js +6 -6
  23. package/lib/asset/AssetGenerator.js +14 -14
  24. package/lib/asset/AssetModulesPlugin.js +3 -7
  25. package/lib/asset/AssetSourceGenerator.js +6 -6
  26. package/lib/buildChunkGraph.js +24 -2
  27. package/lib/cache/getLazyHashedEtag.js +9 -2
  28. package/lib/css/CssModulesPlugin.js +2 -2
  29. package/lib/dependencies/CommonJsImportsParserPlugin.js +108 -1
  30. package/lib/dependencies/CssUrlDependency.js +3 -2
  31. package/lib/dependencies/HarmonyDetectionParserPlugin.js +21 -1
  32. package/lib/dependencies/HarmonyExportInitFragment.js +8 -9
  33. package/lib/dependencies/HtmlInlineScriptDependency.js +3 -14
  34. package/lib/dependencies/HtmlInlineStyleDependency.js +17 -0
  35. package/lib/dependencies/HtmlScriptSrcDependency.js +265 -65
  36. package/lib/dependencies/HtmlSourceDependency.js +21 -2
  37. package/lib/dependencies/WorkerPlugin.js +18 -4
  38. package/lib/hmr/LazyCompilationPlugin.js +104 -0
  39. package/lib/html/HtmlGenerator.js +81 -33
  40. package/lib/html/HtmlModulesPlugin.js +87 -28
  41. package/lib/html/walkHtmlTokens.js +641 -125
  42. package/lib/index.js +2 -0
  43. package/lib/javascript/JavascriptModulesPlugin.js +2 -2
  44. package/lib/javascript/JavascriptParser.js +1 -1
  45. package/lib/library/ModuleLibraryPlugin.js +30 -24
  46. package/lib/node/NodeWatchFileSystem.js +37 -22
  47. package/lib/optimize/ConcatenatedModule.js +3 -2
  48. package/lib/optimize/SideEffectsFlagPlugin.js +1 -2
  49. package/lib/optimize/SplitChunksPlugin.js +4 -4
  50. package/lib/runtime/AutoPublicPathRuntimeModule.js +3 -3
  51. package/lib/runtime/GetChunkFilenameRuntimeModule.js +5 -5
  52. package/lib/sharing/ConsumeSharedPlugin.js +2 -8
  53. package/lib/sharing/ProvideSharedPlugin.js +4 -4
  54. package/lib/util/fs.js +6 -1
  55. package/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js +1 -2
  56. package/package.json +5 -5
  57. package/schemas/WebpackOptions.check.js +1 -1
  58. package/schemas/WebpackOptions.json +11 -9
  59. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  60. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  61. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  62. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  63. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  64. package/types.d.ts +472 -149
@@ -4,12 +4,18 @@
4
4
 
5
5
  "use strict";
6
6
 
7
+ const {
8
+ CSS_IMPORT_TYPE,
9
+ CSS_TYPE,
10
+ JAVASCRIPT_TYPE
11
+ } = require("../ModuleSourceTypeConstants");
12
+ const HtmlGenerator = require("../html/HtmlGenerator");
7
13
  const makeSerializable = require("../util/makeSerializable");
8
- const CssUrlDependency = require("./CssUrlDependency");
9
14
  const ModuleDependency = require("./ModuleDependency");
10
15
 
11
16
  /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
12
17
  /** @typedef {import("../Chunk")} Chunk */
18
+ /** @typedef {import("../ChunkGraph")} ChunkGraph */
13
19
  /** @typedef {import("../Dependency")} Dependency */
14
20
  /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
15
21
  /** @typedef {import("../Entrypoint")} Entrypoint */
@@ -89,39 +95,6 @@ class HtmlScriptSrcDependency extends ModuleDependency {
89
95
  }
90
96
  }
91
97
 
92
- /**
93
- * @param {Chunk} chunk a chunk
94
- * @param {import("../Compilation")} compilation compilation
95
- * @param {"javascript" | "css"} contentHashType which content hash to plug into the filename template
96
- * @returns {string} chunk filename path (no public-path prefix)
97
- */
98
- const getChunkFilename = (chunk, compilation, contentHashType) => {
99
- const outputOptions = compilation.outputOptions;
100
- let filenameTemplate;
101
- if (contentHashType === "css") {
102
- // For a CSS-typed chunk, use the same template the CSS pipeline
103
- // will use when it actually emits the `.css` file, so the `<link
104
- // rel="stylesheet" href>` URL we write into the HTML matches the
105
- // asset on disk.
106
- filenameTemplate =
107
- require("../css/CssModulesPlugin").getChunkFilenameTemplate(
108
- chunk,
109
- outputOptions
110
- );
111
- } else {
112
- filenameTemplate =
113
- chunk.filenameTemplate ||
114
- (chunk.canBeInitial()
115
- ? outputOptions.filename
116
- : outputOptions.chunkFilename);
117
- }
118
-
119
- return compilation.getPath(filenameTemplate, {
120
- chunk,
121
- contentHashType
122
- });
123
- };
124
-
125
98
  /**
126
99
  * @param {Entrypoint} entrypoint entrypoint
127
100
  * @returns {Chunk[]} every chunk this entrypoint needs in load order: the
@@ -184,6 +157,144 @@ const getEntrypointChunksInLoadOrder = (entrypoint) => {
184
157
  return ordered;
185
158
  };
186
159
 
160
+ /**
161
+ * Whether webpack will emit a `.js` file for this chunk that must be
162
+ * loaded with a `<script>` tag. Covers three independent reasons a
163
+ * chunk needs JS output: it owns one or more JS-source-type modules;
164
+ * it has entry modules whose source types include JavaScript (entry
165
+ * modules don't show up in `getChunkModulesIterableBySourceType` until
166
+ * they're connected as regular modules — this is why
167
+ * `JavascriptModulesPlugin#_chunkHasJs` checks them separately); or it
168
+ * is a runtime chunk — `chunk.hasRuntime()` — which produces a `.js`
169
+ * file holding the webpack runtime, but its `RuntimeModule`s live in
170
+ * a separate `runtimeModules` set and are *not* surfaced via
171
+ * `getChunkModulesIterableBySourceType`. Missing the runtime case
172
+ * would cause a `runtimeChunk`-split chunk to fall out of the
173
+ * `<script>` list and re-emerge after the chunks that depend on it,
174
+ * producing `__webpack_require__ is not defined` at load time.
175
+ * @param {Chunk} chunk chunk
176
+ * @param {ChunkGraph} chunkGraph chunk graph
177
+ * @returns {boolean} true if the chunk emits a `.js` file
178
+ */
179
+ const chunkHasJs = (chunk, chunkGraph) => {
180
+ if (chunk.hasRuntime()) return true;
181
+ if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
182
+ for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) {
183
+ if (chunkGraph.getModuleSourceTypes(module).has(JAVASCRIPT_TYPE)) {
184
+ return true;
185
+ }
186
+ }
187
+ }
188
+ return Boolean(
189
+ chunkGraph.getChunkModulesIterableBySourceType(chunk, JAVASCRIPT_TYPE)
190
+ );
191
+ };
192
+
193
+ /**
194
+ * Whether webpack will emit a `.css` file for this chunk that must be
195
+ * loaded with a `<link rel="stylesheet">` tag. Matches
196
+ * `CssModulesPlugin.chunkHasCss` exactly — both regular CSS modules
197
+ * and pure `@import` placeholder modules count, since the latter
198
+ * still contribute a `.css` asset to the chunk.
199
+ * @param {Chunk} chunk chunk
200
+ * @param {ChunkGraph} chunkGraph chunk graph
201
+ * @returns {boolean} true if the chunk emits a `.css` file
202
+ */
203
+ const chunkHasCss = (chunk, chunkGraph) =>
204
+ Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_TYPE)) ||
205
+ Boolean(
206
+ chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_IMPORT_TYPE)
207
+ );
208
+
209
+ /**
210
+ * Compare two chunks for a deterministic tie-break in CSS link ordering.
211
+ * `chunk.name` and `chunk.id` are both stable strings (when present);
212
+ * one of them is set for every chunk webpack emits. We can't rely on
213
+ * `Array.prototype.sort` being stable — webpack still supports Node
214
+ * 10.13 where V8's sort is not guaranteed stable for arrays larger
215
+ * than ten elements — so any time `firstCssModulePostOrderIndex`
216
+ * returns the same value for two chunks (most commonly when several
217
+ * chunks have no reachable CSS module in the entrypoint's dependency
218
+ * walk and all map to `Infinity`) this comparator picks the canonical
219
+ * order.
220
+ * @param {Chunk} a first chunk
221
+ * @param {Chunk} b second chunk
222
+ * @returns {-1 | 0 | 1} ordering
223
+ */
224
+ const compareChunksForCssTieBreak = (a, b) => {
225
+ const an = `${a.name || ""} ${a.id === null || a.id === undefined ? "" : a.id}`;
226
+ const bn = `${b.name || ""} ${b.id === null || b.id === undefined ? "" : b.id}`;
227
+ if (an < bn) return -1;
228
+ if (an > bn) return 1;
229
+ return 0;
230
+ };
231
+
232
+ /**
233
+ * Smallest post-order index among the CSS modules of a chunk, taken
234
+ * from the entrypoint's view of the dependency graph. Used to sort
235
+ * sibling CSS chunks so they appear in source import order in the
236
+ * extracted HTML — `entrypoint.chunks` itself does not give that
237
+ * ordering for arbitrary splitChunks layouts. Considers both
238
+ * `CSS_TYPE` and `CSS_IMPORT_TYPE` modules so a chunk made up
239
+ * exclusively of `@import` placeholder modules (e.g. when splitChunks
240
+ * separates them from their target CSS) still sorts by its true
241
+ * source position rather than collapsing to `Infinity` and relying on
242
+ * the chunk-name tie-breaker.
243
+ * @param {Chunk} chunk chunk
244
+ * @param {Entrypoint} entrypoint entrypoint the chunk belongs to
245
+ * @param {ChunkGraph} chunkGraph chunk graph
246
+ * @returns {number} the lowest post-order index of any CSS or
247
+ * CSS-import module in the chunk, or `Number.POSITIVE_INFINITY` when
248
+ * no such module has a defined index (e.g. for a module the
249
+ * entrypoint never reached on its own dependency walk — runtime-only
250
+ * modules, modules reached via `dependOn`, etc.) so such chunks sort
251
+ * last among CSS chunks
252
+ */
253
+ const firstCssModulePostOrderIndex = (chunk, entrypoint, chunkGraph) => {
254
+ let min = Number.POSITIVE_INFINITY;
255
+ for (const sourceType of [CSS_TYPE, CSS_IMPORT_TYPE]) {
256
+ const modules = chunkGraph.getChunkModulesIterableBySourceType(
257
+ chunk,
258
+ sourceType
259
+ );
260
+ if (!modules) continue;
261
+ for (const module of modules) {
262
+ const idx = entrypoint.getModulePostOrderIndex(module);
263
+ if (idx !== undefined && idx < min) min = idx;
264
+ }
265
+ }
266
+ return min;
267
+ };
268
+
269
+ const COPYABLE_LINK_ATTRS = ["nonce", "crossorigin", "referrerpolicy"];
270
+
271
+ /**
272
+ * Build a fresh `<link rel="stylesheet" href="…">` for a CSS chunk that
273
+ * was pulled in by a `<script src>` entry — the originating tag was a
274
+ * `<script>`, but the chunk is CSS so cloning the script tag verbatim
275
+ * would produce nonsense (`<script src="…\.css">`). Copy
276
+ * `nonce`/`crossorigin`/`referrerpolicy` from the original element so
277
+ * the same CSP and fetch policy applies; `defer`/`async`/`type` have no
278
+ * meaning on `<link>` and are dropped.
279
+ * @param {string} originalTag the originating `<script>`/`<link>` tag's source text
280
+ * @param {string} href URL for the stylesheet
281
+ * @returns {string} the sibling `<link>` tag's HTML
282
+ */
283
+ const buildStylesheetLink = (originalTag, href) => {
284
+ let extra = "";
285
+ for (const attr of COPYABLE_LINK_ATTRS) {
286
+ // Match ` <attr>`, ` <attr>=value`, ` <attr>="value"`, ` <attr>='value'`.
287
+ const re = new RegExp(
288
+ `\\s${attr}(?:\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>]+))?(?=[\\s/>])`,
289
+ "i"
290
+ );
291
+ const m = originalTag.match(re);
292
+ if (m) extra += m[0];
293
+ }
294
+ const safeHref = href.replace(/"/g, "&quot;");
295
+ return `<link rel="stylesheet" href="${safeHref}"${extra}>`;
296
+ };
297
+
187
298
  /**
188
299
  * Clone the original `<script>`/`<link>` opening tag with its `src`/`href`
189
300
  * value swapped for a different chunk URL. Reusing the source text verbatim
@@ -252,6 +363,7 @@ HtmlScriptSrcDependency.Template = class HtmlScriptSrcDependencyTemplate extends
252
363
  const { runtimeTemplate } = templateContext;
253
364
  const dep = /** @type {HtmlScriptSrcDependency} */ (dependency);
254
365
  const compilation = runtimeTemplate.compilation;
366
+ const { chunkGraph } = compilation;
255
367
  const entrypoint = /** @type {Entrypoint | undefined} */ (
256
368
  compilation.entrypoints.get(dep.entryName)
257
369
  );
@@ -263,50 +375,138 @@ HtmlScriptSrcDependency.Template = class HtmlScriptSrcDependencyTemplate extends
263
375
 
264
376
  const orderedChunks = getEntrypointChunksInLoadOrder(entrypoint);
265
377
  const entryChunk = orderedChunks[orderedChunks.length - 1];
266
- const contentHashType =
267
- dep.elementKind === "stylesheet" ? "css" : "javascript";
268
- const entryUrl = `${CssUrlDependency.PUBLIC_PATH_AUTO}${getChunkFilename(
378
+ const isStylesheet = dep.elementKind === "stylesheet";
379
+
380
+ // Rewrite src/href to a chunk-URL sentinel (resolved by renderManifest):
381
+ // `.css` for `<link rel="stylesheet">`, `.js` for everything else.
382
+ const entryContentHashType = isStylesheet ? "css" : "javascript";
383
+ const entryUrl = HtmlGenerator.makeChunkUrlSentinel(
269
384
  entryChunk,
270
- compilation,
271
- contentHashType
272
- )}`;
385
+ entryContentHashType
386
+ );
273
387
  source.replace(dep.range[0], dep.range[1] - 1, entryUrl);
274
388
 
275
- if (
276
- orderedChunks.length <= 1 ||
277
- dep.tagStart < 0 ||
278
- dep.tagOpenEnd <= dep.tagStart
279
- ) {
389
+ if (dep.tagStart < 0 || dep.tagOpenEnd <= dep.tagStart) {
280
390
  return;
281
391
  }
282
392
 
283
- // The browser must load every chunk in dependency order, not just the
284
- // entry chunk. Clone the original tag for each non-entry chunk so the
285
- // preserved attributes (nonce, crossorigin, …) match the entry tag,
286
- // and insert the clones before the original tag's `<`.
393
+ // The browser must load every chunk the entry needs, not just the
394
+ // entry chunk. For `<script>` entries that's the JS for sibling
395
+ // chunks plus critically the CSS for any chunk that holds
396
+ // stylesheets imported transitively from the JS source. Previously
397
+ // every sibling was cloned as a `<script>` pointing at a `.js`
398
+ // filename, so CSS chunks ended up as `<script src="foo.css">`
399
+ // pointing at non-existent `.js` files (the bug in
400
+ // html-webpack-plugin#1838 / webpack/mini-css-extract-plugin#959,
401
+ // magnified here because the entry chunk's own CSS was emitted to
402
+ // disk but never linked from the HTML at all).
287
403
  const originalContent = /** @type {string} */ (source.original().source());
288
404
  const originalTag = originalContent.slice(dep.tagStart, dep.tagOpenEnd);
289
405
  const srcStartInTag = dep.range[0] - dep.tagStart;
290
406
  const srcEndInTag = dep.range[1] - dep.tagStart;
291
407
 
292
- const siblings = [];
293
- for (let i = 0; i < orderedChunks.length - 1; i++) {
294
- const url = `${CssUrlDependency.PUBLIC_PATH_AUTO}${getChunkFilename(
295
- orderedChunks[i],
296
- compilation,
297
- contentHashType
298
- )}`;
299
- siblings.push(
300
- cloneTagWithUrl(
301
- originalTag,
302
- srcStartInTag,
303
- srcEndInTag,
304
- url,
305
- dep.elementKind
306
- )
408
+ /**
409
+ * @param {Chunk} chunk chunk to emit a sibling tag for
410
+ * @param {"javascript" | "css"} kind content type slice of the chunk to emit
411
+ * @returns {string} a single sibling tag's HTML
412
+ */
413
+ const buildSibling = (chunk, kind) => {
414
+ const url = HtmlGenerator.makeChunkUrlSentinel(chunk, kind);
415
+ if (kind === "css" && !isStylesheet) {
416
+ // Originating tag is `<script>` (or `<link rel=modulepreload>`)
417
+ // but this chunk is CSS — emit a fresh `<link>` rather than
418
+ // cloning the script.
419
+ return buildStylesheetLink(originalTag, url);
420
+ }
421
+ return cloneTagWithUrl(
422
+ originalTag,
423
+ srcStartInTag,
424
+ srcEndInTag,
425
+ url,
426
+ dep.elementKind
307
427
  );
428
+ };
429
+
430
+ const siblings = [];
431
+
432
+ if (isStylesheet) {
433
+ // `<link rel="stylesheet">` entries are CSS-only — every sibling
434
+ // chunk in the entrypoint is also CSS. Keep cloning the original
435
+ // `<link>` for them so attributes like `media` carry over.
436
+ for (let i = 0; i < orderedChunks.length - 1; i++) {
437
+ siblings.push(buildSibling(orderedChunks[i], "css"));
438
+ }
439
+ } else {
440
+ // CSS chunks are emitted before JS chunks so the cascade is set
441
+ // up before any script runs. Within CSS the order needs to match
442
+ // the source's import order — `entrypoint.chunks` alone doesn't
443
+ // give us that for arbitrary splitChunks layouts (splitChunks
444
+ // inserts each new chunk before the entry chunk via
445
+ // `insertChunk(_, before)`, so split CSS siblings end up in
446
+ // *reverse* of the order they were processed — exactly the
447
+ // html-webpack-plugin#1838 / mini-css-extract#959 symptom). We
448
+ // re-derive the order from the entrypoint's module post-order
449
+ // index, which mirrors the dependency walk and so reflects the
450
+ // import order.
451
+ /** @type {{ chunk: Chunk, index: number }[]} */
452
+ const cssChunkOrder = [];
453
+ /** @type {Chunk[]} */
454
+ const jsChunks = [];
455
+ for (let i = 0; i < orderedChunks.length - 1; i++) {
456
+ const chunk = orderedChunks[i];
457
+ const hasCss = chunkHasCss(chunk, chunkGraph);
458
+ const hasJs = chunkHasJs(chunk, chunkGraph);
459
+ if (hasCss) {
460
+ cssChunkOrder.push({
461
+ chunk,
462
+ index: firstCssModulePostOrderIndex(chunk, entrypoint, chunkGraph)
463
+ });
464
+ }
465
+ // Anything that isn't CSS-only stays on the JS lane, in the
466
+ // `orderedChunks` order — that preserves the runtime-first /
467
+ // vendor-before-entry invariant of `getEntrypointChunksInLoadOrder`.
468
+ // Chunks that produce no `.js` and no `.css` (e.g. wasm-only
469
+ // or asset-only) still get a `<script>` clone here so we
470
+ // keep prior behavior for users who relied on it.
471
+ if (hasJs || !hasCss) jsChunks.push(chunk);
472
+ }
473
+ // If the entry chunk itself contains CSS (entry JS imports CSS
474
+ // without splitChunks separating it), fold it into the same CSS
475
+ // ordering so the entry-chunk `<link>` lands in the correct
476
+ // cascade position relative to sibling CSS chunks.
477
+ if (chunkHasCss(entryChunk, chunkGraph)) {
478
+ cssChunkOrder.push({
479
+ chunk: entryChunk,
480
+ index: firstCssModulePostOrderIndex(
481
+ entryChunk,
482
+ entrypoint,
483
+ chunkGraph
484
+ )
485
+ });
486
+ }
487
+ cssChunkOrder.sort((a, b) => {
488
+ // Direct subtraction would yield `NaN` when both indices are
489
+ // `Infinity` (the documented fallback for chunks whose CSS
490
+ // modules the entrypoint's walk never reaches), and
491
+ // `Array#sort` doesn't promise stable ordering on the legacy
492
+ // Node 10 targets this repo still supports — so the
493
+ // tie-breaker must always run when the indices match,
494
+ // including the `Infinity === Infinity` case.
495
+ if (a.index < b.index) return -1;
496
+ if (a.index > b.index) return 1;
497
+ return compareChunksForCssTieBreak(a.chunk, b.chunk);
498
+ });
499
+ for (const { chunk } of cssChunkOrder) {
500
+ siblings.push(buildSibling(chunk, "css"));
501
+ }
502
+ for (const chunk of jsChunks) {
503
+ siblings.push(buildSibling(chunk, "javascript"));
504
+ }
505
+ }
506
+
507
+ if (siblings.length > 0) {
508
+ source.insert(dep.tagStart, siblings.join(""));
308
509
  }
309
- source.insert(dep.tagStart, siblings.join(""));
310
510
  }
311
511
  };
312
512
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const { ASSET_URL_TYPE } = require("../ModuleSourceTypeConstants");
8
9
  const RawDataUrlModule = require("../asset/RawDataUrlModule");
9
10
  const makeSerializable = require("../util/makeSerializable");
10
11
  const memoize = require("../util/memoize");
@@ -12,14 +13,17 @@ const ModuleDependency = require("./ModuleDependency");
12
13
 
13
14
  /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
14
15
  /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
16
+ /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
15
17
  /** @typedef {import("../Dependency")} Dependency */
16
18
  /** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
17
19
  /** @typedef {import("../Module")} Module */
20
+ /** @typedef {import("../Module").BuildInfo} BuildInfo */
18
21
  /** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
19
22
  /** @typedef {import("../Module").CodeGenerationResultData} CodeGenerationResultData */
20
23
  /** @typedef {import("../javascript/JavascriptParser").Range} Range */
21
24
  /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
22
25
  /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
26
+ /** @typedef {import("../util/Hash")} Hash */
23
27
  /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
24
28
 
25
29
  const getIgnoredRawDataUrlModule = memoize(
@@ -54,6 +58,21 @@ class HtmlSourceDependency extends ModuleDependency {
54
58
  return getIgnoredRawDataUrlModule();
55
59
  }
56
60
 
61
+ /**
62
+ * Updates the hash with the data contributed by this instance.
63
+ * @param {Hash} hash hash to be updated
64
+ * @param {UpdateHashContext} context context
65
+ * @returns {void}
66
+ */
67
+ updateHash(hash, context) {
68
+ // Fold in the asset's hash so the HTML invalidates when the embedded URL changes.
69
+ const { chunkGraph } = context;
70
+ const module = chunkGraph.moduleGraph.getModule(this);
71
+ if (!module) return;
72
+ const { hash: buildHash } = /** @type {BuildInfo} */ (module.buildInfo);
73
+ if (buildHash) hash.update(buildHash);
74
+ }
75
+
57
76
  /**
58
77
  * Serializes this instance into the provided serializer context.
59
78
  * @param {ObjectSerializerContext} context context
@@ -114,8 +133,8 @@ HtmlSourceDependency.Template = class HtmlSourceDependencyTemplate extends (
114
133
  const data = codeGen.data;
115
134
  if (!data) return "data:,";
116
135
  const url = data.get("url");
117
- if (!url || !url["css-url"]) return "data:,";
118
- return url["css-url"];
136
+ if (!url || !url[ASSET_URL_TYPE]) return "data:,";
137
+ return url[ASSET_URL_TYPE];
119
138
  }
120
139
  };
121
140
 
@@ -349,10 +349,24 @@ class WorkerPlugin {
349
349
  )
350
350
  );
351
351
  } else {
352
- Object.assign(
353
- entryOptions,
354
- importOptions.webpackEntryOptions
355
- );
352
+ // `webpackEntryOptions` is user input from a magic
353
+ // comment, so copy only safe own keys to avoid
354
+ // prototype pollution via `__proto__`/`constructor`/
355
+ // `prototype`.
356
+ const userEntryOptions = importOptions.webpackEntryOptions;
357
+ for (const key of Object.keys(userEntryOptions)) {
358
+ if (
359
+ key === "__proto__" ||
360
+ key === "constructor" ||
361
+ key === "prototype"
362
+ ) {
363
+ continue;
364
+ }
365
+ /** @type {EXPECTED_ANY} */
366
+ (entryOptions)[key] = /** @type {EXPECTED_ANY} */ (
367
+ userEntryOptions
368
+ )[key];
369
+ }
356
370
  }
357
371
  }
358
372
  if (importOptions.webpackChunkName !== undefined) {
@@ -18,6 +18,7 @@ const {
18
18
  const RuntimeGlobals = require("../RuntimeGlobals");
19
19
  const Template = require("../Template");
20
20
  const CommonJsRequireDependency = require("../dependencies/CommonJsRequireDependency");
21
+ const { resolveByProperty } = require("../util/cleverMerge");
21
22
  const { registerNotSerializable } = require("../util/serialization");
22
23
 
23
24
  /** @typedef {import("../config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
@@ -45,6 +46,96 @@ const { registerNotSerializable } = require("../util/serialization");
45
46
 
46
47
  /** @typedef {{ client: string, data: string, active: boolean }} ModuleResult */
47
48
 
49
+ /**
50
+ * Library wrappers of these types pass external modules as closure arguments
51
+ * (e.g. `__WEBPACK_EXTERNAL_MODULE_react__`) baked into the entry chunk at
52
+ * render time. When `lazyCompilation` activates a proxy for the first time,
53
+ * any external dependency the lazily-built module pulls in lands in a hot
54
+ * update chunk that lives outside the original wrapper closure, so the
55
+ * factory body can't resolve its closure identifier and throws at runtime.
56
+ * Reserving the externals up front (during the inactive build) folds them
57
+ * into the initial wrapper, so the closure identifiers are already defined
58
+ * when the activation update arrives.
59
+ */
60
+ const CLOSURE_LIBRARY_TYPES = new Set([
61
+ "umd",
62
+ "umd2",
63
+ "amd",
64
+ "amd-require",
65
+ "system"
66
+ ]);
67
+
68
+ /**
69
+ * `enabledLibraryTypes` covers both the global `output.library.type` and any
70
+ * per-entry `entry.<name>.library.type`, so a UMD/AMD/System wrapper attached
71
+ * to an individual entry is still detected.
72
+ * @param {import("../../declarations/WebpackOptions").OutputNormalized} output normalized output option
73
+ * @returns {boolean} true when at least one library wrapper passes externals as closure arguments
74
+ */
75
+ const hasClosureLibrary = (output) => {
76
+ const enabled = output.enabledLibraryTypes;
77
+ if (enabled) {
78
+ for (const type of enabled) {
79
+ if (CLOSURE_LIBRARY_TYPES.has(type)) return true;
80
+ }
81
+ }
82
+ if (output.library && output.library.type) {
83
+ return CLOSURE_LIBRARY_TYPES.has(output.library.type);
84
+ }
85
+ return false;
86
+ };
87
+
88
+ /**
89
+ * Collects request strings from statically-enumerable externals (string,
90
+ * object, and arrays of those). Function and RegExp forms are skipped because
91
+ * their effective request set isn't knowable until something asks for it.
92
+ *
93
+ * Layer resolution mirrors `ExternalModuleFactoryPlugin.resolveLayer`: the
94
+ * effective map for the proxy's layer is computed via the same
95
+ * `resolveByProperty(..., "byLayer", layer)` helper that the externals system
96
+ * uses, so `byLayer.default` fallback and function-form `byLayer` entries are
97
+ * honored the same way.
98
+ *
99
+ * Entries whose effective value is `false` are skipped — `false` explicitly
100
+ * disables externalization for that request, and reserving it would force the
101
+ * real module into the entry chunk.
102
+ * @param {import("../../declarations/WebpackOptions").Externals | undefined} externals normalized externals option
103
+ * @param {string | null} layer issuer layer for which to resolve `byLayer`
104
+ * @returns {Set<string>} requests to reserve in the entry chunk
105
+ */
106
+ const collectStaticExternalRequests = (externals, layer) => {
107
+ /** @type {Set<string>} */
108
+ const requests = new Set();
109
+ if (!externals) return requests;
110
+ /** @param {import("../../declarations/WebpackOptions").ExternalItem} item one item */
111
+ const visit = (item) => {
112
+ if (typeof item === "string") {
113
+ requests.add(item);
114
+ return;
115
+ }
116
+ if (!item || typeof item !== "object" || item instanceof RegExp) return;
117
+ const resolved = /** @type {Record<string, unknown>} */ (
118
+ resolveByProperty(
119
+ /** @type {Record<string, unknown>} */ (item),
120
+ "byLayer",
121
+ layer
122
+ )
123
+ );
124
+ for (const [request, value] of Object.entries(resolved)) {
125
+ // `false` explicitly opts the request out of externalization; reserving
126
+ // it would pull the actual module into the entry chunk.
127
+ if (value === false) continue;
128
+ requests.add(request);
129
+ }
130
+ };
131
+ if (Array.isArray(externals)) {
132
+ for (const item of externals) visit(item);
133
+ } else {
134
+ visit(externals);
135
+ }
136
+ return requests;
137
+ };
138
+
48
139
  /**
49
140
  * Defines the backend api type used by this module.
50
141
  * @typedef {object} BackendApi
@@ -213,6 +304,19 @@ class LazyCompilationProxyModule extends Module {
213
304
  const block = new AsyncDependenciesBlock({});
214
305
  block.addDependency(dep);
215
306
  this.addBlock(block);
307
+ } else if (hasClosureLibrary(compilation.options.output)) {
308
+ // Reserve statically-declared externals as dependencies of the inactive
309
+ // proxy so the initial entry chunk's library wrapper already exposes
310
+ // their closure identifiers (e.g. `__WEBPACK_EXTERNAL_MODULE_react__`).
311
+ // Once the proxy activates and the lazily-built module references those
312
+ // externals, the identifiers resolve normally instead of throwing.
313
+ const requests = collectStaticExternalRequests(
314
+ options.externals,
315
+ this.layer
316
+ );
317
+ for (const request of requests) {
318
+ this.addDependency(new CommonJsRequireDependency(request));
319
+ }
216
320
  }
217
321
  callback();
218
322
  }