webpack 5.110.0 → 5.110.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 (52) hide show
  1. package/README.md +10 -17
  2. package/lib/ExportsInfo.js +10 -0
  3. package/lib/FileSystemInfo.js +17 -2
  4. package/lib/Generator.js +17 -0
  5. package/lib/NormalModule.js +3 -3
  6. package/lib/RuntimeTemplate.js +423 -430
  7. package/lib/asset/AssetBytesGenerator.js +1 -3
  8. package/lib/asset/AssetGenerator.js +1 -3
  9. package/lib/asset/AssetSourceGenerator.js +1 -3
  10. package/lib/config/defaults.js +6 -0
  11. package/lib/css/CssGenerator.js +1 -3
  12. package/lib/css/CssLoadingRuntimeModule.js +32 -13
  13. package/lib/css/CssModulesPlugin.js +9 -17
  14. package/lib/css/data.js +13 -3
  15. package/lib/css/syntax.js +78 -10
  16. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +10 -4
  17. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +2 -0
  18. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +48 -3
  19. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +83 -14
  20. package/lib/dependencies/HarmonyImportSideEffectDependency.js +114 -0
  21. package/lib/dependencies/WorkerAndWorkletPlugin.js +1 -0
  22. package/lib/dependencies/WorkerDependency.js +6 -12
  23. package/lib/dependencies/WorkletDependency.js +7 -11
  24. package/lib/esm/ModuleChunkLoadingPlugin.js +6 -9
  25. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +7 -1
  26. package/lib/html/HtmlGenerator.js +1 -1
  27. package/lib/html/HtmlModulesPlugin.js +55 -16
  28. package/lib/html/syntax.js +113 -26
  29. package/lib/javascript/JavascriptGenerator.js +1 -1
  30. package/lib/javascript/JavascriptModulesPlugin.js +3 -10
  31. package/lib/json/JsonGenerator.js +1 -1
  32. package/lib/loaders/LoaderRunner.js +33 -5
  33. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +8 -15
  34. package/lib/optimize/ConcatenatedModule.js +8 -0
  35. package/lib/optimize/ConstExportsPlugin.js +5 -0
  36. package/lib/optimize/InlineExports.js +121 -0
  37. package/lib/optimize/RealContentHashPlugin.js +111 -35
  38. package/lib/prefetch/ResourceHintPlugin.js +6 -12
  39. package/lib/serialization/ObjectMiddleware.js +128 -108
  40. package/lib/typescript/TypeScriptPlugin.js +8 -10
  41. package/lib/util/SourceProcessor.js +24 -8
  42. package/lib/util/createMappings.js +25 -3
  43. package/lib/util/removeBOM.js +82 -1
  44. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +5 -3
  45. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +5 -3
  46. package/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +3 -1
  47. package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +7 -12
  48. package/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +3 -1
  49. package/lib/web/FetchCompileAsyncWasmPlugin.js +10 -6
  50. package/lib/web/FetchCompileWasmPlugin.js +4 -2
  51. package/package.json +1 -1
  52. package/types.d.ts +77 -10
@@ -178,6 +178,20 @@ const DEFER_BAILOUT =
178
178
  * @property {boolean | null} loaded whether the chunk loader fetched the chunk
179
179
  */
180
180
 
181
+ /**
182
+ * @typedef {object} AnalyzableChunkUrls
183
+ * @property {Map<ChunkId, string>} urls the urls that could be written, by chunk id
184
+ * @property {boolean} complete false when some chunk kept the runtime form, whose
185
+ * ids the consumer then still resolves through the runtime name lookup
186
+ */
187
+
188
+ /**
189
+ * @typedef {object} WasmGroups
190
+ * @property {(key: string) => string} groupOf the group a runtime key answers with
191
+ * @property {Set<string>} fetching groups an entry of which reads binaries with `fetch`
192
+ * @property {Map<string, boolean>} onlyFetching groups every entry of which fetches
193
+ */
194
+
181
195
  // A reference in no chunk at all: nothing is known about where it is read from.
182
196
  /** @type {Placement} */
183
197
  const NO_PLACEMENT = { undo: null, loaded: null };
@@ -242,6 +256,10 @@ const SPECIFIER_PART_KINDS = new Set([
242
256
  // Only these carry a chunk id, which is the one part value that may be a number.
243
257
  const CHUNK_SPECIFIER_PART_KINDS = new Set(["chunk", "cssChunk"]);
244
258
 
259
+ // Only these are spelled out of the compilation hash, so a stand-in holding one moves
260
+ // the chunk it lands in into the round after that hash settles.
261
+ const FULL_HASH_PART_KINDS = new Set(["publicPath", "template", "unserved"]);
262
+
245
263
  // What the deferred pass scans for — the other half of what the class spells.
246
264
  const ANALYZABLE_TOKEN_REGEXP = /\.\/@@webpackAnalyzableChunk:([\w-]+)@@/g;
247
265
 
@@ -331,6 +349,25 @@ const createHashProbeChunk = (chunk, hash, present) => {
331
349
  return probeChunk;
332
350
  };
333
351
 
352
+ /**
353
+ * A filename function's answer to the stand-in hash, which says what shape the name
354
+ * has before any real hash exists.
355
+ * @param {Exclude<ChunkFilenameTemplate, string>} template the filename function
356
+ * @param {Chunk} chunk the chunk being referenced
357
+ * @param {string} contentHashType which of the chunk's hashes the name reads
358
+ * @param {string} hash the stand-in hash
359
+ * @param {boolean} present whether the chunk is claimed to carry content hashes
360
+ * @returns {string} what the function names it
361
+ */
362
+ const probeTemplateName = (template, chunk, contentHashType, hash, present) =>
363
+ template({
364
+ chunk: createHashProbeChunk(chunk, hash, present),
365
+ runtime: chunk.runtime,
366
+ contentHashType,
367
+ hash,
368
+ contentHash: hash
369
+ });
370
+
334
371
  /**
335
372
  * No module id error message.
336
373
  * @param {Module} module the module
@@ -426,14 +463,10 @@ class RuntimeTemplate {
426
463
  this._placementByChunk = new WeakMap();
427
464
  /** @type {Map<string, string | null | undefined>} */
428
465
  this._entryBaseUriByRuntime = new Map();
429
- /** @type {Map<string, string> | undefined} */
430
- this._wasmRuntimeGroupsByKey = undefined;
431
- /** @type {Set<string> | false | undefined} */
432
- this._wasmFetchingGroupSet = undefined;
466
+ /** @type {WasmGroups | undefined} */
467
+ this._wasmGroupsValue = undefined;
433
468
  /** @type {Map<string, boolean> | undefined} */
434
469
  this._wasmAnchorKnownByGroup = undefined;
435
- /** @type {Map<string, boolean> | undefined} */
436
- this._wasmGroupOnlyFetchesMap = undefined;
437
470
  }
438
471
 
439
472
  isIIFE() {
@@ -630,28 +663,28 @@ class RuntimeTemplate {
630
663
  placedModule
631
664
  )
632
665
  ) {
633
- this._analyzableBailout(
666
+ return this._analyzableBailout(
634
667
  module,
635
- "__webpack_public_path__ is reassigned in a runtime this module belongs to"
668
+ "__webpack_public_path__ is reassigned in a runtime this module belongs to",
669
+ false
636
670
  );
637
- return false;
638
671
  }
639
672
 
640
673
  if (form === "import") {
641
674
  // Read through a native `import()`, or it is not this feature at all.
642
675
  if (outputOptions.chunkFormat !== "module") {
643
- this._analyzableBailout(
676
+ return this._analyzableBailout(
644
677
  module,
645
- `output.chunkFormat is ${JSON.stringify(outputOptions.chunkFormat)}, so chunks are not read through a native import()`
678
+ `output.chunkFormat is ${JSON.stringify(outputOptions.chunkFormat)}, so chunks are not read through a native import()`,
679
+ false
646
680
  );
647
- return false;
648
681
  }
649
682
  if (outputOptions.importFunctionName !== "import") {
650
- this._analyzableBailout(
683
+ return this._analyzableBailout(
651
684
  module,
652
- `output.importFunctionName is ${JSON.stringify(outputOptions.importFunctionName)}, so the call site is not a native import()`
685
+ `output.importFunctionName is ${JSON.stringify(outputOptions.importFunctionName)}, so the call site is not a native import()`,
686
+ false
653
687
  );
654
- return false;
655
688
  }
656
689
  // A worker loading its own chunks some other way keeps that runtime; one on
657
690
  // `import` uses the same ESM loader as the main graph, so it can be analyzable.
@@ -662,29 +695,23 @@ class RuntimeTemplate {
662
695
  if (!entryOptions || !entryOptions.worker) continue;
663
696
  // `WorkerAndWorkletPlugin` always seeds this from `output.workerChunkLoading`.
664
697
  if (entryOptions.chunkLoading !== "import") {
665
- this._analyzableBailout(
698
+ return this._analyzableBailout(
666
699
  module,
667
- `this worker loads its chunks with ${JSON.stringify(entryOptions.chunkLoading)}, not "import"`
700
+ `this worker loads its chunks with ${JSON.stringify(entryOptions.chunkLoading)}, not "import"`,
701
+ false
668
702
  );
669
- return false;
670
703
  }
671
704
  }
672
705
  return true;
673
706
  }
674
707
 
675
708
  // `url-inline` asks whether the file can be named at the call site at all, and
676
- // its `.p + <file>` fallback spells that without `import.meta` — so neither gate
677
- // below rules it out, and the asset's javascript wrapper stays dropped.
709
+ // its `.p + <file>` fallback spells that without `import.meta` — so the gate
710
+ // below does not rule it out, and the asset's javascript wrapper stays dropped.
711
+ // `environment.module` is deliberately not consulted: ESM output writes
712
+ // `import.meta` regardless (the public path, the chunk loader), so the url forms
713
+ // only match what the rest of the bundle already assumes the target reads.
678
714
  if (form === "url-inline") return true;
679
- // `import.meta` is ESM syntax the target has to read. A chunk `import()` is
680
- // emitted by the module chunk loader either way, so only the url forms check.
681
- if (!this.supportsEcmaScriptModuleSyntax()) {
682
- this._analyzableBailout(
683
- module,
684
- "output.environment.module is false, so the target does not read the import.meta this form needs"
685
- );
686
- return false;
687
- }
688
715
  // `eval` devtool wraps each module in `eval(...)`, where `import.meta` is a
689
716
  // syntax error. Runtime modules are emitted beside them, never wrapped.
690
717
  const { devtool } = this.compilation.options;
@@ -693,22 +720,22 @@ class RuntimeTemplate {
693
720
  typeof devtool === "string" &&
694
721
  devtool.includes("eval")
695
722
  ) {
696
- this._analyzableBailout(
723
+ return this._analyzableBailout(
697
724
  module,
698
- `devtool ${JSON.stringify(devtool)} wraps the module in eval(), where import.meta does not parse`
725
+ `devtool ${JSON.stringify(devtool)} wraps the module in eval(), where import.meta does not parse`,
726
+ false
699
727
  );
700
- return false;
701
728
  }
702
729
  if (form === "url" || form === "url-runtime") return true;
703
730
  // A bare relative URL is what `__webpack_require__.p + path` means only under an
704
731
  // `auto` public path; anything else has to be baked, which is the "wasm" form.
705
732
  if (form === "wasm-relative") {
706
733
  if (outputOptions.publicPath === "auto") return true;
707
- this._analyzableBailout(
734
+ return this._analyzableBailout(
708
735
  module,
709
- "output.publicPath is set, so a bare relative url no longer means what the public path would have resolved to"
736
+ "output.publicPath is set, so a bare relative url no longer means what the public path would have resolved to",
737
+ false
710
738
  );
711
- return false;
712
739
  }
713
740
 
714
741
  // The rest is the `"wasm"` form: whether the whole binary url can be spelled
@@ -727,11 +754,11 @@ class RuntimeTemplate {
727
754
  this._anyWasmChunkFetches(runtime) &&
728
755
  !this._wasmFetchAnchorIsKnown(runtime)
729
756
  ) {
730
- this._analyzableBailout(
757
+ return this._analyzableBailout(
731
758
  module,
732
- "output.publicPath needs a base, and no one path back to the document fits every chunk `fetch` reads it from"
759
+ "output.publicPath needs a base, and no one path back to the document fits every chunk `fetch` reads it from",
760
+ false
733
761
  );
734
- return false;
735
762
  }
736
763
  }
737
764
  // The compilation hash is settled after code generation, so a name carrying one
@@ -753,19 +780,22 @@ class RuntimeTemplate {
753
780
  * the channel `ModuleConcatenationPlugin` already reports through, so it reaches
754
781
  * `stats.optimizationBailout`. Silent unless the build asked for ESM output, where
755
782
  * alone the answer is actionable; deduplicated because a module may write many.
783
+ * Answers with `answer` so a caller states the refusal and its reason at once —
784
+ * `false` where it answers a question, `null` where it was building a specifier.
785
+ * @template {false | null} T
756
786
  * @param {Module | undefined} module the module the reference is emitted into
757
787
  * @param {string} reason why no literal could be baked
758
- * @returns {void}
788
+ * @param {T} answer what the caller hands back
789
+ * @returns {T} that answer
759
790
  */
760
- _analyzableBailout(module, reason) {
761
- if (module === undefined || !this.outputOptions.module) return;
762
- const text = `Analyzable ESM bailout: ${reason}`;
763
- const bailouts =
764
- this.compilation.moduleGraph.getOptimizationBailout(module);
765
- for (const existing of bailouts) {
766
- if (existing === text) return;
791
+ _analyzableBailout(module, reason, answer) {
792
+ if (module !== undefined && this.outputOptions.module) {
793
+ const text = `Analyzable ESM bailout: ${reason}`;
794
+ const bailouts =
795
+ this.compilation.moduleGraph.getOptimizationBailout(module);
796
+ if (!bailouts.includes(text)) bailouts.push(text);
767
797
  }
768
- bailouts.push(text);
798
+ return answer;
769
799
  }
770
800
 
771
801
  /**
@@ -810,8 +840,7 @@ class RuntimeTemplate {
810
840
  */
811
841
  _canDeferOrBail(chunks, module) {
812
842
  if (this._canDeferAnalyzableName(chunks)) return true;
813
- this._analyzableBailout(module, DEFER_BAILOUT);
814
- return false;
843
+ return this._analyzableBailout(module, DEFER_BAILOUT, false);
815
844
  }
816
845
 
817
846
  /**
@@ -861,9 +890,7 @@ class RuntimeTemplate {
861
890
  */
862
891
  _partsBakeFullHash(parts) {
863
892
  for (const [kind, value] of parts) {
864
- if (kind === "publicPath" || kind === "template" || kind === "unserved") {
865
- return true;
866
- }
893
+ if (FULL_HASH_PART_KINDS.has(kind)) return true;
867
894
  if (kind === "literal" && this._hasReservedFullHash(String(value))) {
868
895
  return true;
869
896
  }
@@ -962,95 +989,83 @@ class RuntimeTemplate {
962
989
  const base = this.analyzableUrlReadsBaseUri()
963
990
  ? this.entryBaseUri(runtime)
964
991
  : undefined;
965
- if (base !== undefined) {
966
- if (base === null) {
967
- this._analyzableBailout(
968
- module,
969
- "the entries this module is generated for set different baseUri values, so no one url resolves for all of them"
970
- );
971
- return null;
972
- }
973
- // A relative one is no base of its own: the runtime reads it against the
974
- // chunk, so the literal spells it there rather than resolving against it.
975
- if (isChunkRelativeBaseUri(base)) {
976
- return this._getAnalyzableFileSpecifier(
977
- module,
978
- chunkGraph,
979
- [["literal", filename]],
980
- true,
981
- base
982
- );
983
- }
984
- const chunks = this._moduleChunks(module, chunkGraph);
985
- // A protocol-relative base names a host but no scheme, and the runtime reads it
986
- // against the chunk's own url to get one — the very url this literal is
987
- // resolved against — so it may stay protocol-relative instead of being settled
988
- // here. Nothing walks back to the output root first: the base replaces it.
989
- if (base.startsWith("//")) {
990
- const relative = this._resolvePublicPathPrefix(
991
- publicPath,
992
- module,
993
- chunks
994
- );
995
- return relative === null
996
- ? null
997
- : this._specifierOf(
998
- [
999
- ["literal", base],
1000
- ...rootedParts(relative),
1001
- ["literal", filename]
1002
- ],
1003
- chunks,
1004
- module
1005
- );
1006
- }
1007
- if (!isAbsoluteBaseUri(base)) {
1008
- this._analyzableBailout(
1009
- module,
1010
- `an entry sets a baseUri of ${JSON.stringify(base)}, whose scheme names no base anything can be resolved against`
1011
- );
1012
- return null;
1013
- }
1014
- const parts = this._resolvePublicPathPrefix(publicPath, module, chunks);
1015
- if (parts === null) return null;
1016
- // Resolved here rather than against the output root, and only an absolute base
1017
- // settles it — a relative one has no base of its own to be read against.
1018
- const spelled = literalText(parts);
1019
- // A hash the deferred pass still has to fill in cannot be resolved against the
1020
- // base here: the fill would land inside an already-resolved url, where a hash
1021
- // opening with a letter reads as a scheme and drops the base entirely.
1022
- const text =
1023
- spelled !== null && !this._hasReservedFullHash(spelled)
1024
- ? spelled
1025
- : null;
1026
- /** @type {URL} */
1027
- let resolved;
1028
- try {
1029
- // Empty where only the deferred pass can spell the rest, which still settles
1030
- // whether the base itself is one anything may be resolved against.
1031
- resolved = new URL(text === null ? "" : text + filename, base);
1032
- } catch (_error) {
1033
- this._analyzableBailout(
1034
- module,
1035
- `an entry sets a baseUri of ${JSON.stringify(base)}, which is not absolute, so no url can be resolved against it here`
1036
- );
1037
- return null;
1038
- }
1039
- if (text !== null) return toJsStringLiteral(resolved.href);
1040
- if (!this._canDeferOrBail(chunks, module)) return null;
1041
- return toJsStringLiteral(
1042
- this._reserveAnalyzableSpecifier([
1043
- ["base", base],
1044
- ...parts,
1045
- ["literal", filename]
1046
- ])
992
+ // No base of its own: the name is spelled against the output root, as a chunk is.
993
+ if (base === undefined) {
994
+ return this._getAnalyzableFileSpecifier(
995
+ module,
996
+ chunkGraph,
997
+ [["literal", filename]],
998
+ true
1047
999
  );
1048
1000
  }
1049
- return this._getAnalyzableFileSpecifier(
1050
- module,
1051
- chunkGraph,
1052
- [["literal", filename]],
1053
- true
1001
+ if (base === null) {
1002
+ return this._analyzableBailout(
1003
+ module,
1004
+ "the entries this module is generated for set different baseUri values, so no one url resolves for all of them",
1005
+ null
1006
+ );
1007
+ }
1008
+ // A relative one is no base of its own: the runtime reads it against the
1009
+ // chunk, so the literal spells it there rather than resolving against it.
1010
+ if (isChunkRelativeBaseUri(base)) {
1011
+ return this._getAnalyzableFileSpecifier(
1012
+ module,
1013
+ chunkGraph,
1014
+ [["literal", filename]],
1015
+ true,
1016
+ base
1017
+ );
1018
+ }
1019
+ const chunks = this._moduleChunks(module, chunkGraph);
1020
+ const parts = this._resolvePublicPathPrefix(publicPath, module, chunks);
1021
+ if (parts === null) return null;
1022
+ // A protocol-relative base names a host but no scheme, and the runtime reads it
1023
+ // against the chunk's own url to get one — the very url this literal is
1024
+ // resolved against — so it may stay protocol-relative instead of being settled
1025
+ // here. Nothing walks back to the output root first: the base replaces it.
1026
+ if (base.startsWith("//")) {
1027
+ return this._specifierOf(
1028
+ [["literal", base], ...rootedParts(parts), ["literal", filename]],
1029
+ chunks,
1030
+ module
1031
+ );
1032
+ }
1033
+ if (!isAbsoluteBaseUri(base)) {
1034
+ return this._analyzableBailout(
1035
+ module,
1036
+ `an entry sets a baseUri of ${JSON.stringify(base)}, whose scheme names no base anything can be resolved against`,
1037
+ null
1038
+ );
1039
+ }
1040
+ // Resolved here rather than against the output root, and only an absolute base
1041
+ // settles it — a relative one has no base of its own to be read against.
1042
+ const spelled = literalText(parts);
1043
+ // A hash the deferred pass still has to fill in cannot be resolved against the
1044
+ // base here: the fill would land inside an already-resolved url, where a hash
1045
+ // opening with a letter reads as a scheme and drops the base entirely.
1046
+ const text =
1047
+ spelled !== null && !this._hasReservedFullHash(spelled) ? spelled : null;
1048
+ /** @type {URL} */
1049
+ let resolved;
1050
+ try {
1051
+ // Empty where only the deferred pass can spell the rest, which still settles
1052
+ // whether the base itself is one anything may be resolved against.
1053
+ resolved = new URL(text === null ? "" : text + filename, base);
1054
+ } catch (_error) {
1055
+ return this._analyzableBailout(
1056
+ module,
1057
+ `an entry sets a baseUri of ${JSON.stringify(base)}, which is not absolute, so no url can be resolved against it here`,
1058
+ null
1059
+ );
1060
+ }
1061
+ if (text !== null) return toJsStringLiteral(resolved.href);
1062
+ if (!this._canDeferOrBail(chunks, module)) return null;
1063
+ return toJsStringLiteral(
1064
+ this._reserveAnalyzableSpecifier([
1065
+ ["base", base],
1066
+ ...parts,
1067
+ ["literal", filename]
1068
+ ])
1054
1069
  );
1055
1070
  }
1056
1071
 
@@ -1121,55 +1136,119 @@ class RuntimeTemplate {
1121
1136
  return publicPath.includes("[") ? undefined : publicPath;
1122
1137
  }
1123
1138
 
1139
+ /**
1140
+ * Runtime keys grouped by the binaries they share, with what the entries of each
1141
+ * group asked for. Code generation runs once for runtimes a module hashes alike in,
1142
+ * and nothing in a binary's hash knows which loader will read it — so a binary two
1143
+ * runtimes reach carries one shape into both, and a third runtime sharing another
1144
+ * binary with either is pulled in after them. Only an entry names a loader; every
1145
+ * other chunk of the runtime is served by the one its entry asked for. Asked of the
1146
+ * same modules the loaders are created for, and only once per compilation.
1147
+ * @returns {WasmGroups} the grouping, and which groups fetch
1148
+ */
1149
+ _wasmGroups() {
1150
+ if (this._wasmGroupsValue === undefined) {
1151
+ const { chunkGraph, chunks, modules } = this.compilation;
1152
+ /** @type {Map<string, string>} */
1153
+ const parent = new Map();
1154
+ /**
1155
+ * @param {string} key runtime key
1156
+ * @returns {string} the key naming its group
1157
+ */
1158
+ const find = (key) => {
1159
+ let root = key;
1160
+ let seen = parent.get(root);
1161
+ while (seen !== undefined && seen !== root) {
1162
+ root = seen;
1163
+ seen = parent.get(root);
1164
+ }
1165
+ // Path compression keeps a long share chain from costing more than once.
1166
+ let walk = key;
1167
+ let next = parent.get(walk);
1168
+ while (next !== undefined && next !== root) {
1169
+ parent.set(walk, root);
1170
+ walk = next;
1171
+ next = parent.get(walk);
1172
+ }
1173
+ return root;
1174
+ };
1175
+ /** @type {string[]} */
1176
+ const keys = [];
1177
+ for (const module of modules) {
1178
+ if (module.type !== WEBASSEMBLY_MODULE_TYPE_ASYNC) continue;
1179
+ keys.length = 0;
1180
+ for (const runtime of chunkGraph.getModuleRuntimes(module)) {
1181
+ forEachRuntime(runtime, (key) => {
1182
+ keys.push(/** @type {string} */ (key));
1183
+ });
1184
+ }
1185
+ for (const key of keys) if (!parent.has(key)) parent.set(key, key);
1186
+ // Every runtime this binary reaches answers with the first one.
1187
+ for (let i = 1; i < keys.length; i++) {
1188
+ parent.set(find(keys[i]), find(keys[0]));
1189
+ }
1190
+ }
1191
+ // Flattened once, so a lookup is a single `get` rather than a chain walk.
1192
+ for (const key of parent.keys()) parent.set(key, find(key));
1193
+ /**
1194
+ * @param {string} key a runtime key
1195
+ * @returns {string} the group it answers with, or itself when it shares nothing
1196
+ */
1197
+ const groupOf = (key) => {
1198
+ const group = parent.get(key);
1199
+ // An entry named "" makes "" a runtime key, so test absence, not a falsy value.
1200
+ return group === undefined ? key : group;
1201
+ };
1202
+ /** @type {Set<string>} */
1203
+ const fetching = new Set();
1204
+ /** @type {Map<string, boolean>} */
1205
+ const onlyFetching = new Map();
1206
+ for (const chunk of chunks) {
1207
+ if (!chunk.getEntryOptions()) continue;
1208
+ const fetches = this._chunkWasmLoading(chunk) === "fetch";
1209
+ forEachRuntime(chunk.runtime, (key) => {
1210
+ const group = groupOf(/** @type {string} */ (key));
1211
+ if (fetches) fetching.add(group);
1212
+ onlyFetching.set(group, onlyFetching.get(group) !== false && fetches);
1213
+ });
1214
+ }
1215
+ this._wasmGroupsValue = { groupOf, fetching, onlyFetching };
1216
+ }
1217
+ return this._wasmGroupsValue;
1218
+ }
1219
+
1220
+ /**
1221
+ * @param {RuntimeSpec} runtime the runtime being asked about
1222
+ * @returns {string[]} the wasm runtime groups it reaches
1223
+ */
1224
+ _wasmGroupsOf(runtime) {
1225
+ const { groupOf } = this._wasmGroups();
1226
+ /** @type {string[]} */
1227
+ const reached = [];
1228
+ forEachRuntime(runtime, (key) => {
1229
+ reached.push(groupOf(/** @type {string} */ (key)));
1230
+ });
1231
+ return reached;
1232
+ }
1233
+
1124
1234
  /**
1125
1235
  * Whether a chunk loads WebAssembly through `fetch`, which is the only loader the
1126
1236
  * public path reaches: `readFile` resolves the binary's name against the chunk it is
1127
1237
  * read from, exactly as a baked literal does, so a public path is irrelevant to it.
1128
- * Answered per runtime rather than per chunk on purpose — a module's generated source
1238
+ * Answered per group rather than per chunk on purpose — a module's generated source
1129
1239
  * and the runtime module of every chunk holding it have to agree on the shape, and
1130
- * they ask from opposite ends; one loader serves a whole runtime, so that is the
1131
- * finest scope on which they can. Without a runtime, or where the runtimes cannot be
1132
- * told apart, the answer covers the compilation.
1240
+ * they ask from opposite ends; one loader serves a whole group, so that is the finest
1241
+ * scope on which they can. Without a runtime the answer covers the compilation.
1133
1242
  * @param {RuntimeSpec=} runtime the runtime being asked about
1134
1243
  * @returns {boolean} true when a chunk of that runtime fetches its binaries
1135
1244
  */
1136
1245
  _anyWasmChunkFetches(runtime) {
1137
- const fetching = this._wasmFetchingGroups();
1138
- if (fetching === undefined) return false;
1246
+ const { fetching } = this._wasmGroups();
1247
+ if (fetching.size === 0) return false;
1139
1248
  // Without a runtime to place it in there is nothing to narrow by, so any chunk
1140
1249
  // answers for all of them.
1141
1250
  if (runtime === undefined) return true;
1142
- const groups = this._wasmRuntimeGroups();
1143
- if (typeof runtime === "string") {
1144
- return fetching.has(this._wasmGroupOf(groups, runtime));
1145
- }
1146
- for (const key of runtime) {
1147
- if (fetching.has(this._wasmGroupOf(groups, key))) return true;
1148
- }
1149
- return false;
1150
- }
1151
-
1152
- /**
1153
- * Whether every entry of a wasm runtime group reads its binaries with `fetch`. Only
1154
- * an entry names a loader, so the scan is of entries, as `_wasmFetchingGroups` is.
1155
- * @returns {Map<string, boolean>} whether each group fetches and nothing else
1156
- */
1157
- _wasmGroupOnlyFetches() {
1158
- if (this._wasmGroupOnlyFetchesMap === undefined) {
1159
- const groups = this._wasmRuntimeGroups();
1160
- /** @type {Map<string, boolean>} */
1161
- const only = new Map();
1162
- for (const chunk of this.compilation.chunks) {
1163
- if (!chunk.getEntryOptions()) continue;
1164
- const fetches = this._chunkWasmLoading(chunk) === "fetch";
1165
- forEachRuntime(chunk.runtime, (key) => {
1166
- const group = this._wasmGroupOf(groups, /** @type {string} */ (key));
1167
- only.set(group, only.get(group) !== false && fetches);
1168
- });
1169
- }
1170
- this._wasmGroupOnlyFetchesMap = only;
1171
- }
1172
- return this._wasmGroupOnlyFetchesMap;
1251
+ return this._wasmGroupsOf(runtime).some((group) => fetching.has(group));
1173
1252
  }
1174
1253
 
1175
1254
  /**
@@ -1182,30 +1261,31 @@ class RuntimeTemplate {
1182
1261
  _wasmFetchAnchorIsKnown(runtime) {
1183
1262
  if (this._wasmAnchorKnownByGroup === undefined) {
1184
1263
  const { chunkGraph, modules } = this.compilation;
1185
- const publicPath = /** @type {PublicPath} */ (
1186
- this.outputOptions.publicPath
1187
- );
1188
- const groups = this._wasmRuntimeGroups();
1264
+ const { groupOf, onlyFetching } = this._wasmGroups();
1189
1265
  /** @type {Map<string, boolean>} */
1190
1266
  const known = new Map();
1191
1267
  for (const module of modules) {
1192
1268
  if (module.type !== WEBASSEMBLY_MODULE_TYPE_ASYNC) continue;
1269
+ // Probed through the very builder the generator runs, so the gate is
1270
+ // exact: a prefix it can spell — the per-asset deferrals included — is
1271
+ // one the group may bake. The probe name stands in for the binary's.
1193
1272
  const spellable =
1194
- this._modulePlacement(module, chunkGraph).loaded !== null &&
1195
- this._resolvePublicPathPrefix(publicPath, module) !== null;
1273
+ this._getAnalyzableFileSpecifier(
1274
+ module,
1275
+ /** @type {ChunkGraph} */ (chunkGraph),
1276
+ [["literal", "x"]],
1277
+ true
1278
+ ) !== null;
1196
1279
  for (const moduleRuntime of chunkGraph.getModuleRuntimes(module)) {
1197
1280
  forEachRuntime(moduleRuntime, (key) => {
1198
- const group = this._wasmGroupOf(
1199
- groups,
1200
- /** @type {string} */ (key)
1201
- );
1281
+ const group = groupOf(/** @type {string} */ (key));
1202
1282
  known.set(group, known.get(group) !== false && spellable);
1203
1283
  });
1204
1284
  }
1205
1285
  }
1206
1286
  // A group one entry reads with `readFile` cannot take a prefix meant for the
1207
1287
  // document, and only an entry names its loader.
1208
- for (const [group, only] of this._wasmGroupOnlyFetches()) {
1288
+ for (const [group, only] of onlyFetching) {
1209
1289
  if (!only) known.set(group, false);
1210
1290
  }
1211
1291
  this._wasmAnchorKnownByGroup = known;
@@ -1217,124 +1297,9 @@ class RuntimeTemplate {
1217
1297
  for (const value of known.values()) if (!value) return false;
1218
1298
  return known.size > 0;
1219
1299
  }
1220
- const groups = this._wasmRuntimeGroups();
1221
- if (typeof runtime === "string") {
1222
- return known.get(this._wasmGroupOf(groups, runtime)) === true;
1223
- }
1224
- for (const key of runtime) {
1225
- if (known.get(this._wasmGroupOf(groups, key)) !== true) return false;
1226
- }
1227
- return true;
1228
- }
1229
-
1230
- /**
1231
- * The runtime groups an entry of which asked for the `fetch` loader, or `undefined`
1232
- * where none did — the cheap answer for a compilation with no fetching entry at all,
1233
- * which is every non-web target. Only an entry names a loader; every other chunk of
1234
- * the runtime is served by the one its entry asked for.
1235
- * @returns {Set<string> | undefined} the groups that fetch
1236
- */
1237
- _wasmFetchingGroups() {
1238
- if (this._wasmFetchingGroupSet === undefined) {
1239
- /** @type {Set<string>} */
1240
- const fetching = new Set();
1241
- /** @type {Map<string, string> | undefined} */
1242
- let groups;
1243
- for (const chunk of this.compilation.chunks) {
1244
- // Only an entry names a loader; every other chunk of the runtime is served
1245
- // by the one its entry asked for, so it says nothing on its own.
1246
- if (!chunk.getEntryOptions()) continue;
1247
- if (this._chunkWasmLoading(chunk) !== "fetch") continue;
1248
- // Built only once something fetches, so nothing pays for the grouping.
1249
- if (groups === undefined) groups = this._wasmRuntimeGroups();
1250
- const named = /** @type {Map<string, string>} */ (groups);
1251
- forEachRuntime(chunk.runtime, (key) => {
1252
- fetching.add(this._wasmGroupOf(named, /** @type {string} */ (key)));
1253
- });
1254
- }
1255
- this._wasmFetchingGroupSet = fetching.size === 0 ? false : fetching;
1256
- }
1257
- return this._wasmFetchingGroupSet === false
1258
- ? undefined
1259
- : this._wasmFetchingGroupSet;
1260
- }
1261
-
1262
- /**
1263
- * Runtime keys mapped to the group they answer with, named by one key of it. Code
1264
- * generation runs once for runtimes a module hashes alike in, and nothing in a
1265
- * binary's hash knows which loader will read it — so a binary two runtimes reach
1266
- * carries one shape into both, and a third runtime sharing another binary with
1267
- * either is pulled in after them. A key in no group answers as itself. Asked of the
1268
- * same modules the loaders are created for, and only once per compilation.
1269
- * @returns {Map<string, string>} runtime key to the group it answers with
1270
- */
1271
- _wasmRuntimeGroups() {
1272
- if (this._wasmRuntimeGroupsByKey === undefined) {
1273
- const { chunkGraph } = this.compilation;
1274
- /** @type {Map<string, string>} */
1275
- const parent = new Map();
1276
- /**
1277
- * @param {string} key runtime key
1278
- * @returns {string} the key naming its group
1279
- */
1280
- const find = (key) => {
1281
- let root = key;
1282
- let seen = parent.get(root);
1283
- while (seen !== undefined && seen !== root) {
1284
- root = seen;
1285
- seen = parent.get(root);
1286
- }
1287
- // Path compression keeps a long share chain from costing more than once.
1288
- let walk = key;
1289
- let next = parent.get(walk);
1290
- while (next !== undefined && next !== root) {
1291
- parent.set(walk, root);
1292
- walk = next;
1293
- next = parent.get(walk);
1294
- }
1295
- return root;
1296
- };
1297
- /** @type {string[]} */
1298
- const keys = [];
1299
- /**
1300
- * @param {RuntimeSpec} runtime a runtime the module is generated for
1301
- * @returns {void}
1302
- */
1303
- const collectKeys = (runtime) => {
1304
- forEachRuntime(runtime, (key) => {
1305
- keys.push(/** @type {string} */ (key));
1306
- });
1307
- };
1308
- for (const module of this.compilation.modules) {
1309
- if (module.type !== WEBASSEMBLY_MODULE_TYPE_ASYNC) continue;
1310
- keys.length = 0;
1311
- for (const runtime of chunkGraph.getModuleRuntimes(module)) {
1312
- collectKeys(runtime);
1313
- }
1314
- for (const key of keys) {
1315
- if (!parent.has(key)) parent.set(key, key);
1316
- }
1317
- // Every runtime this binary reaches answers with the first one.
1318
- for (let i = 1; i < keys.length; i++) {
1319
- parent.set(find(keys[i]), find(keys[0]));
1320
- }
1321
- }
1322
- // Flattened once, so a lookup is a single `get` rather than a chain walk.
1323
- for (const key of parent.keys()) parent.set(key, find(key));
1324
- this._wasmRuntimeGroupsByKey = parent;
1325
- }
1326
- return this._wasmRuntimeGroupsByKey;
1327
- }
1328
-
1329
- /**
1330
- * @param {Map<string, string>} groups runtime key to the group it answers with
1331
- * @param {string} key a runtime key
1332
- * @returns {string} the group it answers with, or itself when it shares nothing
1333
- */
1334
- _wasmGroupOf(groups, key) {
1335
- const group = groups.get(key);
1336
- // An entry named "" makes "" a runtime key, so test absence, not a falsy value.
1337
- return group === undefined ? key : group;
1300
+ return this._wasmGroupsOf(runtime).every(
1301
+ (group) => known.get(group) === true
1302
+ );
1338
1303
  }
1339
1304
 
1340
1305
  supportTemplateLiteral() {
@@ -2684,6 +2649,22 @@ class RuntimeTemplate {
2684
2649
  return `Promise.resolve(${comment.trim()})`;
2685
2650
  }
2686
2651
 
2652
+ /**
2653
+ * Whether any module of `chunk` carries a source type with a chunk handler of its
2654
+ * own, which `.ei` dispatches alongside the javascript one.
2655
+ * @param {Chunk} chunk the chunk being imported
2656
+ * @param {ChunkGraph} chunkGraph the chunk graph
2657
+ * @returns {boolean} true when a handler beyond the javascript one is reached
2658
+ */
2659
+ _chunkLoadsBeyondJs(chunk, chunkGraph) {
2660
+ for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
2661
+ for (const type of chunkGraph.getModuleSourceTypes(module)) {
2662
+ if (!TYPES_WITHOUT_CHUNK_HANDLER.has(type)) return true;
2663
+ }
2664
+ }
2665
+ return false;
2666
+ }
2667
+
2687
2668
  /**
2688
2669
  * For ESM module output, load a single statically-named chunk through the
2689
2670
  * `analyzableChunkImport` helper — a literal `import("./chunk.js")` other bundlers
@@ -2711,30 +2692,15 @@ class RuntimeTemplate {
2711
2692
  ) {
2712
2693
  return null;
2713
2694
  }
2714
- // Prefetch/preload children are injected by the runtime `.f` handlers, which `.ei`
2715
- // runs too — but they attach to `.f`, so it has to exist.
2716
- const needsHintHandlers =
2717
- chunk.hasChildByOrder(chunkGraph, "prefetch", true) ||
2718
- chunk.hasChildByOrder(chunkGraph, "preload", true) ||
2719
- chunk.hasChildByOrder(chunkGraph, "cssPreload", true);
2720
2695
  // `.ei` always performs the import, where `.e` only reaches the javascript
2721
2696
  // loader when there is javascript to load. A module federation remote is the
2722
2697
  // case that matters: its chunk is emitted by the container's build, not this one.
2723
2698
  if (!JavascriptModulesPlugin.chunkHasJs(chunk, chunkGraph)) {
2724
- this._analyzableBailout(
2699
+ return this._analyzableBailout(
2725
2700
  originModule,
2726
- "this compilation emits no javascript for the chunk, so there is nothing to import"
2701
+ "this compilation emits no javascript for the chunk, so there is nothing to import",
2702
+ null
2727
2703
  );
2728
- return null;
2729
- }
2730
- let hasNonJsTypes = false;
2731
- for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
2732
- for (const type of chunkGraph.getModuleSourceTypes(module)) {
2733
- if (!TYPES_WITHOUT_CHUNK_HANDLER.has(type)) {
2734
- // Loaded by that type's own `.f` handler, which `.ei` still dispatches.
2735
- hasNonJsTypes = true;
2736
- }
2737
- }
2738
2704
  }
2739
2705
  // Relative to the consuming chunk (`import.meta.url`) for `auto`, else an
2740
2706
  // absolute publicPath prefix.
@@ -2756,9 +2722,15 @@ class RuntimeTemplate {
2756
2722
  ? specifier
2757
2723
  : `"./${specifier.slice(1)}`;
2758
2724
  runtimeRequirements.add(RuntimeGlobals.analyzableChunkImport);
2759
- if (needsHintHandlers || hasNonJsTypes) {
2760
- // Those handlers attach to the `.f` map, which has to exist — but the runtime
2761
- // `ensureChunk` around it does not, since `.ei` dispatches them itself.
2725
+ // Prefetch/preload children and every non-javascript source type are loaded by a
2726
+ // `.f` handler, which `.ei` dispatches — but they attach to `.f`, so it has to
2727
+ // exist. The runtime `ensureChunk` around it does not.
2728
+ if (
2729
+ chunk.hasChildByOrder(chunkGraph, "prefetch", true) ||
2730
+ chunk.hasChildByOrder(chunkGraph, "preload", true) ||
2731
+ chunk.hasChildByOrder(chunkGraph, "cssPreload", true) ||
2732
+ this._chunkLoadsBeyondJs(chunk, chunkGraph)
2733
+ ) {
2762
2734
  runtimeRequirements.add(RuntimeGlobals.ensureChunkHandlers);
2763
2735
  }
2764
2736
  // Drop-in for `ensureChunk(id)`: the literal `import()` can be statically
@@ -2928,6 +2900,11 @@ class RuntimeTemplate {
2928
2900
  /** @type {Set<string>} */
2929
2901
  const names = new Set();
2930
2902
  let found = false;
2903
+ // Its own copy, since `lastIndex` is walked below; reset by the loop that ends it.
2904
+ const regexp = new RegExp(
2905
+ ANALYZABLE_TOKEN_REGEXP.source,
2906
+ ANALYZABLE_TOKEN_REGEXP.flags
2907
+ );
2931
2908
  for (const module of modules) {
2932
2909
  if (!results.has(module, chunk.runtime)) continue;
2933
2910
  const source = results.getSource(module, chunk.runtime, JAVASCRIPT_TYPE);
@@ -2937,10 +2914,6 @@ class RuntimeTemplate {
2937
2914
  carriesFullHash = true;
2938
2915
  found = true;
2939
2916
  }
2940
- const regexp = new RegExp(
2941
- ANALYZABLE_TOKEN_REGEXP.source,
2942
- ANALYZABLE_TOKEN_REGEXP.flags
2943
- );
2944
2917
  /** @type {RegExpExecArray | null} */
2945
2918
  let match;
2946
2919
  while ((match = regexp.exec(content)) !== null) {
@@ -2948,11 +2921,7 @@ class RuntimeTemplate {
2948
2921
  if (parts === null) continue;
2949
2922
  found = true;
2950
2923
  for (const [kind, value] of parts) {
2951
- if (
2952
- kind === "publicPath" ||
2953
- kind === "template" ||
2954
- kind === "unserved"
2955
- ) {
2924
+ if (FULL_HASH_PART_KINDS.has(kind)) {
2956
2925
  carriesFullHash = true;
2957
2926
  } else if (CHUNK_SPECIFIER_PART_KINDS.has(kind)) {
2958
2927
  names.add(String(value));
@@ -3213,13 +3182,13 @@ class RuntimeTemplate {
3213
3182
  return template.replace(HASH_IN_FILENAME_GLOBAL, "x");
3214
3183
  }
3215
3184
  try {
3216
- return template({
3217
- chunk: createHashProbeChunk(chunk, HASH_PROBE, true),
3218
- runtime: chunk.runtime,
3219
- contentHashType: JAVASCRIPT_TYPE,
3220
- hash: HASH_PROBE,
3221
- contentHash: HASH_PROBE
3222
- }).replace(HASH_IN_FILENAME_GLOBAL, "x");
3185
+ return probeTemplateName(
3186
+ template,
3187
+ chunk,
3188
+ JAVASCRIPT_TYPE,
3189
+ HASH_PROBE,
3190
+ true
3191
+ ).replace(HASH_IN_FILENAME_GLOBAL, "x");
3223
3192
  } catch (_error) {
3224
3193
  // Nothing to report: the naming call is given strictly less than this probe, so
3225
3194
  // one that throws here fails the build where the name is actually needed.
@@ -3311,6 +3280,41 @@ class RuntimeTemplate {
3311
3280
  : { undo, loaded };
3312
3281
  }
3313
3282
 
3283
+ /**
3284
+ * Static literal specifier (already quoted) for the `new URL(<here>, import.meta.url)`
3285
+ * a worker or worklet entry chunk bakes to, or `null` to keep the runtime form. The
3286
+ * gate and the build are asked together so every call site agrees on both — the
3287
+ * `new Worker(...)` emit, and the resource hint `ResourceHintPlugin` spells for it.
3288
+ * @param {string | undefined} overridePublicPath a `publicPath` set on the worker itself
3289
+ * @param {Chunk} chunk the worker's entry chunk
3290
+ * @param {Module} module the module the reference is emitted into
3291
+ * @param {ChunkGraph} chunkGraph the chunk graph
3292
+ * @param {RuntimeRequirements} runtimeRequirements what the consuming chunk needs
3293
+ * @returns {string | null} a quoted literal, or `null` to fall back
3294
+ */
3295
+ getAnalyzableWorkerUrl(
3296
+ overridePublicPath,
3297
+ chunk,
3298
+ module,
3299
+ chunkGraph,
3300
+ runtimeRequirements
3301
+ ) {
3302
+ // Without an id nothing can name the chunk in a stand-in.
3303
+ if (
3304
+ chunk.id === null ||
3305
+ !this.supportsAnalyzable("url", chunkGraph, module)
3306
+ ) {
3307
+ return null;
3308
+ }
3309
+ return this._getAnalyzableChunkSpecifier(
3310
+ overridePublicPath,
3311
+ chunk,
3312
+ module,
3313
+ chunkGraph,
3314
+ runtimeRequirements
3315
+ );
3316
+ }
3317
+
3314
3318
  /**
3315
3319
  * Static literal specifier (already quoted) for a `new URL(<here>, import.meta.url)`
3316
3320
  * or `import(<here>)` pointing at `chunk`'s JS file, or `null` when it can't be known
@@ -3358,33 +3362,30 @@ class RuntimeTemplate {
3358
3362
  const chunks =
3359
3363
  consumingChunks ||
3360
3364
  this._moduleChunks(/** @type {Module} */ (consumingModule), chunkGraph);
3361
- // Baking makes the consuming chunk's content depend on this one's hash. A pair that
3362
- // reaches back has no fixed point if both bake, so only the lower id's name does.
3363
- const cycles =
3364
- deferred &&
3365
- this._reachesAny(chunk, chunks) &&
3366
- !this._namesBeforeAll(chunk, chunks);
3365
+ const namesFirst = this._namesBeforeAll(chunk, chunks);
3367
3366
  // An id names the chunk in the stand-in. No reference reaches here without one —
3368
3367
  // `blockPromise` drops an id-less chunk before asking, and one the whole build
3369
3368
  // keeps has no name to be emitted under — so the guard carries no reason of its
3370
- // own; it only keeps `null` out of a name.
3369
+ // own; it only keeps `null` out of a name. A pair naming each other is fine here:
3370
+ // `RealContentHashPlugin`, which deferring requires, re-hashes the pair as one
3371
+ // group, so neither hash chases the other.
3371
3372
  const canReserve =
3372
- chunk.id !== null && !cycles && this._canDeferAnalyzableName(chunks);
3373
+ chunk.id !== null && this._canDeferAnalyzableName(chunks);
3373
3374
  // A content-named consumer bakes anyway when this chunk settles first: the fold
3374
- // puts the name into its hash. Only what `_foldsWholeName` covers, though.
3375
- const canFold = !canReserve && this._namesBeforeAll(chunk, chunks);
3375
+ // puts the name into its hash. Only what `_foldsWholeName` covers, though — and
3376
+ // a pair that reaches back has no fold order, so only the one hashing first does.
3377
+ const canFold = !canReserve && namesFirst;
3376
3378
  /**
3377
3379
  * @returns {null} always, having recorded why no stand-in may be reserved
3378
3380
  */
3379
- const cannotReserve = () => {
3381
+ const cannotReserve = () =>
3380
3382
  this._analyzableBailout(
3381
3383
  consumingModule,
3382
- cycles
3383
- ? "this chunk and the one it references name each other, so neither hash could settle"
3384
- : DEFER_BAILOUT
3384
+ !namesFirst && this._reachesAny(chunk, chunks)
3385
+ ? "this chunk and the one it references name each other, which only optimization.realContentHash could settle"
3386
+ : DEFER_BAILOUT,
3387
+ null
3385
3388
  );
3386
- return null;
3387
- };
3388
3389
  if (deferred && !canReserve && !canFold) return cannotReserve();
3389
3390
  const filename = deferred
3390
3391
  ? ""
@@ -3483,24 +3484,17 @@ class RuntimeTemplate {
3483
3484
  // Each knows its own answer, so the fill asks per asset — where each has one,
3484
3485
  // and where nothing else goes between the two shapes.
3485
3486
  if (relativeBase !== undefined || !this._eachChunkServedOneWay(chunks)) {
3486
- this._analyzableBailout(module, SERVED_BAILOUT);
3487
- return null;
3487
+ return this._analyzableBailout(module, SERVED_BAILOUT, null);
3488
3488
  }
3489
3489
  return [...head, ["unserved", ""]];
3490
3490
  }
3491
3491
  if (climb !== "" && relativeBase === undefined) return head;
3492
3492
  const resolved = resolve();
3493
3493
  if (resolved === null) return null;
3494
- return [
3495
- ...head,
3496
- .../** @type {SpecifierPart[]} */ (
3497
- climb === "" ? [] : [["literal", climb]]
3498
- ),
3499
- .../** @type {SpecifierPart[]} */ (
3500
- relativeBase === undefined ? [] : [["literal", relativeBase]]
3501
- ),
3502
- ...rootedParts(resolved)
3503
- ];
3494
+ if (climb !== "") head.push(["literal", climb]);
3495
+ if (relativeBase !== undefined) head.push(["literal", relativeBase]);
3496
+ head.push(...rootedParts(resolved));
3497
+ return head;
3504
3498
  }
3505
3499
 
3506
3500
  /**
@@ -3626,11 +3620,6 @@ class RuntimeTemplate {
3626
3620
  ) {
3627
3621
  const { publicPath } = this.outputOptions;
3628
3622
  const chunks = this._moduleChunks(module, chunkGraph);
3629
- /**
3630
- * @param {SpecifierPart[]} parts the whole specifier
3631
- * @returns {string | null} it already quoted, or `null` when no stand-in may be reserved
3632
- */
3633
- const specifier = (parts) => this._specifierOf(parts, chunks, module);
3634
3623
  if (bakePublicPath && publicPath !== "auto") {
3635
3624
  const prefix = this._analyzablePathPrefix(
3636
3625
  module,
@@ -3638,25 +3627,24 @@ class RuntimeTemplate {
3638
3627
  chunks,
3639
3628
  relativeBase
3640
3629
  );
3641
- return prefix === null ? null : specifier([...prefix, ...file]);
3630
+ if (prefix === null) return null;
3631
+ return this._specifierOf([...prefix, ...file], chunks, module);
3642
3632
  }
3643
3633
  // No public path to place it against, so the `../` path back to the output root
3644
3634
  // is the whole prefix. Different depths — no one is right for every asset the
3645
3635
  // reference is emitted into, so the deferred pass builds each asset's own.
3646
3636
  const { undo } = this._modulePlacement(module, chunkGraph);
3647
- return specifier([
3648
- undo === null ? ["undo", ""] : ["literal", undo],
3649
- ...(relativeBase === undefined
3650
- ? []
3651
- : /** @type {SpecifierPart[]} */ ([["literal", relativeBase]])),
3652
- ...file
3653
- ]);
3637
+ /** @type {SpecifierPart[]} */
3638
+ const parts = [undo === null ? ["undo", ""] : ["literal", undo]];
3639
+ if (relativeBase !== undefined) parts.push(["literal", relativeBase]);
3640
+ parts.push(...file);
3641
+ return this._specifierOf(parts, chunks, module);
3654
3642
  }
3655
3643
 
3656
3644
  /**
3657
3645
  * Static `new URL(<file>, import.meta.url)` for every stylesheet a runtime can load,
3658
- * keyed by chunk id, or `null` when any of them has to keep the runtime
3659
- * `publicPath + getChunkCssFilename(id)` form. Both the runtime module reading them
3646
+ * keyed by chunk id — one that cannot be named leaves the map incomplete, and its
3647
+ * id keeps the runtime `publicPath + getChunkCssFilename(id)` form. Both the runtime module reading them
3660
3648
  * and the plugin declaring what it needs ask this, so the two always agree. The
3661
3649
  * runtime hands an absolute url string to `link.href`, and so does this — the browser
3662
3650
  * resolves the element against the document, which is not where the chunk sits, and
@@ -3666,7 +3654,7 @@ class RuntimeTemplate {
3666
3654
  * @param {ChunkGraph} chunkGraph the chunk graph
3667
3655
  * @param {ReadOnlyRuntimeRequirements} runtimeRequirements what that chunk needs
3668
3656
  * @param {Module=} consumingModule the runtime module, where one exists to report against
3669
- * @returns {Map<ChunkId, string> | null} the urls, or `null` to keep the runtime form
3657
+ * @returns {AnalyzableChunkUrls | null} the urls, or `null` to keep the runtime form
3670
3658
  */
3671
3659
  analyzableCssChunkUrls(
3672
3660
  runtimeChunk,
@@ -3698,14 +3686,14 @@ class RuntimeTemplate {
3698
3686
  /**
3699
3687
  * Static `new URL(<file>, import.meta.url).href` for every javascript chunk a
3700
3688
  * runtime can hint at with `<link rel="prefetch"/"modulepreload">`, keyed by chunk
3701
- * id, or `null` when any of them has to keep the runtime
3702
- * `publicPath + getChunkScriptFilename(id)` form. Both the runtime module reading
3689
+ * id — one that cannot be named leaves the map incomplete, and its id keeps the
3690
+ * runtime `publicPath + getChunkScriptFilename(id)` form. Both the runtime module reading
3703
3691
  * them and the plugin declaring what it needs ask this, so the two always agree.
3704
3692
  * @param {Chunk} runtimeChunk the chunk holding the hint handlers
3705
3693
  * @param {ChunkGraph} chunkGraph the chunk graph
3706
3694
  * @param {ReadOnlyRuntimeRequirements} runtimeRequirements what that chunk needs
3707
3695
  * @param {Module=} consumingModule the runtime module, where one exists to report against
3708
- * @returns {Map<ChunkId, string> | null} the urls, or `null` to keep the runtime form
3696
+ * @returns {AnalyzableChunkUrls | null} the urls, or `null` to keep the runtime form
3709
3697
  */
3710
3698
  analyzableChunkScriptUrls(
3711
3699
  runtimeChunk,
@@ -3722,11 +3710,9 @@ class RuntimeTemplate {
3722
3710
  );
3723
3711
  /** @type {Set<ChunkId>} */
3724
3712
  const hinted = new Set();
3725
- for (const order of Object.keys(orders)) {
3726
- for (const from of Object.keys(orders[order])) {
3727
- for (const id of orders[order][/** @type {ChunkId} */ (from)]) {
3728
- hinted.add(id);
3729
- }
3713
+ for (const byParent of Object.values(orders)) {
3714
+ for (const ids of Object.values(byParent)) {
3715
+ for (const id of ids) hinted.add(id);
3730
3716
  }
3731
3717
  }
3732
3718
  /** @type {Chunk[]} */
@@ -3746,14 +3732,15 @@ class RuntimeTemplate {
3746
3732
 
3747
3733
  /**
3748
3734
  * The urls of one asset of each of `chunks`, written out so they can be read by
3749
- * chunk id, or `null` as soon as one of them cannot be named here.
3735
+ * chunk id. A chunk that cannot be named here leaves the map incomplete rather
3736
+ * than losing it — the consumer keeps the runtime form for the ids the map lacks.
3750
3737
  * @param {Iterable<Chunk>} chunks the chunks whose asset is wanted
3751
3738
  * @param {ChunkGraph} chunkGraph the chunk graph
3752
3739
  * @param {Module | undefined} consumingModule the runtime module, where one exists to report against
3753
3740
  * @param {Chunk[]} consumingChunks the chunks the urls are written into
3754
3741
  * @param {string} sourceType which asset of each chunk to name
3755
3742
  * @param {ReadOnlyRuntimeRequirements} runtimeRequirements what the runtime chunk needs
3756
- * @returns {Map<ChunkId, string> | null} the urls, or `null` to keep the runtime form
3743
+ * @returns {AnalyzableChunkUrls | null} the urls, or `null` to keep the runtime form
3757
3744
  */
3758
3745
  _analyzableChunkUrls(
3759
3746
  chunks,
@@ -3773,6 +3760,7 @@ class RuntimeTemplate {
3773
3760
  }
3774
3761
  /** @type {Map<ChunkId, string>} */
3775
3762
  const urls = new Map();
3763
+ let complete = true;
3776
3764
  for (const chunk of chunks) {
3777
3765
  if (chunk.id === null) continue;
3778
3766
  const specifier = this._getAnalyzableChunkSpecifier(
@@ -3784,10 +3772,14 @@ class RuntimeTemplate {
3784
3772
  consumingChunks,
3785
3773
  sourceType
3786
3774
  );
3787
- if (specifier === null) return null;
3775
+ // `_getAnalyzableChunkSpecifier` has already recorded why.
3776
+ if (specifier === null) {
3777
+ complete = false;
3778
+ continue;
3779
+ }
3788
3780
  urls.set(chunk.id, `${this.importMetaUrl(specifier)}.href`);
3789
3781
  }
3790
- return urls.size > 0 ? urls : null;
3782
+ return urls.size > 0 ? { urls, complete } : null;
3791
3783
  }
3792
3784
 
3793
3785
  /**
@@ -3852,20 +3844,20 @@ class RuntimeTemplate {
3852
3844
  try {
3853
3845
  // Everything the naming call is given except the hashes, so only a hash can
3854
3846
  // make the two answers differ.
3855
- const template = filenameTemplate({
3856
- chunk: createHashProbeChunk(chunk, HASH_PROBE, true),
3857
- runtime: chunk.runtime,
3847
+ const template = probeTemplateName(
3848
+ filenameTemplate,
3849
+ chunk,
3858
3850
  contentHashType,
3859
- hash: HASH_PROBE,
3860
- contentHash: HASH_PROBE
3861
- });
3862
- const probe = filenameTemplate({
3863
- chunk: createHashProbeChunk(chunk, HASH_PROBE_ALTERNATE, false),
3864
- runtime: chunk.runtime,
3851
+ HASH_PROBE,
3852
+ true
3853
+ );
3854
+ const probe = probeTemplateName(
3855
+ filenameTemplate,
3856
+ chunk,
3865
3857
  contentHashType,
3866
- hash: HASH_PROBE_ALTERNATE,
3867
- contentHash: HASH_PROBE_ALTERNATE
3868
- });
3858
+ HASH_PROBE_ALTERNATE,
3859
+ false
3860
+ );
3869
3861
  return template === probe ? template : undefined;
3870
3862
  } catch (_error) {
3871
3863
  // Nothing to report: the probe is given strictly more than the naming call,
@@ -3882,21 +3874,16 @@ class RuntimeTemplate {
3882
3874
  * @returns {boolean} true when every runtime reaching it fetches
3883
3875
  */
3884
3876
  _wasmModuleFetches(module, chunkGraph) {
3885
- /** @type {string[]} */
3886
- const keys = [];
3877
+ const { onlyFetching } = this._wasmGroups();
3878
+ let placed = false;
3887
3879
  for (const moduleRuntime of chunkGraph.getModuleRuntimes(module)) {
3888
- forEachRuntime(moduleRuntime, (key) => {
3889
- keys.push(/** @type {string} */ (key));
3890
- });
3880
+ for (const group of this._wasmGroupsOf(moduleRuntime)) {
3881
+ placed = true;
3882
+ if (!onlyFetching.get(group)) return false;
3883
+ }
3891
3884
  }
3892
3885
  // In no runtime at all nothing named a loader, so `output` answers.
3893
- if (keys.length === 0) return this.outputOptions.wasmLoading === "fetch";
3894
- const only = this._wasmGroupOnlyFetches();
3895
- const groups = this._wasmRuntimeGroups();
3896
- for (const key of keys) {
3897
- if (!only.get(this._wasmGroupOf(groups, key))) return false;
3898
- }
3899
- return true;
3886
+ return placed || this.outputOptions.wasmLoading === "fetch";
3900
3887
  }
3901
3888
 
3902
3889
  /**
@@ -4073,6 +4060,18 @@ class RuntimeTemplate {
4073
4060
  let chunksById;
4074
4061
  /** @type {Map<string, Chunk> | undefined} */
4075
4062
  let chunkByAsset;
4063
+ /** @type {string | undefined} */
4064
+ let publicPathText;
4065
+ // One value for the whole pass, however many stand-ins read it.
4066
+ const resolvedPublicPath = () => {
4067
+ if (publicPathText === undefined) {
4068
+ publicPathText = compilation.getPath(
4069
+ outputOptions.publicPath || "",
4070
+ {}
4071
+ );
4072
+ }
4073
+ return publicPathText;
4074
+ };
4076
4075
  /**
4077
4076
  * @param {string} name an emitted asset's name
4078
4077
  * @returns {Chunk | undefined} the chunk it was emitted for
@@ -4127,10 +4126,7 @@ class RuntimeTemplate {
4127
4126
  {}
4128
4127
  );
4129
4128
  } else if (kind === "publicPath") {
4130
- specifier += compilation.getPath(
4131
- outputOptions.publicPath || "",
4132
- {}
4133
- );
4129
+ specifier += resolvedPublicPath();
4134
4130
  } else if (kind === "unserved") {
4135
4131
  const holder = chunkOf(assetName);
4136
4132
  if (holder === undefined) return null;
@@ -4141,10 +4137,7 @@ class RuntimeTemplate {
4141
4137
  compilation.runtimeTemplate._chunkPlacement(holder).loaded ===
4142
4138
  false
4143
4139
  ) {
4144
- const path = compilation.getPath(
4145
- outputOptions.publicPath || "",
4146
- {}
4147
- );
4140
+ const path = resolvedPublicPath();
4148
4141
  // The `../` already reached the root this is read from.
4149
4142
  specifier += path.startsWith("./") ? path.slice(2) : path;
4150
4143
  }