webpack 5.108.4 → 5.109.0

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 (186) hide show
  1. package/lib/APIPlugin.js +48 -29
  2. package/lib/CacheFacade.js +8 -0
  3. package/lib/ChunkGroup.js +1 -0
  4. package/lib/CompatibilityPlugin.js +31 -29
  5. package/lib/Compilation.js +49 -4
  6. package/lib/ConstPlugin.js +11 -11
  7. package/lib/ContextModule.js +239 -6
  8. package/lib/ContextModuleFactory.js +139 -14
  9. package/lib/DefinePlugin.js +396 -101
  10. package/lib/Entrypoint.js +1 -1
  11. package/lib/ExportsInfoApiPlugin.js +2 -2
  12. package/lib/ExternalModule.js +107 -5
  13. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  14. package/lib/FileSystemInfo.js +130 -74
  15. package/lib/HotModuleReplacementPlugin.js +18 -15
  16. package/lib/Module.js +3 -0
  17. package/lib/ModuleFilenameHelpers.js +2 -2
  18. package/lib/ModuleTypeConstants.js +8 -1
  19. package/lib/MultiCompiler.js +13 -2
  20. package/lib/MultiWatching.js +1 -0
  21. package/lib/NodeStuffPlugin.js +51 -33
  22. package/lib/NormalModule.js +169 -401
  23. package/lib/NormalModuleFactory.js +76 -2
  24. package/lib/ProgressPlugin.js +211 -119
  25. package/lib/ProvidePlugin.js +2 -2
  26. package/lib/RuntimeGlobals.js +47 -0
  27. package/lib/RuntimePlugin.js +33 -0
  28. package/lib/RuntimeTemplate.js +304 -16
  29. package/lib/UseStrictPlugin.js +1 -1
  30. package/lib/WebpackIsIncludedPlugin.js +1 -1
  31. package/lib/WebpackOptionsApply.js +24 -19
  32. package/lib/asset/AssetGenerator.js +110 -28
  33. package/lib/asset/AssetModulesPlugin.js +92 -2
  34. package/lib/asset/WebManifestGenerator.js +164 -0
  35. package/lib/asset/WebManifestParser.js +130 -0
  36. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  37. package/lib/async-modules/isGeneratorLowered.js +26 -0
  38. package/lib/cache/PackFileCacheStrategy.js +9 -3
  39. package/lib/config/browserslistTargetHandler.js +40 -0
  40. package/lib/config/defaults.js +219 -29
  41. package/lib/config/normalization.js +26 -0
  42. package/lib/config/target.js +9 -1
  43. package/lib/css/CssGenerator.js +40 -31
  44. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  45. package/lib/css/CssModulesPlugin.js +13 -0
  46. package/lib/css/CssParser.js +594 -263
  47. package/lib/css/syntax.js +875 -462
  48. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  49. package/lib/dependencies/AMDPlugin.js +2 -2
  50. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  51. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  52. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  53. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  54. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  55. package/lib/dependencies/CommonJsPlugin.js +2 -2
  56. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  57. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  58. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  59. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  60. package/lib/dependencies/CssIcssExportDependency.js +162 -71
  61. package/lib/dependencies/CssUrlDependency.js +35 -4
  62. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  63. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  64. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  65. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  66. package/lib/dependencies/HarmonyImportDependency.js +12 -1
  67. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
  68. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  69. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  70. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  71. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  72. package/lib/dependencies/ImportDependency.js +43 -1
  73. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  74. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  75. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  76. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  77. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  78. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  79. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  80. package/lib/dependencies/ImportParserPlugin.js +80 -17
  81. package/lib/dependencies/ImportPhase.js +10 -8
  82. package/lib/dependencies/LoaderPlugin.js +5 -1
  83. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  84. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  85. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  86. package/lib/dependencies/SystemPlugin.js +2 -4
  87. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  88. package/lib/dependencies/URLDependency.js +141 -21
  89. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  90. package/lib/dependencies/WorkerDependency.js +86 -18
  91. package/lib/dependencies/WorkletDependency.js +278 -0
  92. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  93. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  94. package/lib/errors/LoaderLoadingError.js +20 -0
  95. package/lib/errors/ModuleParseError.js +39 -22
  96. package/lib/errors/NodeStuffInWebError.js +1 -1
  97. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  98. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  99. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  100. package/lib/hmr/lazyCompilationBackend.js +32 -8
  101. package/lib/html/HtmlGenerator.js +887 -15
  102. package/lib/html/HtmlModule.js +2 -2
  103. package/lib/html/HtmlModulesPlugin.js +610 -166
  104. package/lib/html/HtmlParser.js +603 -247
  105. package/lib/html/favicon.svg +1 -0
  106. package/lib/html/syntax.js +1264 -661
  107. package/lib/ids/IdHelpers.js +1 -1
  108. package/lib/index.js +6 -0
  109. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  110. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  111. package/lib/javascript/JavascriptGenerator.js +197 -49
  112. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  113. package/lib/javascript/JavascriptParser.js +701 -355
  114. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  115. package/lib/javascript/syntax.js +4380 -0
  116. package/lib/library/ModuleLibraryPlugin.js +38 -1
  117. package/lib/loaders/LoaderRunner.js +714 -0
  118. package/lib/loaders/loadLoader.js +100 -0
  119. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
  120. package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
  121. package/lib/optimize/ConcatenatedModule.js +120 -16
  122. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  123. package/lib/optimize/InnerGraphPlugin.js +50 -30
  124. package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
  125. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  126. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  127. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  128. package/lib/prefetch/ResourceHintPlugin.js +663 -0
  129. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  130. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  131. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  132. package/lib/rules/RuleSetCompiler.js +114 -0
  133. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  134. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  135. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  136. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  137. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  138. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  139. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  140. package/lib/schemes/HttpUriPlugin.js +40 -1
  141. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  142. package/lib/serialization/FileMiddleware.js +29 -3
  143. package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
  144. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  145. package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
  146. package/lib/stats/StatsFactory.js +15 -8
  147. package/lib/url/URLParserPlugin.js +66 -13
  148. package/lib/util/AsyncQueue.js +122 -72
  149. package/lib/util/SetHelpers.js +1 -1
  150. package/lib/util/StackedMap.js +122 -48
  151. package/lib/util/binarySearchBounds.js +2 -2
  152. package/lib/util/concatenate.js +40 -14
  153. package/lib/util/createHooksRegistry.js +13 -5
  154. package/lib/util/dataURL.js +2 -2
  155. package/lib/util/deterministicGrouping.js +7 -5
  156. package/lib/util/formatSize.js +6 -2
  157. package/lib/util/globUtils.js +710 -0
  158. package/lib/util/identifier.js +23 -6
  159. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  160. package/lib/util/internalSerializables.js +88 -71
  161. package/lib/util/magicComment.js +121 -25
  162. package/lib/util/numberHash.js +2 -2
  163. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  164. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  165. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  166. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  167. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  168. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  169. package/lib/webpack.js +65 -0
  170. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  171. package/module.d.ts +21 -0
  172. package/package.json +13 -12
  173. package/schemas/WebpackOptions.check.js +1 -1
  174. package/schemas/WebpackOptions.json +759 -36
  175. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  176. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  177. package/schemas/plugins/ProgressPlugin.json +17 -1
  178. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  179. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  180. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  181. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  182. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  183. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  184. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  185. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  186. package/types.d.ts +1213 -96
@@ -10,6 +10,8 @@ const {
10
10
  JAVASCRIPT_TYPE
11
11
  } = require("../ModuleSourceTypeConstants");
12
12
  const HtmlGenerator = require("../html/HtmlGenerator");
13
+ const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin");
14
+ const ResourceHintRuntimeModule = require("../prefetch/ResourceHintRuntimeModule");
13
15
  const makeSerializable = require("../util/makeSerializable");
14
16
  const ModuleDependency = require("./ModuleDependency");
15
17
 
@@ -20,10 +22,10 @@ const ModuleDependency = require("./ModuleDependency");
20
22
  /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
21
23
  /** @typedef {import("../Entrypoint")} Entrypoint */
22
24
  /** @typedef {import("../javascript/JavascriptParser").Range} Range */
23
- /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet"} HtmlEntryElementKind */
25
+ /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} HtmlEntryElementKind */
24
26
 
25
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null)]>} ObjectDeserializerContext */
26
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null)]>} ObjectSerializerContext */
27
+ /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null), boolean, number, number]>} ObjectDeserializerContext */
28
+ /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[string, string, HtmlEntryElementKind, number, number, boolean, string, number, boolean, number, (Range | null), (Range | null), boolean, number, number]>} ObjectSerializerContext */
27
29
 
28
30
  class HtmlEntryDependency extends ModuleDependency {
29
31
  /**
@@ -39,9 +41,12 @@ class HtmlEntryDependency extends ModuleDependency {
39
41
  * @param {string=} copyableAttrsText the originating tag's `nonce`/`crossorigin`/`referrerpolicy` attribute source spans (leading-space-prefixed, in that fixed order), captured at parse time so synthesized sibling `<link>`/`<script>` tags carry the same CSP/fetch policy without the template re-parsing the tag text; empty when none are present
40
42
  * @param {number=} tagNameEnd position right after the originating tag's name (e.g. after `<script`), captured at parse time so the template can insert a `crossorigin` attribute without re-scanning the tag text for the name boundary
41
43
  * @param {boolean=} hasOwnCrossOrigin whether the originating tag already carries a `crossorigin` attribute; when true the author's value wins and `output.crossOriginLoading` is not applied
42
- * @param {number=} cssAnchor source offset of the first `<script>` tag; a later entry's injected stylesheet `<link>`s are inserted here so CSS loads before any script. -1 when the document has no script
44
+ * @param {number=} cssAnchor source offset of the first classic blocking `<script>` tag (not `defer`/`async`/module — those execute after parsing and after pending stylesheets); injected stylesheet `<link>`s are kept ahead of it. -1 when the document has no blocking script, letting CSS follow `defer`/module script tags (the order Vite emits)
43
45
  * @param {Range | null=} integrityRange source span of the originating tag's `integrity` attribute (offsets relative to `tagStart`, leading whitespace and quotes included), captured at parse time so cloned sibling tags can drop it without re-parsing; null when absent
44
46
  * @param {Range | null=} typeValueRange source span of the originating tag's `type` attribute value (offsets relative to `tagStart`), captured at parse time so `script-module` sibling clones can force `type="module"` in place; null when there is no value to rewrite (a `type="module"` is then inserted instead)
47
+ * @param {boolean=} forceInline `<!-- webpackInline: true -->` was set before this tag; takes precedence over `output.html.inline`
48
+ * @param {number=} headOpenEnd position just inside the (possibly implicit) `<head>` — after its opening tag, else after `<html>` or the doctype — captured from the parse so the template needn't re-scan the source (a text scan would match markup inside comments); resource hints are inserted here. -1 when unknown
49
+ * @param {number=} headAnchor position right after the last node inside `<head>` (the same anchor `output.html` title/meta injection uses); `inject: "head"` sibling tags are inserted here. -1 when the document has no usable head (insertion then falls back to the entry tag)
45
50
  */
46
51
  constructor(
47
52
  request,
@@ -57,7 +62,10 @@ class HtmlEntryDependency extends ModuleDependency {
57
62
  hasOwnCrossOrigin,
58
63
  cssAnchor,
59
64
  integrityRange,
60
- typeValueRange
65
+ typeValueRange,
66
+ forceInline,
67
+ headOpenEnd,
68
+ headAnchor
61
69
  ) {
62
70
  super(request);
63
71
  this.range = range;
@@ -85,6 +93,12 @@ class HtmlEntryDependency extends ModuleDependency {
85
93
  this.integrityRange = integrityRange || null;
86
94
  /** @type {Range | null} */
87
95
  this.typeValueRange = typeValueRange || null;
96
+ /** @type {boolean} */
97
+ this.forceInline = forceInline === true;
98
+ /** @type {number} */
99
+ this.headOpenEnd = headOpenEnd === undefined ? -1 : headOpenEnd;
100
+ /** @type {number} */
101
+ this.headAnchor = headAnchor === undefined ? -1 : headAnchor;
88
102
  }
89
103
 
90
104
  get type() {
@@ -112,7 +126,10 @@ class HtmlEntryDependency extends ModuleDependency {
112
126
  .write(this.hasOwnCrossOrigin)
113
127
  .write(this.cssAnchor)
114
128
  .write(this.integrityRange)
115
- .write(this.typeValueRange);
129
+ .write(this.typeValueRange)
130
+ .write(this.forceInline)
131
+ .write(this.headOpenEnd)
132
+ .write(this.headAnchor);
116
133
  super.serialize(context);
117
134
  }
118
135
 
@@ -144,7 +161,13 @@ class HtmlEntryDependency extends ModuleDependency {
144
161
  this.integrityRange = c10.read();
145
162
  const c11 = c10.rest;
146
163
  this.typeValueRange = c11.read();
147
- super.deserialize(c11.rest);
164
+ const c12 = c11.rest;
165
+ this.forceInline = c12.read();
166
+ const c13 = c12.rest;
167
+ this.headOpenEnd = c13.read();
168
+ const c14 = c13.rest;
169
+ this.headAnchor = c14.read();
170
+ super.deserialize(c14.rest);
148
171
  }
149
172
  }
150
173
 
@@ -278,6 +301,25 @@ const cssChunkSortKey = (chunk) =>
278
301
  chunk.id === null || chunk.id === undefined ? "" : chunk.id
279
302
  }`;
280
303
 
304
+ /**
305
+ * Sort comparator for the CSS sibling emission order. Direct subtraction
306
+ * would yield `NaN` when both indices are `Infinity` (the documented
307
+ * fallback for chunks whose CSS modules the entrypoint's walk never
308
+ * reaches), and `Array#sort` doesn't promise stable ordering on the legacy
309
+ * Node 10 targets this repo still supports — so the tie-breaker must always
310
+ * run when the indices match, including the `Infinity === Infinity` case.
311
+ * @param {{ index: number, key: string }} a first entry
312
+ * @param {{ index: number, key: string }} b second entry
313
+ * @returns {-1 | 0 | 1} sort order
314
+ */
315
+ const compareCssChunkOrder = (a, b) => {
316
+ if (a.index < b.index) return -1;
317
+ if (a.index > b.index) return 1;
318
+ if (a.key < b.key) return -1;
319
+ if (a.key > b.key) return 1;
320
+ return 0;
321
+ };
322
+
281
323
  const CSS_SOURCE_TYPES = [CSS_TYPE, CSS_IMPORT_TYPE];
282
324
 
283
325
  /**
@@ -328,11 +370,12 @@ const firstCssModulePostOrderIndex = (chunk, entrypoint, chunkGraph) => {
328
370
  * @param {string} extra the copyable CSP/fetch attribute source text (leading-space-prefixed, or empty)
329
371
  * @param {string} href URL for the stylesheet
330
372
  * @param {string} crossOrigin ` crossorigin="…"` to append from `output.crossOriginLoading`, or `""`
373
+ * @param {string} integrity ` integrity="<sentinel>"` to append when `output.html.integrity` is on, or `""`
331
374
  * @returns {string} the sibling `<link>` tag's HTML
332
375
  */
333
- const buildStylesheetLink = (extra, href, crossOrigin) => {
376
+ const buildStylesheetLink = (extra, href, crossOrigin, integrity) => {
334
377
  const safeHref = href.replace(/"/g, "&quot;");
335
- return `<link rel="stylesheet" href="${safeHref}"${extra}${crossOrigin}>`;
378
+ return `<link rel="stylesheet" href="${safeHref}"${extra}${crossOrigin}${integrity}>`;
336
379
  };
337
380
 
338
381
  /**
@@ -348,13 +391,12 @@ const buildStylesheetLink = (extra, href, crossOrigin) => {
348
391
  * @param {string} src URL for the script
349
392
  * @param {boolean} isModule whether to emit `type="module"`
350
393
  * @param {string} crossOrigin ` crossorigin="…"` to append from `output.crossOriginLoading`, or `""`
394
+ * @param {string} integrity ` integrity="<sentinel>"` to append when `output.html.integrity` is on, or `""`
351
395
  * @returns {string} the sibling `<script>` tag's HTML
352
396
  */
353
- const buildScriptTag = (extra, src, isModule, crossOrigin) => {
397
+ const buildScriptTag = (extra, src, isModule, crossOrigin, integrity) => {
354
398
  const safeSrc = src.replace(/"/g, "&quot;");
355
- return `<script${
356
- isModule ? ' type="module"' : ""
357
- } src="${safeSrc}"${extra}${crossOrigin}></script>`;
399
+ return `<script${isModule ? ' type="module"' : ""} src="${safeSrc}"${extra}${crossOrigin}${integrity}></script>`;
358
400
  };
359
401
 
360
402
  /**
@@ -375,9 +417,10 @@ const buildScriptTag = (extra, src, isModule, crossOrigin) => {
375
417
  * @param {string} newUrl URL to put into the cloned tag's src/href slot
376
418
  * @param {HtmlEntryElementKind} elementKind shape of the originating tag
377
419
  * @param {string} crossOrigin ` crossorigin="…"` to insert from `output.crossOriginLoading`, or `""`
378
- * @param {number} tagNameEndInTag offset right after the tag name within `originalTag`, where `crossOrigin` is inserted
420
+ * @param {number} tagNameEndInTag offset right after the tag name within `originalTag`, where `crossOrigin`/`integrity` are inserted
379
421
  * @param {Range | null} integrityRange `integrity` attribute span within `originalTag` to drop, or null
380
422
  * @param {Range | null} typeValueRange `type` value span within `originalTag` to overwrite with `module`, or null to insert `type="module"`
423
+ * @param {string} integrity ` integrity="<sentinel>"` to insert when `output.html.integrity` is on, or `""`
381
424
  * @returns {string} the sibling tag's HTML (including a closing `</script>` for script tags)
382
425
  */
383
426
  const cloneTagWithUrl = (
@@ -389,7 +432,8 @@ const cloneTagWithUrl = (
389
432
  crossOrigin,
390
433
  tagNameEndInTag,
391
434
  integrityRange,
392
- typeValueRange
435
+ typeValueRange,
436
+ integrity
393
437
  ) => {
394
438
  const isLink =
395
439
  elementKind === "modulepreload" || elementKind === "stylesheet";
@@ -397,9 +441,9 @@ const cloneTagWithUrl = (
397
441
  /** @type {{ start: number, end: number, text: string }[]} */
398
442
  const edits = [{ start: srcStartInTag, end: srcEndInTag, text: newUrl }];
399
443
 
400
- // `integrity` is content-specific — drop it from the clone.
401
- // TODO: emit a correct per-chunk `integrity` once a core SRI output
402
- // option exists, instead of dropping it.
444
+ // Drop the author's `integrity` — content-specific, wrong for another
445
+ // chunk's file. A correct per-chunk one is re-inserted below from
446
+ // `output.html.integrity`.
403
447
  if (integrityRange) {
404
448
  edits.push({ start: integrityRange[0], end: integrityRange[1], text: "" });
405
449
  }
@@ -420,6 +464,8 @@ const cloneTagWithUrl = (
420
464
  }
421
465
  }
422
466
 
467
+ // Per-chunk `integrity` sentinel goes in alongside `crossorigin`/`type`.
468
+ afterName += integrity;
423
469
  if (afterName) {
424
470
  edits.push({
425
471
  start: tagNameEndInTag,
@@ -438,6 +484,181 @@ const cloneTagWithUrl = (
438
484
  return isLink ? body : `${body}</script>`;
439
485
  };
440
486
 
487
+ // ES5 `<link rel="modulepreload">` polyfill (Safari <17, Firefox <115, …):
488
+ // on browsers without native support, fetch each modulepreload target with the
489
+ // tag's own credentials/integrity so the pending module import reuses it. The
490
+ // `.ep` guard makes it idempotent, and the observer catches any later-inserted
491
+ // links. Emitted only when the target environment lacks native support.
492
+ const MODULEPRELOAD_POLYFILL = [
493
+ "(function(){",
494
+ 'var s=document.createElement("link").relList;',
495
+ 'if(s&&s.supports&&s.supports("modulepreload"))return;',
496
+ "function f(e){",
497
+ "if(e.ep)return;e.ep=1;",
498
+ "var o={};",
499
+ "if(e.integrity)o.integrity=e.integrity;",
500
+ "if(e.referrerPolicy)o.referrerPolicy=e.referrerPolicy;",
501
+ 'o.credentials=e.crossOrigin==="use-credentials"?"include":e.crossOrigin==="anonymous"?"omit":"same-origin";',
502
+ "fetch(e.href,o)}",
503
+ "var l=document.querySelectorAll('link[rel=\"modulepreload\"]');",
504
+ "for(var i=0;i<l.length;i++)f(l[i]);",
505
+ "new MutationObserver(function(m){",
506
+ "for(var j=0;j<m.length;j++)",
507
+ "for(var a=m[j].addedNodes,k=0;k<a.length;k++)",
508
+ 'if(a[k].tagName==="LINK"&&a[k].rel==="modulepreload")f(a[k])',
509
+ "}).observe(document,{childList:true,subtree:true})",
510
+ "})();"
511
+ ].join("");
512
+
513
+ // One polyfill `<script>` per rendered page. Keyed by the `ReplaceSource` of a
514
+ // single HTML code-generation pass (shared by all of a page's entry tags, fresh
515
+ // per pass), so multi-`<script>` pages get exactly one and the cache stays correct.
516
+ /** @type {WeakSet<ReplaceSource>} */
517
+ const polyfilledSources = new WeakSet();
518
+
519
+ /** @typedef {"preload" | "prefetch" | "modulepreload" | "preconnect" | "dns-prefetch"} HtmlResourceHintRel */
520
+ /**
521
+ * A custom resource-hint `<link>` (`output.html.resourceHints`). Exactly one of
522
+ * `href` / `chunk` / `entry` names the target; `chunk`/`entry` URLs (hash and
523
+ * public path) are resolved by webpack, `href` is used verbatim.
524
+ * @typedef {object} HtmlResourceHint
525
+ * @property {HtmlResourceHintRel} rel hint relationship
526
+ * @property {string=} href literal URL (external, `preconnect`, or an already-hashed asset)
527
+ * @property {string=} chunk chunk name to hint
528
+ * @property {string=} entry entrypoint name to hint (expands to one hint per initial chunk)
529
+ * @property {string=} as the `as` attribute; defaults to `script` for chunk/entry refs
530
+ * @property {string=} type the `type` attribute (MIME)
531
+ * @property {(boolean | "anonymous" | "use-credentials")=} crossorigin CORS mode (`true` → `anonymous`)
532
+ * @property {string=} media the `media` attribute
533
+ * @property {boolean=} integrity SRI for a chunk/entry ref; follows `output.html.integrity` by default, `false` opts out
534
+ * @property {("low" | "high" | "auto")=} fetchPriority the `fetchpriority` attribute (allowed on `preload`/`modulepreload`/`prefetch`)
535
+ */
536
+ /**
537
+ * @typedef {object} HtmlResourceHintContext
538
+ * @property {string} entryName name of the entry this page loads
539
+ * @property {Entrypoint} entrypoint the entrypoint
540
+ * @property {"html" | "js"} hostType `"html"` when the entry has an extracted HTML page; `"js"` when a JS-only entry is being consulted (SSR framework case)
541
+ * @property {import("../Compilation")} compilation the compilation
542
+ * @property {{ rel: "modulepreload" | "preload" | "prefetch", chunk: string, hostChunks: string[] }[]} defaultHints the auto initial-graph hints (`chunk` / `hostChunks` name the referencing chunk — Vite's `hostId`)
543
+ */
544
+
545
+ /**
546
+ * @param {string} value attribute value
547
+ * @returns {string} value with `"` escaped so it can't break out of the attribute
548
+ */
549
+ const escapeAttr = (value) => String(value).replace(/"/g, "&quot;");
550
+
551
+ /**
552
+ * @param {boolean | "anonymous" | "use-credentials" | undefined} value crossorigin option
553
+ * @returns {string | undefined} ` crossorigin="…"`, or undefined when unset/false
554
+ */
555
+ const crossOriginAttr = (value) => {
556
+ if (value === undefined || value === false) return undefined;
557
+ return ` crossorigin="${value === true ? "anonymous" : value}"`;
558
+ };
559
+
560
+ /**
561
+ * Build one resource-hint `<link>` tag from a resolved href and attributes.
562
+ * @param {HtmlResourceHintRel} rel the `rel`
563
+ * @param {{ href: string, as?: string, type?: string, crossorigin?: string, integrity?: string, media?: string, fetchpriority?: string }} attrs attributes (already href-resolved; `crossorigin`/`integrity` are full ` name="…"` spans)
564
+ * @returns {string} the `<link>` tag
565
+ */
566
+ const buildLinkTag = (rel, attrs) =>
567
+ `<link rel="${rel}"${attrs.as ? ` as="${escapeAttr(attrs.as)}"` : ""}${
568
+ attrs.type ? ` type="${escapeAttr(attrs.type)}"` : ""
569
+ } href="${escapeAttr(attrs.href)}"${attrs.crossorigin || ""}${
570
+ attrs.integrity || ""
571
+ }${attrs.media ? ` media="${escapeAttr(attrs.media)}"` : ""}${
572
+ attrs.fetchpriority
573
+ ? ` fetchpriority="${escapeAttr(attrs.fetchpriority)}"`
574
+ : ""
575
+ }>`;
576
+
577
+ /**
578
+ * Resolve one `output.html.resourceHints` descriptor to zero or more `<link>`
579
+ * tags. A `chunk`/`entry` reference becomes the emitted chunk URL (a sentinel
580
+ * resolved with the final hash and public path), an `href` is used verbatim, and
581
+ * `preconnect`/`dns-prefetch` use only `href`. Unknown chunk/entry names are skipped.
582
+ * @param {HtmlResourceHint} desc the descriptor
583
+ * @param {{ compilation: import("../Compilation"), chunkGraph: ChunkGraph, chunkByName: Map<string, Chunk>, outputCrossOrigin: string, integrityOn: boolean }} ctx resolution context
584
+ * @returns {string[]} the resolved tags
585
+ */
586
+ const resolveResourceHint = (desc, ctx) => {
587
+ const rel = desc.rel;
588
+ if (rel === "preconnect" || rel === "dns-prefetch") {
589
+ if (!desc.href) return [];
590
+ return [
591
+ buildLinkTag(rel, {
592
+ href: desc.href,
593
+ crossorigin: crossOriginAttr(desc.crossorigin),
594
+ media: desc.media
595
+ })
596
+ ];
597
+ }
598
+ if (desc.href) {
599
+ return [
600
+ buildLinkTag(rel, {
601
+ href: desc.href,
602
+ as: desc.as,
603
+ type: desc.type,
604
+ crossorigin: crossOriginAttr(desc.crossorigin),
605
+ media: desc.media,
606
+ fetchpriority: desc.fetchPriority
607
+ })
608
+ ];
609
+ }
610
+ /** @type {Chunk[]} */
611
+ let chunks;
612
+ if (desc.entry) {
613
+ const entrypoint = ctx.compilation.entrypoints.get(desc.entry);
614
+ if (!entrypoint) return [];
615
+ chunks = getEntrypointChunksInLoadOrder(entrypoint);
616
+ } else if (desc.chunk) {
617
+ const chunk = ctx.chunkByName.get(desc.chunk);
618
+ if (!chunk) return [];
619
+ chunks = [chunk];
620
+ } else {
621
+ return [];
622
+ }
623
+ const kind = desc.as === "style" ? "css" : "javascript";
624
+ const as = desc.as || (rel === "modulepreload" ? undefined : "script");
625
+ // A hint targeting another chunk must match how that chunk is fetched, so
626
+ // inherit `output.crossOriginLoading` unless the descriptor overrides it.
627
+ const crossorigin =
628
+ desc.crossorigin !== undefined
629
+ ? crossOriginAttr(desc.crossorigin)
630
+ : ctx.outputCrossOrigin || undefined;
631
+ /** @type {string[]} */
632
+ const tags = [];
633
+ for (const chunk of chunks) {
634
+ const has =
635
+ kind === "css"
636
+ ? chunkHasCss(chunk, ctx.chunkGraph)
637
+ : chunkHasJs(chunk, ctx.chunkGraph);
638
+ if (!has) continue;
639
+ tags.push(
640
+ buildLinkTag(rel, {
641
+ href: HtmlGenerator.makeChunkUrlSentinel(chunk, kind),
642
+ as,
643
+ type: desc.type,
644
+ crossorigin,
645
+ // SRI follows the global `output.html.integrity` policy (like the
646
+ // auto hints); `integrity: false` opts a single hint out. Only
647
+ // `preload`/`modulepreload` are SRI-eligible — `prefetch` is not.
648
+ integrity:
649
+ (rel === "preload" || rel === "modulepreload") &&
650
+ desc.integrity !== false &&
651
+ ctx.integrityOn
652
+ ? ` integrity="${HtmlGenerator.makeChunkIntegritySentinel(chunk, kind)}"`
653
+ : undefined,
654
+ media: desc.media,
655
+ fetchpriority: desc.fetchPriority
656
+ })
657
+ );
658
+ }
659
+ return tags;
660
+ };
661
+
441
662
  HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
442
663
  ModuleDependency.Template
443
664
  ) {
@@ -452,6 +673,20 @@ HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
452
673
  const { runtimeTemplate } = templateContext;
453
674
  const dep = /** @type {HtmlEntryDependency} */ (dependency);
454
675
  const compilation = runtimeTemplate.compilation;
676
+
677
+ // A `type: "html"` link points at another emitted page, not a JS/CSS
678
+ // chunk: rewrite the attribute to that page's filename (resolved by
679
+ // HtmlModulesPlugin once it's built) and emit no sibling tags — a page
680
+ // link loads only itself.
681
+ if (dep.elementKind === "html") {
682
+ source.replace(
683
+ dep.range[0],
684
+ dep.range[1] - 1,
685
+ HtmlGenerator.makeHtmlPageUrlSentinel(dep.entryName)
686
+ );
687
+ return;
688
+ }
689
+
455
690
  const { chunkGraph } = compilation;
456
691
  const { crossOriginLoading } = compilation.outputOptions;
457
692
  const entrypoint = /** @type {Entrypoint | undefined} */ (
@@ -465,74 +700,218 @@ HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
465
700
 
466
701
  const orderedChunks = getEntrypointChunksInLoadOrder(entrypoint);
467
702
  const entryChunk = orderedChunks[orderedChunks.length - 1];
703
+
704
+ // `crossorigin` mirrors `output.crossOriginLoading` onto every injected
705
+ // tag. Empty when the option is off or the originating tag already set
706
+ // `crossorigin` (author value wins, flagged at parse time). Mirrors
707
+ // webpack's runtime, which sets `crossOrigin` on chunk-loading scripts,
708
+ // and matches Vite, which emits it on every injected script/stylesheet.
709
+ const crossOrigin =
710
+ crossOriginLoading && !dep.hasOwnCrossOrigin
711
+ ? ` crossorigin="${crossOriginLoading}"`
712
+ : "";
713
+
714
+ // Per-chunk SRI: emit an `integrity` sentinel resolved late (after the
715
+ // chunks' final bytes exist) by `resolveChunkIntegritySentinels`. On when
716
+ // `output.html.integrity` is `true`, a non-empty array, or a function.
717
+ const htmlOption = compilation.outputOptions.html;
718
+ const integrity =
719
+ typeof htmlOption === "object" ? htmlOption.integrity : undefined;
720
+ const integrityOn =
721
+ integrity === true ||
722
+ typeof integrity === "function" ||
723
+ (Array.isArray(integrity) && integrity.length > 0);
724
+ const inject =
725
+ typeof htmlOption === "object" ? htmlOption.inject : undefined;
726
+
727
+ /**
728
+ * Mirror `crossorigin`/`integrity` onto a native entry tag, inserted right
729
+ * after the tag name so they sit alongside any `type="module"` the parser
730
+ * injected. The author's content-specific `integrity` is dropped first (as
731
+ * clones do) so the tag never carries two.
732
+ * @param {Chunk} chunk chunk whose SRI hash the tag references
733
+ * @param {"javascript" | "css"} contentHashType which chunk asset to hash
734
+ * @returns {void}
735
+ */
736
+ const applyEntryTagAttrs = (chunk, contentHashType) => {
737
+ if (!dep.tagIsNative || dep.tagNameEnd < 0) return;
738
+ if (crossOrigin) source.insert(dep.tagNameEnd, crossOrigin);
739
+ if (integrityOn) {
740
+ if (dep.integrityRange) {
741
+ source.replace(
742
+ dep.tagStart + dep.integrityRange[0],
743
+ dep.tagStart + dep.integrityRange[1] - 1,
744
+ ""
745
+ );
746
+ }
747
+ source.insert(
748
+ dep.tagNameEnd,
749
+ ` integrity="${HtmlGenerator.makeChunkIntegritySentinel(
750
+ chunk,
751
+ contentHashType
752
+ )}"`
753
+ );
754
+ }
755
+ };
756
+
757
+ // `rel="preload"/"prefetch"` is a resource hint: rewrite the `href` to the
758
+ // built chunk's URL (CSS for `as="style"`, JS otherwise) and stop — no
759
+ // sibling tags and no execution.
760
+ if (dep.elementKind === "preload" || dep.elementKind === "prefetch") {
761
+ const contentHashType =
762
+ dep.category === "css-import" ? "css" : "javascript";
763
+ source.replace(
764
+ dep.range[0],
765
+ dep.range[1] - 1,
766
+ HtmlGenerator.makeChunkUrlSentinel(entryChunk, contentHashType)
767
+ );
768
+ // `preload` is integrity-eligible per the SRI spec, so mirror
769
+ // `crossorigin`/`integrity` onto it (SRI requires CORS). `prefetch` is a
770
+ // navigation cache hint the spec doesn't cover — leave it a bare href.
771
+ if (dep.elementKind === "preload") {
772
+ applyEntryTagAttrs(entryChunk, contentHashType);
773
+ }
774
+ return;
775
+ }
776
+
468
777
  const isStylesheet = dep.elementKind === "stylesheet";
778
+ // Inlined scripts are `type="module"` only for ESM output; classic-IIFE
779
+ // chunks (the default) must stay a plain `<script>`.
780
+ const scriptTypeAttr =
781
+ compilation.outputOptions.module === true ? ' type="module"' : "";
469
782
 
470
- // Rewrite src/href to a chunk-URL sentinel (resolved by renderManifest):
471
- // `.css` for `<link rel="stylesheet">`, `.js` for everything else.
472
783
  const entryContentHashType = isStylesheet ? "css" : "javascript";
473
- const entryUrl = HtmlGenerator.makeChunkUrlSentinel(
474
- entryChunk,
475
- entryContentHashType
476
- );
477
- source.replace(dep.range[0], dep.range[1] - 1, entryUrl);
784
+ const inlineOption =
785
+ typeof htmlOption === "object" ? htmlOption.inline : undefined;
786
+ /** @type {RegExp[] | null} */
787
+ const inlinePatterns = Array.isArray(inlineOption) ? inlineOption : null;
788
+ /**
789
+ * @param {Chunk} chunk chunk to test
790
+ * @param {"javascript" | "css"} contentType which asset of the chunk is being emitted
791
+ * @returns {boolean} true when this chunk's content should be inlined
792
+ */
793
+ const isChunkInlined = (chunk, contentType) => {
794
+ // A `<link rel="modulepreload">` is a void fetch hint — inlining it would
795
+ // emit an unclosed, executing `<script>`.
796
+ if (dep.elementKind === "modulepreload") return false;
797
+ if (dep.forceInline) return true;
798
+ if (!inlineOption) return false;
799
+ if (inlineOption === true) return true;
800
+ // `"script"`/`"style"` inline by asset type, not chunk name — a chunk's
801
+ // JS and CSS share a name, so only the emitted `contentType` tells them
802
+ // apart.
803
+ if (inlineOption === "script") return contentType === "javascript";
804
+ if (inlineOption === "style") return contentType === "css";
805
+ const name = chunk.name || String(chunk.id);
806
+ return /** @type {RegExp[]} */ (inlinePatterns).some((re) =>
807
+ re.test(name)
808
+ );
809
+ };
810
+
811
+ // Only inline when the HTML module is emitted as a standalone page (extract mode).
812
+ // If it's exported as a JS string, sentinels would end up embedded in the JS chunk.
813
+ const isExtractedHtml = chunkGraph
814
+ .getModuleSourceTypes(
815
+ /** @type {import("../Module")} */ (templateContext.module)
816
+ )
817
+ .has("html");
818
+ const entryInlined =
819
+ isExtractedHtml &&
820
+ dep.tagStart >= 0 &&
821
+ dep.tagOpenEnd > dep.tagStart &&
822
+ isChunkInlined(entryChunk, entryContentHashType);
823
+
824
+ if (entryInlined) {
825
+ const inlineSentinel = HtmlGenerator.makeChunkInlineSentinel(
826
+ entryChunk,
827
+ entryContentHashType
828
+ );
829
+ if (isStylesheet) {
830
+ source.replace(
831
+ dep.tagStart,
832
+ dep.tagOpenEnd - 1,
833
+ `<style>${inlineSentinel}</style>`
834
+ );
835
+ } else {
836
+ source.replace(
837
+ dep.tagStart,
838
+ dep.tagOpenEnd - 1,
839
+ `<script${scriptTypeAttr}>${inlineSentinel}`
840
+ );
841
+ }
842
+ } else {
843
+ source.replace(
844
+ dep.range[0],
845
+ dep.range[1] - 1,
846
+ HtmlGenerator.makeChunkUrlSentinel(entryChunk, entryContentHashType)
847
+ );
848
+ }
478
849
 
479
850
  if (dep.tagStart < 0 || dep.tagOpenEnd <= dep.tagStart) {
480
851
  return;
481
852
  }
482
853
 
483
- // The browser must load every chunk the entry needs, not just the
484
- // entry chunk. For `<script>` entries that's the JS for sibling
485
- // chunks plus — critically — the CSS for any chunk that holds
486
- // stylesheets imported transitively from the JS source. Previously
487
- // every sibling was cloned as a `<script>` pointing at a `.js`
488
- // filename, so CSS chunks ended up as `<script src="foo.css">`
489
- // pointing at non-existent `.js` files (the bug in
490
- // html-webpack-plugin#1838 / webpack/mini-css-extract-plugin#959,
491
- // magnified here because the entry chunk's own CSS was emitted to
492
- // disk but never linked from the HTML at all).
493
854
  const originalContent = /** @type {string} */ (source.original().source());
494
855
  const originalTag = originalContent.slice(dep.tagStart, dep.tagOpenEnd);
495
856
  const srcStartInTag = dep.range[0] - dep.tagStart;
496
857
  const srcEndInTag = dep.range[1] - dep.tagStart;
497
- // Non-native originating elements (a custom element mapped to a
498
- // `script`/`stylesheet` source `type`) can't be cloned verbatim — the
499
- // clone would be invalid markup (e.g. `<my-script …></script>`), so a
500
- // real native tag is synthesized instead. Decided at parse time from the
501
- // tag name (`dep.tagIsNative`) rather than re-parsing the source text.
502
858
  const tagIsNative = dep.tagIsNative;
503
-
504
- // `crossorigin` to mirror `output.crossOriginLoading` onto every injected
505
- // tag. Empty when the option is off or the originating tag already set
506
- // `crossorigin` (author value wins, flagged at parse time). Mirrors
507
- // webpack's runtime, which sets `crossOrigin` on chunk-loading scripts,
508
- // and matches Vite, which emits it on every injected script/stylesheet.
509
- const crossOrigin =
510
- crossOriginLoading && !dep.hasOwnCrossOrigin
511
- ? ` crossorigin="${crossOriginLoading}"`
512
- : "";
513
859
  const tagNameEndInTag = dep.tagNameEnd - dep.tagStart;
514
860
 
515
- // Mirror it onto the entry tag too (siblings get it via the builders
516
- // below), inserted right after the tag name — its parse-time offset — so
517
- // it sits alongside any `type="module"` the parser injected.
518
- if (tagIsNative && crossOrigin && dep.tagNameEnd >= 0) {
519
- source.insert(dep.tagNameEnd, crossOrigin);
520
- }
861
+ // Mirror `crossorigin`/`integrity` onto the non-inlined entry tag
862
+ // (siblings get them via the builders below).
863
+ if (!entryInlined) applyEntryTagAttrs(entryChunk, entryContentHashType);
521
864
 
522
- /**
523
- * @param {Chunk} chunk chunk to emit a sibling tag for
524
- * @param {"javascript" | "css"} kind content type slice of the chunk to emit
525
- * @returns {string} a single sibling tag's HTML
526
- */
527
- const buildSibling = (chunk, kind) => {
528
- const url = HtmlGenerator.makeChunkUrlSentinel(chunk, kind);
529
- if (kind === "css") {
530
- // A CSS chunk is always loaded via `<link rel="stylesheet">`.
531
- // Clone only when the entry tag is itself a native `<link
532
- // rel="stylesheet">` (so attributes like `media` carry over);
533
- // a `<script>` entry that imported CSS, or a custom element,
534
- // gets a freshly synthesized `<link>`.
535
- if (isStylesheet && tagIsNative) {
865
+ // `inject: false` suppresses sibling-chunk tags only; resource hints
866
+ // below still apply (the way to reference the suppressed chunks).
867
+ if (inject !== false) {
868
+ /**
869
+ * @param {Chunk} chunk chunk to emit a sibling tag for
870
+ * @param {"javascript" | "css"} kind content type slice of the chunk to emit
871
+ * @returns {string} a single sibling tag's HTML
872
+ */
873
+ const buildSibling = (chunk, kind) => {
874
+ if (isChunkInlined(chunk, kind)) {
875
+ const sentinel = HtmlGenerator.makeChunkInlineSentinel(chunk, kind);
876
+ return kind === "css"
877
+ ? `<style>${sentinel}</style>`
878
+ : `<script${scriptTypeAttr}>${sentinel}</script>`;
879
+ }
880
+ const url = HtmlGenerator.makeChunkUrlSentinel(chunk, kind);
881
+ const integrityAttr = integrityOn
882
+ ? ` integrity="${HtmlGenerator.makeChunkIntegritySentinel(chunk, kind)}"`
883
+ : "";
884
+ if (kind === "css") {
885
+ // A CSS chunk is always loaded via `<link rel="stylesheet">`.
886
+ // Clone only when the entry tag is itself a native `<link
887
+ // rel="stylesheet">` (so attributes like `media` carry over);
888
+ // a `<script>` entry that imported CSS, or a custom element,
889
+ // gets a freshly synthesized `<link>`.
890
+ if (isStylesheet && tagIsNative) {
891
+ return cloneTagWithUrl(
892
+ originalTag,
893
+ srcStartInTag,
894
+ srcEndInTag,
895
+ url,
896
+ dep.elementKind,
897
+ crossOrigin,
898
+ tagNameEndInTag,
899
+ dep.integrityRange,
900
+ dep.typeValueRange,
901
+ integrityAttr
902
+ );
903
+ }
904
+ return buildStylesheetLink(
905
+ dep.copyableAttrsText,
906
+ url,
907
+ crossOrigin,
908
+ integrityAttr
909
+ );
910
+ }
911
+ // A JS chunk is loaded via `<script>`. Clone a native `<script>`
912
+ // verbatim; synthesize a real `<script>` for custom elements that
913
+ // were mapped to `type: script`/`script-module`.
914
+ if (tagIsNative) {
536
915
  return cloneTagWithUrl(
537
916
  originalTag,
538
917
  srcStartInTag,
@@ -542,122 +921,368 @@ HtmlEntryDependency.Template = class HtmlEntryDependencyTemplate extends (
542
921
  crossOrigin,
543
922
  tagNameEndInTag,
544
923
  dep.integrityRange,
545
- dep.typeValueRange
924
+ dep.typeValueRange,
925
+ integrityAttr
546
926
  );
547
927
  }
548
- return buildStylesheetLink(dep.copyableAttrsText, url, crossOrigin);
549
- }
550
- // A JS chunk is loaded via `<script>`. Clone a native `<script>`
551
- // verbatim; synthesize a real `<script>` for custom elements that
552
- // were mapped to `type: script`/`script-module`.
553
- if (tagIsNative) {
554
- return cloneTagWithUrl(
555
- originalTag,
556
- srcStartInTag,
557
- srcEndInTag,
928
+ return buildScriptTag(
929
+ dep.copyableAttrsText,
558
930
  url,
559
- dep.elementKind,
931
+ dep.elementKind === "script-module",
560
932
  crossOrigin,
561
- tagNameEndInTag,
562
- dep.integrityRange,
563
- dep.typeValueRange
933
+ integrityAttr
564
934
  );
565
- }
566
- return buildScriptTag(
567
- dep.copyableAttrsText,
568
- url,
569
- dep.elementKind === "script-module",
570
- crossOrigin
571
- );
572
- };
935
+ };
573
936
 
574
- const siblings = [];
937
+ const siblings = [];
938
+ // Parser-derived anchor (a source text scan would match `</head>` inside
939
+ // comments or raw text).
940
+ const headClose = inject === "head" ? dep.headAnchor : -1;
941
+ // When the entry tag is already inside <head> (tagStart < headClose),
942
+ // inserting siblings at headClose would place them after the entry,
943
+ // reversing runtime-before-entry execution order for deferred scripts.
944
+ // Insert before the entry tag instead so the load order is preserved.
945
+ const headAt =
946
+ headClose < 0
947
+ ? -1
948
+ : dep.tagStart < headClose
949
+ ? dep.tagStart
950
+ : headClose;
575
951
 
576
- if (isStylesheet) {
577
- // `<link rel="stylesheet">` entries are CSS-only — every sibling
578
- // chunk in the entrypoint is also CSS. Keep cloning the original
579
- // `<link>` for them so attributes like `media` carry over.
580
- for (let i = 0; i < orderedChunks.length - 1; i++) {
581
- siblings.push(buildSibling(orderedChunks[i], "css"));
582
- }
583
- } else {
584
- // CSS chunks are emitted before JS chunks so the cascade is set
585
- // up before any script runs. Within CSS the order needs to match
586
- // the source's import order — `entrypoint.chunks` alone doesn't
587
- // give us that for arbitrary splitChunks layouts (splitChunks
588
- // inserts each new chunk before the entry chunk via
589
- // `insertChunk(_, before)`, so split CSS siblings end up in
590
- // *reverse* of the order they were processed — exactly the
591
- // html-webpack-plugin#1838 / mini-css-extract#959 symptom). We
592
- // re-derive the order from the entrypoint's module post-order
593
- // index, which mirrors the dependency walk and so reflects the
594
- // import order.
595
- /** @type {{ chunk: Chunk, index: number, key: string }[]} */
596
- const cssChunkOrder = [];
597
- /** @type {Chunk[]} */
598
- const jsChunks = [];
599
- for (let i = 0; i < orderedChunks.length - 1; i++) {
600
- const chunk = orderedChunks[i];
601
- const hasCss = chunkHasCss(chunk, chunkGraph);
602
- const hasJs = chunkHasJs(chunk, chunkGraph);
603
- if (hasCss) {
952
+ let cssHtml = "";
953
+ let cssAt = -1;
954
+ if (isStylesheet) {
955
+ // `<link rel="stylesheet">` entries are CSS, but a shared/runtime
956
+ // JS-only chunk (e.g. `optimization.runtimeChunk`) can still sit in
957
+ // the entrypoint — only clone the `<link>` for siblings that actually
958
+ // produce a `.css` asset (mirrors the JS branch below), otherwise the
959
+ // link points at a non-existent `.css` file (a 404, or an empty
960
+ // `<style>` when inlined). Attributes like `media` still carry over.
961
+ for (let i = 0; i < orderedChunks.length - 1; i++) {
962
+ if (chunkHasCss(orderedChunks[i], chunkGraph)) {
963
+ siblings.push(buildSibling(orderedChunks[i], "css"));
964
+ }
965
+ }
966
+ } else {
967
+ // CSS chunks are emitted before JS chunks so the cascade is set
968
+ // up before any script runs. Within CSS the order needs to match
969
+ // the source's import order — `entrypoint.chunks` alone doesn't
970
+ // give us that for arbitrary splitChunks layouts (splitChunks
971
+ // inserts each new chunk before the entry chunk via
972
+ // `insertChunk(_, before)`, so split CSS siblings end up in
973
+ // *reverse* of the order they were processed — exactly the
974
+ // html-webpack-plugin#1838 / mini-css-extract#959 symptom). We
975
+ // re-derive the order from the entrypoint's module post-order
976
+ // index, which mirrors the dependency walk and so reflects the
977
+ // import order.
978
+ /** @type {{ chunk: Chunk, index: number, key: string }[]} */
979
+ const cssChunkOrder = [];
980
+ /** @type {Chunk[]} */
981
+ const jsChunks = [];
982
+ for (let i = 0; i < orderedChunks.length - 1; i++) {
983
+ const chunk = orderedChunks[i];
984
+ const hasCss = chunkHasCss(chunk, chunkGraph);
985
+ const hasJs = chunkHasJs(chunk, chunkGraph);
986
+ if (hasCss) {
987
+ cssChunkOrder.push({
988
+ chunk,
989
+ index: firstCssModulePostOrderIndex(
990
+ chunk,
991
+ entrypoint,
992
+ chunkGraph
993
+ ),
994
+ key: cssChunkSortKey(chunk)
995
+ });
996
+ }
997
+ // Anything that isn't CSS-only stays on the JS lane, in the
998
+ // `orderedChunks` order — that preserves the runtime-first /
999
+ // vendor-before-entry invariant of `getEntrypointChunksInLoadOrder`.
1000
+ // Chunks that produce no `.js` and no `.css` (e.g. wasm-only
1001
+ // or asset-only) still get a `<script>` clone here so we
1002
+ // keep prior behavior for users who relied on it.
1003
+ if (hasJs || !hasCss) jsChunks.push(chunk);
1004
+ }
1005
+ // If the entry chunk itself contains CSS (entry JS imports CSS
1006
+ // without splitChunks separating it), fold it into the same CSS
1007
+ // ordering so the entry-chunk `<link>` lands in the correct
1008
+ // cascade position relative to sibling CSS chunks.
1009
+ if (chunkHasCss(entryChunk, chunkGraph)) {
604
1010
  cssChunkOrder.push({
605
- chunk,
606
- index: firstCssModulePostOrderIndex(chunk, entrypoint, chunkGraph),
607
- key: cssChunkSortKey(chunk)
1011
+ chunk: entryChunk,
1012
+ index: firstCssModulePostOrderIndex(
1013
+ entryChunk,
1014
+ entrypoint,
1015
+ chunkGraph
1016
+ ),
1017
+ key: cssChunkSortKey(entryChunk)
608
1018
  });
609
1019
  }
610
- // Anything that isn't CSS-only stays on the JS lane, in the
611
- // `orderedChunks` order — that preserves the runtime-first /
612
- // vendor-before-entry invariant of `getEntrypointChunksInLoadOrder`.
613
- // Chunks that produce no `.js` and no `.css` (e.g. wasm-only
614
- // or asset-only) still get a `<script>` clone here so we
615
- // keep prior behavior for users who relied on it.
616
- if (hasJs || !hasCss) jsChunks.push(chunk);
617
- }
618
- // If the entry chunk itself contains CSS (entry JS imports CSS
619
- // without splitChunks separating it), fold it into the same CSS
620
- // ordering so the entry-chunk `<link>` lands in the correct
621
- // cascade position relative to sibling CSS chunks.
622
- if (chunkHasCss(entryChunk, chunkGraph)) {
623
- cssChunkOrder.push({
624
- chunk: entryChunk,
625
- index: firstCssModulePostOrderIndex(
626
- entryChunk,
627
- entrypoint,
628
- chunkGraph
629
- ),
630
- key: cssChunkSortKey(entryChunk)
631
- });
1020
+ cssChunkOrder.sort(compareCssChunkOrder);
1021
+ // Stylesheets always land in <head> when the page has one. A
1022
+ // classic blocking script pins them ahead of itself (`cssAnchor`)
1023
+ // so its execution never awaits later CSS; when every script is
1024
+ // `defer`/module they go to the *end* of head, after the script
1025
+ // tags — Vite's and html-webpack-plugin's order: the scripts
1026
+ // start fetching earlier yet still execute after parsing and
1027
+ // after pending stylesheets.
1028
+ cssAt =
1029
+ dep.headAnchor >= 0
1030
+ ? dep.cssAnchor >= 0 && dep.cssAnchor < dep.headAnchor
1031
+ ? dep.cssAnchor
1032
+ : dep.headAnchor
1033
+ : dep.cssAnchor >= 0
1034
+ ? dep.cssAnchor
1035
+ : dep.tagStart;
1036
+ cssHtml = cssChunkOrder
1037
+ .map(({ chunk }) => buildSibling(chunk, "css"))
1038
+ .join("");
1039
+ for (const chunk of jsChunks) {
1040
+ siblings.push(buildSibling(chunk, "javascript"));
1041
+ }
632
1042
  }
633
- cssChunkOrder.sort((a, b) => {
634
- // Direct subtraction would yield `NaN` when both indices are
635
- // `Infinity` (the documented fallback for chunks whose CSS
636
- // modules the entrypoint's walk never reaches), and
637
- // `Array#sort` doesn't promise stable ordering on the legacy
638
- // Node 10 targets this repo still supports — so the
639
- // tie-breaker must always run when the indices match,
640
- // including the `Infinity === Infinity` case.
641
- if (a.index < b.index) return -1;
642
- if (a.index > b.index) return 1;
643
- if (a.key < b.key) return -1;
644
- if (a.key > b.key) return 1;
645
- return 0;
646
- });
647
- // Insert CSS before the first script (not just before this tag) so a
648
- // later entry's stylesheet still loads ahead of every script.
649
- const cssAt = dep.cssAnchor === -1 ? dep.tagStart : dep.cssAnchor;
650
- const cssLinks = cssChunkOrder.map(({ chunk }) =>
651
- buildSibling(chunk, "css")
652
- );
653
- if (cssLinks.length > 0) source.insert(cssAt, cssLinks.join(""));
654
- for (const chunk of jsChunks) {
655
- siblings.push(buildSibling(chunk, "javascript"));
1043
+
1044
+ // Same-offset inserts keep call order: CSS goes in before the
1045
+ // sibling scripts only when a blocking script demands CSS-first.
1046
+ if (cssHtml !== "" && dep.cssAnchor >= 0) source.insert(cssAt, cssHtml);
1047
+ if (siblings.length > 0) {
1048
+ source.insert(headAt >= 0 ? headAt : dep.tagStart, siblings.join(""));
656
1049
  }
1050
+ if (cssHtml !== "" && dep.cssAnchor < 0) source.insert(cssAt, cssHtml);
657
1051
  }
658
1052
 
659
- if (siblings.length > 0) {
660
- source.insert(dep.tagStart, siblings.join(""));
1053
+ // Resource hints are opt-in — webpack already loads the initial chunks
1054
+ // via parallel `<script>` tags, so there is no waterfall to prevent by
1055
+ // default. `output.resourceHints` requests them: `true` preloads the
1056
+ // entry's initial dependency chunks (runtime, vendor, split); an array
1057
+ // or function adds custom hints (a function may spread the auto
1058
+ // `defaultHints`). Only for extracted pages — a JS-string HTML export
1059
+ // would embed the sentinels in the JS chunk. URL-referenced assets
1060
+ // (fonts, images) carrying `webpackPrefetch` / `webpackPreload`
1061
+ // (magic-comment or `parser.<type>.urlHints` rule) also emit here.
1062
+ const resourceHintResolver =
1063
+ ResourceHintPlugin.getCompilationResolver(compilation);
1064
+ const resourceHints = resourceHintResolver.hints;
1065
+ if (
1066
+ isExtractedHtml &&
1067
+ (dep.elementKind === "script" || dep.elementKind === "script-module")
1068
+ ) {
1069
+ const isModuleOutput = compilation.outputOptions.module === true;
1070
+
1071
+ /** @type {string[]} */
1072
+ const hints = [];
1073
+
1074
+ // === preconnect channel — cross-origin publicPath (resourceHints.preconnect) ===
1075
+ const rhOptions = compilation.outputOptions.resourceHints;
1076
+ if (rhOptions && rhOptions.preconnect) {
1077
+ const pp = compilation.outputOptions.publicPath;
1078
+ const originMatch =
1079
+ typeof pp === "string" ? /^(https?:)?\/\/[^/?#]+/i.exec(pp) : null;
1080
+ if (originMatch) {
1081
+ hints.push(
1082
+ `<link rel="preconnect" href="${escapeAttr(originMatch[0])}"${crossOrigin}>`
1083
+ );
1084
+ }
1085
+ }
1086
+
1087
+ // === chunks channel — the entry's own initial dependency graph ===
1088
+ if (resourceHints !== undefined && resourceHints !== false) {
1089
+ // The auto initial-graph preload as descriptors carrying their exact
1090
+ // tag, built only when needed (`true`/`"prefetch"` emits them, a
1091
+ // function may spread them). The entry chunk is skipped — it's
1092
+ // already the `<script src>`. ESM uses `modulepreload` (fetch +
1093
+ // parse), classic output `preload as="script"`, and `"prefetch"`
1094
+ // opts into `<link rel="prefetch">` (idle-time fetch, no `as` / no
1095
+ // `integrity` — the spec doesn't list them). Async `import()` chunks
1096
+ // aren't covered here; use `module.parser.javascript.dynamicImport*`
1097
+ // which routes through webpack's on-demand chunk-load runtime.
1098
+ const prefetchMode = resourceHints === "prefetch";
1099
+ const preloadMode = resourceHints === true;
1100
+ /** @type {"modulepreload" | "preload" | "prefetch"} */
1101
+ const rel = prefetchMode
1102
+ ? "prefetch"
1103
+ : isModuleOutput
1104
+ ? "modulepreload"
1105
+ : "preload";
1106
+ /** @type {{ rel: "modulepreload" | "preload" | "prefetch", chunk: string, hostChunks: string[], _tag: string }[]} */
1107
+ const defaultHints = [];
1108
+ if (
1109
+ preloadMode ||
1110
+ prefetchMode ||
1111
+ typeof resourceHints === "function"
1112
+ ) {
1113
+ for (const chunk of orderedChunks) {
1114
+ if (
1115
+ chunk === entryChunk ||
1116
+ isChunkInlined(chunk, "javascript") ||
1117
+ !chunkHasJs(chunk, chunkGraph)
1118
+ ) {
1119
+ continue;
1120
+ }
1121
+ const href = HtmlGenerator.makeChunkUrlSentinel(
1122
+ chunk,
1123
+ "javascript"
1124
+ );
1125
+ const integrityAttr =
1126
+ integrityOn && !prefetchMode
1127
+ ? ` integrity="${HtmlGenerator.makeChunkIntegritySentinel(
1128
+ chunk,
1129
+ "javascript"
1130
+ )}"`
1131
+ : "";
1132
+ // Mirror the entry tag's `nonce`/`crossorigin`/`referrerpolicy`
1133
+ // (and `output.crossOriginLoading`) so the request matches the
1134
+ // sibling `<script>`'s and the browser reuses it instead of
1135
+ // fetching twice.
1136
+ const attrs = `${dep.copyableAttrsText}${crossOrigin}${integrityAttr}`;
1137
+ const tag = prefetchMode
1138
+ ? `<link rel="prefetch" href="${href}"${attrs}>`
1139
+ : isModuleOutput
1140
+ ? `<link rel="modulepreload" href="${href}"${attrs}>`
1141
+ : `<link rel="preload" as="script" href="${href}"${attrs}>`;
1142
+ const chunkName = chunk.name || String(chunk.id);
1143
+ defaultHints.push({
1144
+ rel,
1145
+ chunk: chunkName,
1146
+ hostChunks: [chunkName],
1147
+ _tag: tag
1148
+ });
1149
+ }
1150
+ }
1151
+
1152
+ if (preloadMode || prefetchMode) {
1153
+ for (const d of defaultHints) {
1154
+ hints.push(d._tag);
1155
+ }
1156
+ } else {
1157
+ // Array (custom only) or function (returns the final list, may spread
1158
+ // `defaultHints`). A returned default hint keeps its prebuilt tag; any
1159
+ // other descriptor is resolved through the URL/SRI pipeline.
1160
+ let descriptors = resourceHints;
1161
+ if (typeof resourceHints === "function") {
1162
+ // The page's own entry name (its named entry chunk), not the synthetic
1163
+ // `<script>` entry — that's what users key on.
1164
+ let entryName = dep.entryName;
1165
+ for (const c of chunkGraph.getModuleChunks(
1166
+ /** @type {import("../Module")} */ (templateContext.module)
1167
+ )) {
1168
+ if (c.name) {
1169
+ entryName = c.name;
1170
+ break;
1171
+ }
1172
+ }
1173
+ descriptors = resourceHints({
1174
+ entryName,
1175
+ entrypoint,
1176
+ hostType: "html",
1177
+ compilation,
1178
+ defaultHints
1179
+ });
1180
+ }
1181
+ if (Array.isArray(descriptors) && descriptors.length > 0) {
1182
+ /** @type {Map<string, Chunk>} */
1183
+ const chunkByName = new Map();
1184
+ for (const c of compilation.chunks) {
1185
+ if (c.name) chunkByName.set(c.name, c);
1186
+ }
1187
+ const outputCrossOrigin = crossOriginLoading
1188
+ ? ` crossorigin="${crossOriginLoading}"`
1189
+ : "";
1190
+ const ctx = {
1191
+ compilation,
1192
+ chunkGraph,
1193
+ chunkByName,
1194
+ outputCrossOrigin,
1195
+ integrityOn
1196
+ };
1197
+ for (const desc of descriptors) {
1198
+ if (!desc || typeof desc !== "object") continue;
1199
+ const prebuilt = /** @type {{ _tag?: string }} */ (desc)._tag;
1200
+ if (prebuilt) {
1201
+ hints.push(prebuilt);
1202
+ } else if (desc.rel) {
1203
+ for (const tag of resolveResourceHint(desc, ctx)) {
1204
+ hints.push(tag);
1205
+ }
1206
+ }
1207
+ }
1208
+ }
1209
+ }
1210
+ }
1211
+
1212
+ // === assets channel — URL-referenced assets carrying `prefetch`/`preload` ===
1213
+ // The plugin has already scanned the entrypoint's chunks × modules
1214
+ // once and cached the URL/CSS/HTML asset deps for this entry name;
1215
+ // consume that list here so this template doesn't repeat the walk.
1216
+ // Each unique asset emits one `<link rel="prefetch|preload">` into
1217
+ // the HTML `<head>`; the JS chunk-startup runtime skips it (no
1218
+ // double `<link>` in DOM). URLs are sentinels resolved from asset
1219
+ // codegen data during `HtmlModulesPlugin` render — the asset
1220
+ // module might not have been codegen'd yet at this point.
1221
+ const outputCrossOriginAttr = crossOriginLoading
1222
+ ? ` crossorigin="${crossOriginLoading}"`
1223
+ : "";
1224
+ for (const { module, dep: md } of resourceHintResolver.getHtmlHinted(
1225
+ dep.entryName
1226
+ )) {
1227
+ const rel = md.preload ? "preload" : "prefetch";
1228
+ const asAttr =
1229
+ md.asAttribute ||
1230
+ ResourceHintRuntimeModule.guessAsAttribute(
1231
+ /** @type {import("./URLDependency")} */ (md).request || ""
1232
+ );
1233
+ const typeAttr = md.typeAttribute;
1234
+ const mediaAttr = md.mediaAttribute;
1235
+ const fetchPriority = md.fetchPriority;
1236
+ const href = HtmlGenerator.makeAssetUrlSentinel(module.identifier());
1237
+ const attrs =
1238
+ `${asAttr ? ` as="${escapeAttr(asAttr)}"` : ""}` +
1239
+ `${typeAttr ? ` type="${escapeAttr(typeAttr)}"` : ""}` +
1240
+ ` href="${escapeAttr(href)}"${
1241
+ outputCrossOriginAttr
1242
+ }${mediaAttr ? ` media="${escapeAttr(mediaAttr)}"` : ""}` +
1243
+ `${fetchPriority ? ` fetchpriority="${escapeAttr(fetchPriority)}"` : ""}`;
1244
+ hints.push(`<link rel="${rel}"${attrs}>`);
1245
+ }
1246
+
1247
+ if (hints.length > 0) {
1248
+ // De-duplicate (a custom hint may repeat an auto one), preserving order.
1249
+ /** @type {Set<string>} */
1250
+ const seen = new Set();
1251
+ /** @type {string[]} */
1252
+ const finalHints = [];
1253
+ for (const h of hints) {
1254
+ if (!seen.has(h)) {
1255
+ seen.add(h);
1256
+ finalHints.push(h);
1257
+ }
1258
+ }
1259
+ // When the target environment lacks native `modulepreload` (an
1260
+ // `output.environment` capability, like the JS-feature checks), append
1261
+ // a tiny polyfill once per page so the hints still warm the cache there.
1262
+ // `resourceHints.modulePreloadPolyfill` defaults from the target's
1263
+ // modulepreload support (`output.environment.modulePreload`); `false`
1264
+ // opts out (strict CSP).
1265
+ if (
1266
+ rhOptions &&
1267
+ rhOptions.modulePreloadPolyfill &&
1268
+ !polyfilledSources.has(source) &&
1269
+ finalHints.some((h) => h.startsWith('<link rel="modulepreload"'))
1270
+ ) {
1271
+ polyfilledSources.add(source);
1272
+ finalHints.push(`<script>${MODULEPRELOAD_POLYFILL}</script>`);
1273
+ }
1274
+ // Insert right after the `<head>` open tag (parser-derived) so the
1275
+ // hints are the document's first fetches; fall back to the first
1276
+ // blocking script (else the entry tag) when the page has no
1277
+ // written `<head>` (e.g. an HTML fragment).
1278
+ const hintAt =
1279
+ dep.headOpenEnd >= 0
1280
+ ? dep.headOpenEnd
1281
+ : dep.cssAnchor === -1
1282
+ ? dep.tagStart
1283
+ : dep.cssAnchor;
1284
+ source.insert(hintAt, finalHints.join(""));
1285
+ }
661
1286
  }
662
1287
  }
663
1288
  };