webpack 5.108.4 → 5.109.1

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 (200) hide show
  1. package/bin/webpack.js +12 -0
  2. package/lib/APIPlugin.js +48 -29
  3. package/lib/CacheFacade.js +8 -0
  4. package/lib/ChunkGraph.js +17 -15
  5. package/lib/ChunkGroup.js +1 -0
  6. package/lib/CompatibilityPlugin.js +62 -29
  7. package/lib/Compilation.js +58 -6
  8. package/lib/ConstPlugin.js +11 -11
  9. package/lib/ContextModule.js +239 -6
  10. package/lib/ContextModuleFactory.js +139 -14
  11. package/lib/DefinePlugin.js +433 -112
  12. package/lib/Entrypoint.js +1 -1
  13. package/lib/ExportsInfo.js +22 -5
  14. package/lib/ExportsInfoApiPlugin.js +2 -2
  15. package/lib/ExternalModule.js +107 -5
  16. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  17. package/lib/FileSystemInfo.js +256 -100
  18. package/lib/FlagDependencyUsagePlugin.js +33 -12
  19. package/lib/HotModuleReplacementPlugin.js +18 -15
  20. package/lib/InitFragment.js +20 -32
  21. package/lib/Module.js +3 -0
  22. package/lib/ModuleFilenameHelpers.js +2 -2
  23. package/lib/ModuleTypeConstants.js +8 -1
  24. package/lib/MultiCompiler.js +13 -2
  25. package/lib/MultiWatching.js +1 -0
  26. package/lib/NodeStuffPlugin.js +51 -33
  27. package/lib/NormalModule.js +175 -401
  28. package/lib/NormalModuleFactory.js +76 -2
  29. package/lib/ProgressPlugin.js +211 -119
  30. package/lib/ProvidePlugin.js +2 -2
  31. package/lib/RuntimeGlobals.js +47 -0
  32. package/lib/RuntimePlugin.js +67 -107
  33. package/lib/RuntimeTemplate.js +304 -16
  34. package/lib/Template.js +23 -3
  35. package/lib/UseStrictPlugin.js +1 -1
  36. package/lib/WebpackIsIncludedPlugin.js +1 -1
  37. package/lib/WebpackOptionsApply.js +24 -19
  38. package/lib/asset/AssetGenerator.js +110 -28
  39. package/lib/asset/AssetModulesPlugin.js +92 -2
  40. package/lib/asset/WebManifestGenerator.js +164 -0
  41. package/lib/asset/WebManifestParser.js +130 -0
  42. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  43. package/lib/async-modules/isGeneratorLowered.js +26 -0
  44. package/lib/cache/PackFileCacheStrategy.js +9 -3
  45. package/lib/config/browserslistTargetHandler.js +40 -0
  46. package/lib/config/defaults.js +219 -29
  47. package/lib/config/normalization.js +26 -0
  48. package/lib/config/target.js +9 -1
  49. package/lib/container/HoistContainerReferencesPlugin.js +47 -55
  50. package/lib/css/CssGenerator.js +40 -31
  51. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  52. package/lib/css/CssModulesPlugin.js +13 -0
  53. package/lib/css/CssParser.js +729 -313
  54. package/lib/css/syntax.js +1371 -861
  55. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  56. package/lib/dependencies/AMDPlugin.js +2 -2
  57. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  58. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  59. package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
  60. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  61. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  62. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  63. package/lib/dependencies/CommonJsPlugin.js +2 -2
  64. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  65. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  66. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  67. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  68. package/lib/dependencies/CssIcssExportDependency.js +162 -71
  69. package/lib/dependencies/CssUrlDependency.js +35 -4
  70. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  71. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  72. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  73. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
  74. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  75. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
  76. package/lib/dependencies/HarmonyImportDependency.js +13 -2
  77. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
  78. package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
  79. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  80. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  81. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  82. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  83. package/lib/dependencies/ImportDependency.js +43 -1
  84. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  85. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  86. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  87. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  88. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  89. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  90. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  91. package/lib/dependencies/ImportParserPlugin.js +80 -17
  92. package/lib/dependencies/ImportPhase.js +10 -8
  93. package/lib/dependencies/LoaderPlugin.js +5 -1
  94. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  95. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  96. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  97. package/lib/dependencies/SystemPlugin.js +2 -4
  98. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  99. package/lib/dependencies/URLDependency.js +141 -21
  100. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  101. package/lib/dependencies/WorkerDependency.js +86 -18
  102. package/lib/dependencies/WorkletDependency.js +278 -0
  103. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  104. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  105. package/lib/errors/LoaderLoadingError.js +20 -0
  106. package/lib/errors/ModuleParseError.js +39 -22
  107. package/lib/errors/NodeStuffInWebError.js +1 -1
  108. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  109. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  110. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  111. package/lib/hmr/lazyCompilationBackend.js +51 -12
  112. package/lib/html/HtmlGenerator.js +907 -15
  113. package/lib/html/HtmlModule.js +2 -2
  114. package/lib/html/HtmlModulesPlugin.js +610 -166
  115. package/lib/html/HtmlParser.js +603 -247
  116. package/lib/html/favicon.svg +1 -0
  117. package/lib/html/syntax.js +3451 -2762
  118. package/lib/ids/IdHelpers.js +25 -11
  119. package/lib/index.js +6 -0
  120. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  121. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  122. package/lib/javascript/JavascriptGenerator.js +197 -49
  123. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  124. package/lib/javascript/JavascriptParser.js +704 -355
  125. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  126. package/lib/javascript/syntax.js +5037 -0
  127. package/lib/library/ModuleLibraryPlugin.js +38 -1
  128. package/lib/loaders/LoaderRunner.js +714 -0
  129. package/lib/loaders/loadLoader.js +100 -0
  130. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +29 -2
  131. package/lib/node/ReadFileCompileWasmPlugin.js +24 -2
  132. package/lib/optimize/AggressiveMergingPlugin.js +19 -15
  133. package/lib/optimize/ConcatenatedModule.js +139 -31
  134. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  135. package/lib/optimize/InnerGraphPlugin.js +50 -30
  136. package/lib/optimize/ModuleConcatenationPlugin.js +139 -24
  137. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  138. package/lib/optimize/SplitChunksPlugin.js +146 -44
  139. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  140. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  141. package/lib/prefetch/ResourceHintPlugin.js +687 -0
  142. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  143. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  144. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  145. package/lib/rules/RuleSetCompiler.js +114 -0
  146. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  147. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  148. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  149. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  150. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  151. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  152. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  153. package/lib/schemes/HttpUriPlugin.js +40 -1
  154. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  155. package/lib/serialization/BinaryMiddleware.js +270 -938
  156. package/lib/serialization/FileMiddleware.js +29 -3
  157. package/lib/stats/DefaultStatsFactoryPlugin.js +229 -194
  158. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  159. package/lib/stats/DefaultStatsPrinterPlugin.js +36 -168
  160. package/lib/stats/StatsFactory.js +46 -17
  161. package/lib/url/URLParserPlugin.js +66 -13
  162. package/lib/util/AsyncQueue.js +122 -72
  163. package/lib/util/SetHelpers.js +1 -1
  164. package/lib/util/StackedMap.js +122 -48
  165. package/lib/util/binarySearchBounds.js +2 -2
  166. package/lib/util/concatenate.js +40 -14
  167. package/lib/util/createHooksRegistry.js +13 -5
  168. package/lib/util/dataURL.js +2 -2
  169. package/lib/util/deterministicGrouping.js +7 -5
  170. package/lib/util/formatSize.js +6 -2
  171. package/lib/util/globUtils.js +710 -0
  172. package/lib/util/identifier.js +23 -6
  173. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  174. package/lib/util/internalSerializables.js +88 -71
  175. package/lib/util/magicComment.js +121 -25
  176. package/lib/util/numberHash.js +2 -2
  177. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  178. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  179. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  180. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  181. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  182. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  183. package/lib/webpack.js +65 -0
  184. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  185. package/module.d.ts +21 -0
  186. package/package.json +12 -11
  187. package/schemas/WebpackOptions.check.js +1 -1
  188. package/schemas/WebpackOptions.json +759 -36
  189. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  190. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  191. package/schemas/plugins/ProgressPlugin.json +17 -1
  192. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  193. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  194. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  195. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  196. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  197. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  198. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  199. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  200. package/types.d.ts +1212 -99
@@ -5,15 +5,6 @@
5
5
  "use strict";
6
6
 
7
7
  const Parser = require("../Parser");
8
- const {
9
- TT_EOF,
10
- TT_FUNCTION,
11
- TT_STRING,
12
- TT_URL,
13
- TT_WHITESPACE,
14
- TokenStream,
15
- equalsLowerCase
16
- } = require("../css/syntax");
17
8
  const ConstDependency = require("../dependencies/ConstDependency");
18
9
  const HtmlEntryDependency = require("../dependencies/HtmlEntryDependency");
19
10
  const HtmlInlineHtmlDependency = require("../dependencies/HtmlInlineHtmlDependency");
@@ -25,6 +16,8 @@ const CommentCompilationWarning = require("../errors/CommentCompilationWarning")
25
16
  const ModuleDependencyError = require("../errors/ModuleDependencyError");
26
17
  const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
27
18
  const WebpackError = require("../errors/WebpackError");
19
+ const ResourceHintPlugin = require("../prefetch/ResourceHintPlugin");
20
+ const parseResourceHintOptions = require("../prefetch/parseResourceHintOptions");
28
21
  const LazySet = require("../util/LazySet");
29
22
  const LocConverter = require("../util/LocConverter");
30
23
  const createHash = require("../util/createHash");
@@ -40,8 +33,13 @@ const {
40
33
  NodeType,
41
34
  SVG_TAG_ADJUST,
42
35
  SourceProcessor,
43
- decodeHtmlEntities,
44
- decodeHtmlEntitiesWithMap,
36
+ decodeEntities,
37
+ escapeAttribute,
38
+ escapeText,
39
+ isAsciiWhitespace,
40
+ parseCssUrls,
41
+ parseMsapplicationTask,
42
+ parseSrc,
45
43
  parseSrcset
46
44
  } = require("./syntax");
47
45
 
@@ -69,31 +67,6 @@ const {
69
67
  */
70
68
  /** @typedef {(source: string, context: HtmlTemplateContext) => string} HtmlTemplateFunction */
71
69
 
72
- const HORIZONTAL_TAB = "\u0009".charCodeAt(0);
73
- const NEWLINE = "\u000A".charCodeAt(0);
74
- const FORM_FEED = "\u000C".charCodeAt(0);
75
- const CARRIAGE_RETURN = "\u000D".charCodeAt(0);
76
- const SPACE = "\u0020".charCodeAt(0);
77
-
78
- /**
79
- * @param {number} char char
80
- * @returns {boolean} true when ASCII whitespace, otherwise false
81
- */
82
- function isASCIIWhitespace(char) {
83
- return (
84
- // Horizontal tab
85
- char === HORIZONTAL_TAB ||
86
- // New line
87
- char === NEWLINE ||
88
- // Form feed
89
- char === FORM_FEED ||
90
- // Carriage return
91
- char === CARRIAGE_RETURN ||
92
- // Space
93
- char === SPACE
94
- );
95
- }
96
-
97
70
  /** @typedef {import("./syntax").ParsedSource} ParsedSource */
98
71
 
99
72
  // Cheap pre-filter for a `style="..."` attribute: only route it through the
@@ -116,6 +89,63 @@ const SRCDOC_ASSET_REGEXP = /[=]|url\(|@import/i;
116
89
  // base per the URL spec, so `<base href>` never rewrites it.
117
90
  const ABSOLUTE_URL_SCHEME_REGEXP = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
118
91
 
92
+ // a `charset=` declaration inside an `http-equiv="content-type"` content value
93
+ const CHARSET_DECLARATION_REGEXP = /charset\s*=/i;
94
+
95
+ /**
96
+ * @param {string} name meta name
97
+ * @param {string} content meta content
98
+ * @returns {string} meta tag
99
+ */
100
+ const metaTag = (name, content) => {
101
+ // og: uses property=; all others (including twitter:) use name=
102
+ const attr = name.startsWith("og:")
103
+ ? `property="${escapeAttribute(name)}"`
104
+ : `name="${escapeAttribute(name)}"`;
105
+ return `<meta ${attr} content="${escapeAttribute(content)}">`;
106
+ };
107
+
108
+ /**
109
+ * @param {string | { href: string, target?: string }} base base option
110
+ * @returns {string} base tag
111
+ */
112
+ const baseTag = (base) => {
113
+ const href = typeof base === "string" ? base : base.href;
114
+ const targetAttr =
115
+ typeof base === "object" && base.target
116
+ ? ` target="${escapeAttribute(base.target)}"`
117
+ : "";
118
+ return `<base href="${escapeAttribute(href)}"${targetAttr}>`;
119
+ };
120
+
121
+ /**
122
+ * Serializes every head tag an `output.html` options object asks for, in
123
+ * spec order: charset, base, meta tags, title. Used where a page is created
124
+ * from scratch (HtmlModulesPlugin's synthetic entry wrapper); authored pages
125
+ * instead merge the options against their existing tags during `parse` —
126
+ * existing tags win.
127
+ * @param {import("../../declarations/WebpackOptions").OutputHtmlOptions} opts html options
128
+ * @returns {string} head tags string
129
+ */
130
+ const buildHeadTags = (opts) => {
131
+ let out = "";
132
+ const meta = opts.meta;
133
+ if (meta && meta.charset) {
134
+ out += `<meta charset="${escapeAttribute(meta.charset)}">`;
135
+ }
136
+ if (opts.base) out += baseTag(opts.base);
137
+ if (meta) {
138
+ for (const [name, content] of Object.entries(meta)) {
139
+ if (name === "charset") continue;
140
+ out += metaTag(name, content);
141
+ }
142
+ }
143
+ if (opts.title) {
144
+ out += `<title>${escapeText(opts.title)}</title>`;
145
+ }
146
+ return out;
147
+ };
148
+
119
149
  // CSP/fetch attributes copied verbatim onto a synthesized sibling `<link>` /
120
150
  // `<script>` (`HtmlEntryDependency`). Fixed output order, independent of
121
151
  // source order.
@@ -147,124 +177,34 @@ const attrSourceSpan = (path, source, attr) => {
147
177
  return source.slice(path.attributeNameStart(attr) - 1, end);
148
178
  };
149
179
 
150
- // eslint-disable-next-line no-control-regex
151
- const IGNORE_CHARS_REGEXP = /[\u0000-\u001F\u007F-\u009F\u00A0]/g;
152
-
153
180
  /**
154
- * @param {string} input input
155
- * @returns {ParsedSource[]} parsed src
181
+ * Source span of a tag's `integrity` attribute (offsets relative to
182
+ * `elementStart`, leading whitespace and quotes included), so the template can
183
+ * drop the content-specific author value without re-parsing. Null when absent.
184
+ * @param {import("./syntax").HtmlPath} path walk path (for attribute reads)
185
+ * @param {string} source HTML source
186
+ * @param {number} elementStart offset of the tag's opening `<`
187
+ * @returns {Range | null} the span, or null when there is no `integrity`
156
188
  */
157
- const parseSrc = (input) => {
158
- const len = input.length;
159
- if (len === 0) throw new Error("Must be non-empty");
160
-
161
- let start = 0;
162
- let end = len;
163
-
164
- while (start < end) {
165
- const code = input.charCodeAt(start);
166
- if (code > 32 && code !== 160) break;
167
- start++;
168
- }
169
-
170
- if (start === end) throw new Error("Must be non-empty");
171
-
172
- while (end > start) {
173
- const code = input.charCodeAt(end - 1);
174
- if (code > 32 && code !== 160) break;
175
- end--;
176
- }
177
-
178
- let value = input.slice(start, end);
179
-
180
- if (IGNORE_CHARS_REGEXP.test(value)) {
181
- value = value.replace(IGNORE_CHARS_REGEXP, "");
182
- if (value.length === 0) throw new Error("Must be non-empty");
189
+ const captureIntegrityRange = (path, source, elementStart) => {
190
+ const integrityAttr = path.findAttribute("integrity");
191
+ if (integrityAttr === 0 || path.attributeNameStart(integrityAttr) < 0) {
192
+ return null;
183
193
  }
184
-
185
- return [[value, start, end]];
186
- };
187
-
188
- /**
189
- * Extracts the `icon-uri` value of an `msapplication-task` meta content
190
- * (`name=…;action-uri=…;icon-uri=…`) — the other parts are page URLs, not assets.
191
- * @param {string} input input
192
- * @returns {ParsedSource[]} parsed icon-uri
193
- */
194
- const parseMsapplicationTask = (input) => {
195
- const len = input.length;
196
- let pos = 0;
197
- while (pos < len) {
198
- let sep = input.indexOf(";", pos);
199
- if (sep === -1) sep = len;
200
- const eq = input.indexOf("=", pos);
201
- if (eq !== -1 && eq < sep) {
202
- const key = input.slice(pos, eq).trim().toLowerCase();
203
- if (key === "icon-uri") {
204
- let start = eq + 1;
205
- let end = sep;
206
- while (start < end && isASCIIWhitespace(input.charCodeAt(start))) {
207
- start++;
208
- }
209
- while (end > start && isASCIIWhitespace(input.charCodeAt(end - 1))) {
210
- end--;
211
- }
212
- if (start === end) return [];
213
- return [[input.slice(start, end), start, end]];
214
- }
215
- }
216
- pos = sep + 1;
194
+ let rs = path.attributeNameStart(integrityAttr);
195
+ while (rs > 0 && isAsciiWhitespace(source.charCodeAt(rs - 1))) {
196
+ rs--;
217
197
  }
218
- return [];
219
- };
220
-
221
- /**
222
- * Extracts `url(...)` references from a CSS value (an SVG presentation
223
- * attribute). Reuses webpack's CSS lexer so quoting/escaping match the CSS
224
- * spec; returns the `parseSrc` shape so the shared emit path maps and rewrites
225
- * the spans. Unquoted `url(path)` rewrites the content span; quoted
226
- * `url("path")` rewrites the inner string span (quotes preserved).
227
- * @param {string} input attribute value (a CSS component-value list)
228
- * @returns {ParsedSource[]} url references
229
- */
230
- const parseCssUrls = (input) => {
231
- const ts = new TokenStream(input);
232
- /** @type {ParsedSource[]} */
233
- const result = [];
234
- for (;;) {
235
- const t = ts.consume();
236
- if (t.type === TT_EOF) break;
237
- if (t.type === TT_URL) {
238
- if (t.contentEnd > t.contentStart) {
239
- result.push([
240
- input.slice(t.contentStart, t.contentEnd),
241
- t.contentStart,
242
- t.contentEnd
243
- ]);
244
- }
245
- } else if (
246
- t.type === TT_FUNCTION &&
247
- equalsLowerCase(input.slice(t.start, t.end - 1), "url")
248
- ) {
249
- let s = ts.consume();
250
- while (s.type === TT_WHITESPACE) s = ts.consume();
251
- if (s.type === TT_STRING) {
252
- const quote = input.charCodeAt(s.start);
253
- const innerStart = s.start + 1;
254
- // Drop the closing quote, unless the string is unterminated at EOF.
255
- const innerEnd =
256
- input.charCodeAt(s.end - 1) === quote ? s.end - 1 : s.end;
257
- if (innerEnd > innerStart) {
258
- result.push([
259
- input.slice(innerStart, innerEnd),
260
- innerStart,
261
- innerEnd
262
- ]);
263
- }
264
- }
265
- }
198
+ let re;
199
+ const ivs = path.attributeValueStart(integrityAttr);
200
+ if (ivs === -1) {
201
+ re = path.attributeNameEnd(integrityAttr);
202
+ } else {
203
+ const q = source.charCodeAt(ivs - 1);
204
+ const ive = path.attributeValueEnd(integrityAttr);
205
+ re = q === CC_QUOTATION || q === CC_APOSTROPHE ? ive + 1 : ive;
266
206
  }
267
- return result;
207
+ return [rs - elementStart, re - elementStart];
268
208
  };
269
209
 
270
210
  /**
@@ -290,6 +230,8 @@ const META = new Map([
290
230
  "msapplication-task",
291
231
  "twitter:image",
292
232
  "twitter:image:src",
233
+ // Twitter player card: URL to the raw video/media stream
234
+ "twitter:player:stream",
293
235
  // Legacy preview-image hint
294
236
  "thumbnail"
295
237
  ])
@@ -345,9 +287,7 @@ const ALLOWED_LINK_RELS = new Set([
345
287
  "apple-touch-icon",
346
288
  "apple-touch-icon-precomposed",
347
289
  "apple-touch-startup-image",
348
- // TODO the manifest file is emitted as an asset, but its JSON
349
- // `icons`/`screenshots`/`shortcuts[].icons` URLs aren't parsed — needs a
350
- // dedicated webmanifest module type (parser + generator), not a table entry.
290
+ // Routed to the `asset/webmanifest` module type, which parses its icon URLs.
351
291
  "manifest",
352
292
  "prefetch",
353
293
  "preload",
@@ -458,14 +398,17 @@ const isLinkStylesheet = (attributes) => {
458
398
  return rel.trim().toLowerCase().split(/\s+/).includes("stylesheet");
459
399
  };
460
400
 
461
- /** @typedef {"src" | "srcset" | "css-url" | "msapplication-task" | "script" | "script-module" | "modulepreload" | "stylesheet" | "stylesheet-style" | "stylesheet-style-attribute" | "srcdoc"} SourceType */
401
+ /** @typedef {"src" | "srcset" | "css-url" | "msapplication-task" | "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch" | "stylesheet-style" | "stylesheet-style-attribute" | "srcdoc"} SourceType */
462
402
  /** Entry types: a `type` whose value is loaded as its own compilation entry chunk. */
463
- /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet"} EntrySourceType */
403
+ /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} EntrySourceType */
464
404
  /** @typedef {SourceType | ((attrs: Map<string, string>, css: boolean) => SourceType)} SourceTypeOrResolver */
465
405
  /** @typedef {(attributes: Map<string, string>, value: string) => boolean} SourceFilter */
466
406
  /** @typedef {{ tag?: string, attribute: string, type: SourceType, filter?: SourceFilter }} SourceEntry */
467
407
  /** A `type` plus optional gates: `filter` (the decoded attribute map + the decoded value; return false to skip — covers both cross-attribute checks and cheap value checks) and `namespace` (restrict to an element namespace, e.g. SVG). */
468
408
  /** @typedef {{ type: SourceTypeOrResolver, filter?: SourceFilter, namespace?: number }} SourceItem */
409
+ /** A tag→attribute lookup; a `undefined` value marks a built-in source disabled via `sources` (`type: false`). */
410
+ /** @typedef {Record<string, SourceItem | undefined>} SourceBucket */
411
+ /** @typedef {Record<string, SourceBucket>} SourceTable */
469
412
  /** A source whose value is the element's text content (a `<style>`/`<script>` body) rather than an attribute. */
470
413
  /** @typedef {{ type: SourceTypeOrResolver, filter?: (attributes: Map<string, string>) => boolean }} ContentSourceItem */
471
414
 
@@ -501,6 +444,24 @@ const LINK_HREF = {
501
444
  type: (attrs, css) => {
502
445
  if (isLinkModulePreload(attrs)) return "modulepreload";
503
446
  if (css && isLinkStylesheet(attrs)) return "stylesheet";
447
+ // `rel="preload"/"prefetch"` with `as="script"` (or `as="style"` when
448
+ // `experiments.css` is on) becomes a bundled, non-executing entry whose
449
+ // `href` is rewritten to the built chunk; other `as` targets (image,
450
+ // font, fetch, …) stay plain asset URLs.
451
+ const rel = getAttributeValue(attrs, "rel");
452
+ if (rel) {
453
+ const rels = rel.trim().toLowerCase().split(/\s+/);
454
+ const kind = rels.includes("preload")
455
+ ? "preload"
456
+ : rels.includes("prefetch")
457
+ ? "prefetch"
458
+ : undefined;
459
+ if (kind) {
460
+ const as = getAttributeValue(attrs, "as");
461
+ const asLower = as ? as.trim().toLowerCase() : "";
462
+ if (asLower === "script" || (css && asLower === "style")) return kind;
463
+ }
464
+ }
504
465
  return "src";
505
466
  },
506
467
  filter: filterLinkUnion
@@ -588,15 +549,22 @@ const DEFAULT_SOURCES_BY_TAG = dict({
588
549
  // `xlink:href` reference another element/resource; fragment-only `#id`
589
550
  // values are left untouched, so only external `file.svg#id` is rewritten.
590
551
  // `color-profile`'s `xlink:href` points at an external ICC profile file.
552
+ // `cursor`/`font-face-uri` point at an external image/font file; `altGlyph`/
553
+ // `glyphRef`/`tref` are legacy element references (removed in SVG 2).
554
+ altGlyph: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
591
555
  "color-profile": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
556
+ cursor: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
592
557
  feImage: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
593
558
  filter: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
559
+ "font-face-uri": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
560
+ glyphRef: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
594
561
  image: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
595
562
  linearGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
596
563
  mpath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
597
564
  pattern: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
598
565
  radialGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
599
566
  textPath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
567
+ tref: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
600
568
  use: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC })
601
569
  // CSS `url(...)` references in SVG presentation attributes (fill, stroke,
602
570
  // clip-path, …) are handled separately during the walk (`parseCssUrls`).
@@ -657,9 +625,9 @@ const ANY_TAG = "";
657
625
  * Folds the any-tag sources into every per-tag bucket (tag-specific entries
658
626
  * win) and stores them under `ANY_TAG`, so the whole source model is one
659
627
  * object and the walk's per-attribute lookup is a single property read.
660
- * @param {Record<string, Record<string, SourceItem>>} byTag per-tag sources
661
- * @param {Record<string, SourceItem>} any any-tag sources
662
- * @returns {Record<string, Record<string, SourceItem>>} folded table
628
+ * @param {SourceTable} byTag per-tag sources
629
+ * @param {SourceBucket} any any-tag sources
630
+ * @returns {SourceTable} folded table
663
631
  */
664
632
  const foldAnySources = (byTag, any) => {
665
633
  for (const tag of Object.keys(byTag)) {
@@ -672,12 +640,12 @@ const foldAnySources = (byTag, any) => {
672
640
  // Default table (per-tag defaults + any-tag sources folded in) and the
673
641
  // `sources: false` table (only the always-on `style=""` attribute). Both are
674
642
  // precomputed so the common cases reference them with zero per-parser work.
675
- /** @type {Record<string, Record<string, SourceItem>>} */
643
+ /** @type {SourceTable} */
676
644
  const DEFAULT_SOURCES_FOLDED = foldAnySources(
677
645
  dict(DEFAULT_SOURCES_BY_TAG),
678
646
  DEFAULT_ANY_SOURCES
679
647
  );
680
- /** @type {Record<string, Record<string, SourceItem>>} */
648
+ /** @type {SourceTable} */
681
649
  const DISABLED_SOURCES_FOLDED = foldAnySources(dict(), ALWAYS_ANY_SOURCES);
682
650
 
683
651
  /**
@@ -734,9 +702,21 @@ class HtmlParser extends Parser {
734
702
  // the module source before it is stored and parsed.
735
703
  /** @type {HtmlTemplateFunction | undefined} */
736
704
  this.template = options.template;
705
+ /** @type {import("../../declarations/WebpackOptions").UrlHintRule[] | undefined} */
706
+ this._urlHints = options.urlHints;
707
+ // `as` selects the parse mode — the HTML analog of the CSS parser's `as`.
708
+ // `"document"` (default) parses a full page; any other value is the
709
+ // context element whose inner HTML the source is parsed as (a fragment),
710
+ // so context-sensitive tags (`<tr>`, `<td>`, `<option>`, …) in a partial
711
+ // are kept, not dropped. `"template"` is the neutral generic fragment.
712
+ /** @type {string | undefined} */
713
+ this.fragmentContext =
714
+ options.as === undefined || options.as === "document"
715
+ ? undefined
716
+ : options.as;
737
717
  // One source model: a per-tag table that also holds the any-tag sources
738
718
  // under `ANY_TAG`. The common cases reference precomputed tables.
739
- /** @type {Record<string, Record<string, SourceItem>>} */
719
+ /** @type {SourceTable} */
740
720
  this.sourcesByTag = DEFAULT_SOURCES_FOLDED;
741
721
 
742
722
  const sources = options.sources;
@@ -755,19 +735,25 @@ class HtmlParser extends Parser {
755
735
  // User entries override regardless of position. `dict()` keeps every
756
736
  // table null-prototype (see its doc), and per-tag writes rebuild the
757
737
  // bucket so the aliased default buckets stay intact.
758
- /** @type {Record<string, Record<string, SourceItem>>} */
738
+ /** @type {SourceTable} */
759
739
  const byTag = sources.includes("...")
760
740
  ? dict(DEFAULT_SOURCES_BY_TAG)
761
741
  : dict();
762
- /** @type {Record<string, SourceItem>} */
742
+ /** @type {SourceBucket} */
763
743
  const any = dict(DEFAULT_ANY_SOURCES);
764
744
  for (const entry of sources) {
765
745
  if (entry === "...") continue;
766
- /** @type {SourceItem} */
767
- const item = {
768
- type: entry.type,
769
- filter: typeof entry.filter === "function" ? entry.filter : undefined
770
- };
746
+ // `type: false` disables a built-in source: writing `undefined` over the
747
+ // attribute makes the walk's `sources[name]` lookup skip it (see below).
748
+ /** @type {SourceItem | undefined} */
749
+ const item =
750
+ entry.type === false
751
+ ? undefined
752
+ : {
753
+ type: entry.type,
754
+ filter:
755
+ typeof entry.filter === "function" ? entry.filter : undefined
756
+ };
771
757
  const attr = entry.attribute.toLowerCase();
772
758
  if (entry.tag === undefined) {
773
759
  any[attr] = item;
@@ -782,7 +768,7 @@ class HtmlParser extends Parser {
782
768
  }
783
769
  }
784
770
  }
785
- /** @type {Record<string, Record<string, SourceItem>>} */
771
+ /** @type {SourceTable} */
786
772
  this.sourcesByTag = foldAnySources(byTag, any);
787
773
  }
788
774
 
@@ -871,6 +857,7 @@ class HtmlParser extends Parser {
871
857
  const { hashFunction, module: outputModule } = compilation.outputOptions;
872
858
  const context = compilation.compiler.context;
873
859
  const css = Boolean(compilation.options.experiments.css);
860
+ const urlHints = this._urlHints;
874
861
 
875
862
  // Stable, per-HTML-module prefix used when generating entry names for
876
863
  // script src / modulepreload references so they don't collide across
@@ -888,6 +875,43 @@ class HtmlParser extends Parser {
888
875
  .digest("hex")
889
876
  .slice(0, 8);
890
877
 
878
+ // `output.html` head injection: facts are collected during the walk and
879
+ // become presentational dependencies afterwards — no later asset pass has
880
+ // to re-parse the page. `data:` documents are skipped: synthetic entry
881
+ // wrappers already carry the tags and iframe srcdoc pages must stay
882
+ // untouched.
883
+ const htmlOutputOptions =
884
+ compilation.options && compilation.options.output
885
+ ? compilation.options.output.html
886
+ : undefined;
887
+ const headOptions =
888
+ typeof htmlOutputOptions === "object" &&
889
+ (htmlOutputOptions.title !== undefined ||
890
+ htmlOutputOptions.meta !== undefined ||
891
+ htmlOutputOptions.base !== undefined) &&
892
+ !resource.startsWith("data:")
893
+ ? htmlOutputOptions
894
+ : undefined;
895
+ let doctypeEnd = -1;
896
+ let htmlTagEnd = -1;
897
+ // insertion offsets just inside the (possibly implicit) head
898
+ let headStart = 0;
899
+ let headLastEnd = 0;
900
+ let headSeen = false;
901
+ let inHead = false;
902
+ // `<template>` contents are inert
903
+ let headTemplateDepth = 0;
904
+ // Depth of open `<template>` elements anywhere in the tree. Template
905
+ // content is an inert, separate fragment per spec, so a `<base>` inside
906
+ // it must NOT set the document base (`headTemplateDepth` only covers
907
+ // templates seen while in `<head>`, so it can't guard body templates).
908
+ let templateDepth = 0;
909
+ let headHasTitle = false;
910
+ let headHasBase = false;
911
+ let headCharsetEnd = -1;
912
+ /** @type {Set<string> | undefined} */
913
+ let headMetaNames;
914
+
891
915
  // Script src / modulepreload references are collected per-type
892
916
  // during the walk; HtmlModulesPlugin later turns them into real
893
917
  // entries. `script` and `script-module` entries are chained via a
@@ -896,23 +920,37 @@ class HtmlParser extends Parser {
896
920
  // must preload without running, so they can never become a runtime
897
921
  // leader that other entries would import.
898
922
  /**
899
- * @typedef {object} EntryScriptInfo
923
+ * @typedef {object} HtmlEntryInfo
900
924
  * @property {string} request
901
925
  * @property {string} entryName
902
- * @property {"script" | "script-module" | "modulepreload" | "stylesheet"} type
926
+ * @property {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} type
927
+ * @property {boolean=} css entry emits a CSS chunk (`<link rel="preload" as="style">`), so it needs the CSS filename template
903
928
  */
904
- /** @type {EntryScriptInfo[]} */
929
+ /** @type {HtmlEntryInfo[]} */
905
930
  const scriptEntries = [];
906
- /** @type {EntryScriptInfo[]} */
931
+ /** @type {HtmlEntryInfo[]} */
907
932
  const scriptModuleEntries = [];
908
- /** @type {EntryScriptInfo[]} */
933
+ /** @type {HtmlEntryInfo[]} */
909
934
  const modulePreloadEntries = [];
910
- /** @type {EntryScriptInfo[]} */
935
+ /** @type {HtmlEntryInfo[]} */
911
936
  const stylesheetEntries = [];
912
-
913
- // Offset of the first script tag; anchors injected stylesheet `<link>`s
914
- // before it so a later entry's CSS still loads ahead of every script.
915
- let firstScriptStart = -1;
937
+ // `type: "html"` links — each referenced `.html` becomes its own page
938
+ // entry (`<a href="page.html">`), independent (no dependOn).
939
+ /** @type {HtmlEntryInfo[]} */
940
+ const htmlLinkEntries = [];
941
+ // `<link rel="preload"/"prefetch">` of a bundled resource: each becomes an
942
+ // independent, non-executing entry whose `href` is rewritten to the built
943
+ // chunk's URL (JS for `as="script"`, CSS for `as="style"`).
944
+ /** @type {HtmlEntryInfo[]} */
945
+ const preloadEntries = [];
946
+ /** @type {HtmlEntryInfo[]} */
947
+ const prefetchEntries = [];
948
+
949
+ // Offset of the first classic blocking script tag; injected stylesheet
950
+ // `<link>`s stay ahead of it. `defer`/`async`/module scripts don't
951
+ // anchor — they execute after parsing (and after pending stylesheets),
952
+ // so CSS may follow their tags (the order Vite emits).
953
+ let firstBlockingScriptStart = -1;
916
954
 
917
955
  let nextEntryIndex = 0;
918
956
 
@@ -940,6 +978,14 @@ class HtmlParser extends Parser {
940
978
  * @type {boolean | undefined}
941
979
  */
942
980
  let pendingWebpackIgnore;
981
+ /** @type {boolean | undefined} */
982
+ let pendingWebpackInline;
983
+ /**
984
+ * Tracks the resource-hint values from the most recent `<!-- webpackPrefetch: … -->`
985
+ * (etc.) comment so the next asset URL attribute picks them up.
986
+ * @type {import("../prefetch/parseResourceHintOptions").ResourceHintOptions | undefined}
987
+ */
988
+ let pendingHints;
943
989
 
944
990
  const magicCommentContext = this.magicCommentContext;
945
991
 
@@ -979,7 +1025,7 @@ class HtmlParser extends Parser {
979
1025
  let attrStart = path.attributeNameStart(typeAttr);
980
1026
  if (
981
1027
  attrStart > 0 &&
982
- isASCIIWhitespace(input.charCodeAt(attrStart - 1))
1028
+ isAsciiWhitespace(input.charCodeAt(attrStart - 1))
983
1029
  ) {
984
1030
  attrStart -= 1;
985
1031
  }
@@ -1016,10 +1062,7 @@ class HtmlParser extends Parser {
1016
1062
  * @param {import("./syntax").HtmlAttributeRef} hrefAttr the base's href attribute
1017
1063
  */
1018
1064
  const resolveDocumentBase = (path, hrefAttr) => {
1019
- documentBase = decodeHtmlEntities(
1020
- path.attributeValue(hrefAttr),
1021
- true
1022
- ).trim();
1065
+ documentBase = decodeEntities(path.attributeValue(hrefAttr), true).trim();
1023
1066
  if (!documentBase) return;
1024
1067
  if (
1025
1068
  ABSOLUTE_URL_SCHEME_REGEXP.test(documentBase) ||
@@ -1074,7 +1117,7 @@ class HtmlParser extends Parser {
1074
1117
  // the browser sees (e.g. `rel="&#105;con"` means `icon`)
1075
1118
  currentAttributesMap.set(
1076
1119
  path.attributeName(attr),
1077
- decodeHtmlEntities(path.attributeValue(attr), true)
1120
+ decodeEntities(path.attributeValue(attr), true)
1078
1121
  );
1079
1122
  }
1080
1123
  return currentAttributesMap;
@@ -1089,6 +1132,10 @@ class HtmlParser extends Parser {
1089
1132
  [NodeType.Comment]: (path) => {
1090
1133
  const start = path.start();
1091
1134
  const end = path.end();
1135
+ // comments count toward the head-end insertion anchor
1136
+ if (inHead && headTemplateDepth === 0 && end > headLastEnd) {
1137
+ headLastEnd = end;
1138
+ }
1092
1139
  // Only proper `<!-- ... -->` comments carry magic comments.
1093
1140
  if (
1094
1141
  end - start < 7 ||
@@ -1101,11 +1148,15 @@ class HtmlParser extends Parser {
1101
1148
  source.charCodeAt(end - 3) !== 0x2d
1102
1149
  ) {
1103
1150
  pendingWebpackIgnore = undefined;
1151
+ pendingWebpackInline = undefined;
1152
+ pendingHints = undefined;
1104
1153
  return;
1105
1154
  }
1106
1155
  const value = path.data();
1107
1156
  if (!webpackCommentRegExp.test(value)) {
1108
1157
  pendingWebpackIgnore = undefined;
1158
+ pendingWebpackInline = undefined;
1159
+ pendingHints = undefined;
1109
1160
  return;
1110
1161
  }
1111
1162
  /** @type {Record<string, EXPECTED_ANY>} */
@@ -1127,13 +1178,26 @@ class HtmlParser extends Parser {
1127
1178
  )
1128
1179
  );
1129
1180
  pendingWebpackIgnore = undefined;
1181
+ pendingWebpackInline = undefined;
1182
+ pendingHints = undefined;
1130
1183
  return;
1131
1184
  }
1132
- if (options.webpackIgnore === undefined) {
1185
+ if (
1186
+ options.webpackIgnore === undefined &&
1187
+ options.webpackInline === undefined &&
1188
+ options.webpackPrefetch === undefined &&
1189
+ options.webpackPreload === undefined &&
1190
+ options.webpackFetchPriority === undefined
1191
+ ) {
1133
1192
  pendingWebpackIgnore = undefined;
1193
+ pendingWebpackInline = undefined;
1194
+ pendingHints = undefined;
1134
1195
  return;
1135
1196
  }
1136
- if (typeof options.webpackIgnore !== "boolean") {
1197
+ if (
1198
+ options.webpackIgnore !== undefined &&
1199
+ typeof options.webpackIgnore !== "boolean"
1200
+ ) {
1137
1201
  const { line: sl, column: sc } = locConverter.get(start);
1138
1202
  const { line: el, column: ec } = locConverter.get(end);
1139
1203
  module.addWarning(
@@ -1146,12 +1210,59 @@ class HtmlParser extends Parser {
1146
1210
  )
1147
1211
  );
1148
1212
  pendingWebpackIgnore = undefined;
1213
+ pendingWebpackInline = undefined;
1214
+ pendingHints = undefined;
1215
+ return;
1216
+ }
1217
+ if (
1218
+ options.webpackInline !== undefined &&
1219
+ typeof options.webpackInline !== "boolean"
1220
+ ) {
1221
+ const { line: sl, column: sc } = locConverter.get(start);
1222
+ const { line: el, column: ec } = locConverter.get(end);
1223
+ module.addWarning(
1224
+ new UnsupportedFeatureWarning(
1225
+ `\`webpackInline\` expected a boolean, but received: ${options.webpackInline}.`,
1226
+ {
1227
+ start: { line: sl, column: sc },
1228
+ end: { line: el, column: ec }
1229
+ }
1230
+ )
1231
+ );
1232
+ pendingWebpackIgnore = undefined;
1233
+ pendingWebpackInline = undefined;
1234
+ pendingHints = undefined;
1149
1235
  return;
1150
1236
  }
1151
1237
  pendingWebpackIgnore = options.webpackIgnore;
1238
+ pendingWebpackInline = options.webpackInline;
1239
+ if (
1240
+ options.webpackPrefetch !== undefined ||
1241
+ options.webpackPreload !== undefined ||
1242
+ options.webpackFetchPriority !== undefined
1243
+ ) {
1244
+ const s = locConverter.get(start);
1245
+ const e = locConverter.get(end);
1246
+ const parsedHints = parseResourceHintOptions(options, module, {
1247
+ start: { line: s.line, column: s.column },
1248
+ end: { line: e.line, column: e.column }
1249
+ });
1250
+ pendingHints =
1251
+ parsedHints.prefetch !== undefined ||
1252
+ parsedHints.preload !== undefined ||
1253
+ parsedHints.fetchPriority !== undefined
1254
+ ? parsedHints
1255
+ : undefined;
1256
+ } else {
1257
+ pendingHints = undefined;
1258
+ }
1152
1259
  },
1153
- [NodeType.Doctype]: () => {
1260
+ [NodeType.Doctype]: (path) => {
1261
+ // only visited when head injection is active (see `skip` below)
1262
+ if (doctypeEnd === -1) doctypeEnd = path.end();
1154
1263
  pendingWebpackIgnore = undefined;
1264
+ pendingWebpackInline = undefined;
1265
+ pendingHints = undefined;
1155
1266
  },
1156
1267
  [NodeType.Element]: {
1157
1268
  // Children (and `<template>` content) are walked by the
@@ -1159,7 +1270,115 @@ class HtmlParser extends Parser {
1159
1270
  // `webpackIgnore` once they're done.
1160
1271
  enter: (path) => {
1161
1272
  const ignore = pendingWebpackIgnore === true;
1273
+ const forceInline = pendingWebpackInline === true;
1162
1274
  pendingWebpackIgnore = undefined;
1275
+ pendingWebpackInline = undefined;
1276
+
1277
+ // `<head>` anchors are collected for every page — they place
1278
+ // `inject: "head"` siblings and resource hints; the
1279
+ // title/meta/base facts only when configured. Structural, so it
1280
+ // runs even for `webpackIgnore`d elements.
1281
+ {
1282
+ const name = path.tagName();
1283
+ if (name === "template" && path.namespace() === NS_HTML) {
1284
+ templateDepth++;
1285
+ }
1286
+ if (headTemplateDepth > 0) {
1287
+ if (name === "template" && path.namespace() === NS_HTML) {
1288
+ headTemplateDepth++;
1289
+ }
1290
+ } else if (inHead) {
1291
+ if (headOptions !== undefined && path.namespace() === NS_HTML) {
1292
+ switch (name) {
1293
+ case "title":
1294
+ headHasTitle = true;
1295
+ break;
1296
+ case "base":
1297
+ headHasBase = true;
1298
+ break;
1299
+ case "meta": {
1300
+ if (path.findAttribute("charset") !== 0) {
1301
+ headCharsetEnd = path.tagEnd();
1302
+ } else {
1303
+ const httpEquiv = path.findAttribute("http-equiv");
1304
+ const contentAttr =
1305
+ httpEquiv !== 0 &&
1306
+ decodeEntities(
1307
+ path.attributeValue(httpEquiv),
1308
+ true
1309
+ ).toLowerCase() === "content-type"
1310
+ ? path.findAttribute("content")
1311
+ : 0;
1312
+ if (
1313
+ contentAttr !== 0 &&
1314
+ CHARSET_DECLARATION_REGEXP.test(
1315
+ decodeEntities(
1316
+ path.attributeValue(contentAttr),
1317
+ true
1318
+ )
1319
+ )
1320
+ ) {
1321
+ headCharsetEnd = path.tagEnd();
1322
+ }
1323
+ }
1324
+ const nameAttr = path.findAttribute("name");
1325
+ const propertyAttr = path.findAttribute("property");
1326
+ if (nameAttr !== 0 || propertyAttr !== 0) {
1327
+ if (headMetaNames === undefined) {
1328
+ headMetaNames = new Set();
1329
+ }
1330
+ if (nameAttr !== 0) {
1331
+ headMetaNames.add(
1332
+ decodeEntities(
1333
+ path.attributeValue(nameAttr),
1334
+ true
1335
+ ).toLowerCase()
1336
+ );
1337
+ }
1338
+ if (propertyAttr !== 0) {
1339
+ headMetaNames.add(
1340
+ decodeEntities(
1341
+ path.attributeValue(propertyAttr),
1342
+ true
1343
+ ).toLowerCase()
1344
+ );
1345
+ }
1346
+ }
1347
+ break;
1348
+ }
1349
+ case "template":
1350
+ headTemplateDepth++;
1351
+ break;
1352
+ }
1353
+ } else if (
1354
+ name === "template" &&
1355
+ path.namespace() === NS_HTML
1356
+ ) {
1357
+ headTemplateDepth++;
1358
+ }
1359
+ if (path.end() > headLastEnd) headLastEnd = path.end();
1360
+ } else if (!headSeen && path.namespace() === NS_HTML) {
1361
+ if (name === "html") {
1362
+ htmlTagEnd = path.tagEnd();
1363
+ } else if (name === "head") {
1364
+ headSeen = true;
1365
+ inHead = true;
1366
+ // explicit head has real offsets; an implicit one (head
1367
+ // tags are optional in HTML5) anchors after `<html>`,
1368
+ // the doctype, or the document start
1369
+ const tagEnd = path.tagEnd();
1370
+ headStart =
1371
+ tagEnd > 0
1372
+ ? tagEnd
1373
+ : htmlTagEnd > 0
1374
+ ? htmlTagEnd
1375
+ : doctypeEnd > 0
1376
+ ? doctypeEnd
1377
+ : 0;
1378
+ headLastEnd = headStart;
1379
+ }
1380
+ }
1381
+ }
1163
1382
 
1164
1383
  if (ignore) {
1165
1384
  return;
@@ -1171,9 +1390,12 @@ class HtmlParser extends Parser {
1171
1390
  const tagEnd = path.tagEnd();
1172
1391
  const nameEnd = path.nameEnd();
1173
1392
 
1174
- // The first `<base href>` freezes the base for later URLs.
1393
+ // The first `<base href>` freezes the base for later URLs — but
1394
+ // only one in the real document tree, not inside an inert
1395
+ // `<template>` (whose contents are a separate fragment).
1175
1396
  if (
1176
1397
  documentBase === undefined &&
1398
+ templateDepth === 0 &&
1177
1399
  elementName === "base" &&
1178
1400
  path.namespace() === NS_HTML
1179
1401
  ) {
@@ -1246,7 +1468,7 @@ class HtmlParser extends Parser {
1246
1468
  // The filter also sees the decoded value (the browser tokenizes it
1247
1469
  // decoded, e.g. `&#117;rl(`), so a value-only check needn't re-read it.
1248
1470
  const decoded = value.includes("&")
1249
- ? decodeHtmlEntities(value, true)
1471
+ ? decodeEntities(value, true)
1250
1472
  : value;
1251
1473
  if (!filter(getAttributesMap(), decoded)) continue;
1252
1474
  }
@@ -1266,9 +1488,7 @@ class HtmlParser extends Parser {
1266
1488
  case "stylesheet-style":
1267
1489
  case "stylesheet-style-attribute": {
1268
1490
  if (!css) break;
1269
- const cssText = content
1270
- ? value
1271
- : decodeHtmlEntities(value, true);
1491
+ const cssText = content ? value : decodeEntities(value, true);
1272
1492
  if (cssText.trim() === "") break;
1273
1493
  const dep = new HtmlInlineStyleDependency(
1274
1494
  dataUri("text/css", cssText),
@@ -1287,7 +1507,7 @@ class HtmlParser extends Parser {
1287
1507
  // whose URL is `about:srcdoc`, so its base URL is inherited from
1288
1508
  // this document — asset URLs resolve against this file's context.
1289
1509
  case "srcdoc": {
1290
- const htmlText = decodeHtmlEntities(value, true);
1510
+ const htmlText = decodeEntities(value, true);
1291
1511
  // Only spin up a nested module when there is markup that can
1292
1512
  // reference an asset; text/formatting-only markup is identical
1293
1513
  // after rewriting (see `SRCDOC_ASSET_REGEXP`).
@@ -1346,7 +1566,10 @@ class HtmlParser extends Parser {
1346
1566
  case "css-url":
1347
1567
  case "msapplication-task":
1348
1568
  case "modulepreload":
1349
- case "stylesheet": {
1569
+ case "stylesheet":
1570
+ case "html":
1571
+ case "preload":
1572
+ case "prefetch": {
1350
1573
  // Parse the value into one or more URLs (decoding character
1351
1574
  // references, mapping spans back to raw offsets), then emit a plain
1352
1575
  // asset reference or an entry chunk per URL.
@@ -1362,7 +1585,7 @@ class HtmlParser extends Parser {
1362
1585
  /** @type {number[] | undefined} */
1363
1586
  let map;
1364
1587
  if (value.includes("&")) {
1365
- ({ text, map } = decodeHtmlEntitiesWithMap(value, true));
1588
+ ({ text, map } = decodeEntities(value, true, true));
1366
1589
  if (!/\S/.test(text)) break;
1367
1590
  }
1368
1591
  /** @type {ParsedSource[] | undefined} */
@@ -1408,6 +1631,70 @@ class HtmlParser extends Parser {
1408
1631
  }
1409
1632
  const s = start + (map ? map[us] : us);
1410
1633
  const e = start + (map ? map[ue] : ue);
1634
+ // `html` links the URL as its own emitted page: the same
1635
+ // `HtmlEntryDependency` as `<script src>` / `<link>`, but the `html`
1636
+ // kind rewrites to the page's filename and emits no sibling tags.
1637
+ // Recorded as an independent entry (no `dependOn`), like an entry point.
1638
+ if (type === "html") {
1639
+ const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1640
+ const dep = new HtmlEntryDependency(
1641
+ request,
1642
+ [s, e],
1643
+ entryName,
1644
+ undefined,
1645
+ "html"
1646
+ );
1647
+ setLoc(dep, s, e);
1648
+ module.addPresentationalDependency(dep);
1649
+ htmlLinkEntries.push({ request, entryName, type });
1650
+ continue;
1651
+ }
1652
+ // `rel="preload"/"prefetch"` of a bundled resource: an
1653
+ // independent, non-executing entry whose `href` is rewritten to
1654
+ // the built chunk's URL (CSS for `as="style"`, JS otherwise). No
1655
+ // sibling tags — a resource hint references exactly one URL.
1656
+ if (type === "preload" || type === "prefetch") {
1657
+ const as = getAttributeValue(getAttributesMap(), "as");
1658
+ const asStyle = as
1659
+ ? as.trim().toLowerCase() === "style"
1660
+ : false;
1661
+ const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1662
+ // Tag metadata so the template can mirror `crossorigin`/
1663
+ // `integrity` onto a native `preload` `<link>` (integrity-
1664
+ // eligible per the SRI spec). `prefetch` gets none — the spec
1665
+ // doesn't list it — so its `integrity` is left uncaptured.
1666
+ const isNativeLink = elementName === "link";
1667
+ const dep = new HtmlEntryDependency(
1668
+ request,
1669
+ [s, e],
1670
+ entryName,
1671
+ asStyle ? "css-import" : undefined,
1672
+ type,
1673
+ elementStart,
1674
+ tagEnd,
1675
+ isNativeLink,
1676
+ "",
1677
+ nameEnd,
1678
+ isNativeLink && path.findAttribute("crossorigin") !== 0,
1679
+ -1,
1680
+ type === "preload" && isNativeLink
1681
+ ? captureIntegrityRange(path, source, elementStart)
1682
+ : null,
1683
+ null
1684
+ );
1685
+ setLoc(dep, s, e);
1686
+ module.addPresentationalDependency(dep);
1687
+ (type === "preload"
1688
+ ? preloadEntries
1689
+ : prefetchEntries
1690
+ ).push({
1691
+ request,
1692
+ entryName,
1693
+ type,
1694
+ css: asStyle
1695
+ });
1696
+ continue;
1697
+ }
1411
1698
  // `src`/`srcset`/`css-url`/`msapplication-task` are plain assets;
1412
1699
  // the rest are narrowed to entry types here and share one
1413
1700
  // `HtmlEntryDependency`, recorded for HtmlModulesPlugin.
@@ -1461,63 +1748,49 @@ class HtmlParser extends Parser {
1461
1748
  }
1462
1749
  }
1463
1750
  }
1751
+ // Blocking-ness of the tag as *emitted*:
1752
+ // `reconcileScriptTypeAttr` upgrades every script entry
1753
+ // to `type="module"` under `output.module` and strips an
1754
+ // authored `type="module"` otherwise — so only non-module
1755
+ // output without `defer`/`async` blocks. Non-native
1756
+ // elements anchor conservatively (their synthesized
1757
+ // siblings may be plain blocking `<script>`s).
1464
1758
  if (
1465
- firstScriptStart === -1 &&
1466
- (type === "script" || type === "script-module")
1759
+ firstBlockingScriptStart === -1 &&
1760
+ (type === "script" || type === "script-module") &&
1761
+ !outputModule &&
1762
+ (elementName !== nativeTag ||
1763
+ (path.findAttribute("defer") === 0 &&
1764
+ path.findAttribute("async") === 0))
1467
1765
  ) {
1468
- firstScriptStart = elementStart;
1766
+ firstBlockingScriptStart = elementStart;
1469
1767
  }
1470
1768
  // Sibling-clone attribute edits, captured from the parsed
1471
1769
  // attributes (offsets relative to the tag) so the template
1472
1770
  // needn't re-parse the tag text: `integrity` is dropped
1473
1771
  // (content-specific) and `script-module` clones are forced
1474
1772
  // to `type="module"` (its value range, else inserted).
1475
- /** @type {Range | null} */
1476
- let integrityRange = null;
1773
+ const integrityRange =
1774
+ elementStart >= 0 && elementName === nativeTag
1775
+ ? captureIntegrityRange(path, source, elementStart)
1776
+ : null;
1477
1777
  /** @type {Range | null} */
1478
1778
  let typeValueRange = null;
1479
- if (elementStart >= 0 && elementName === nativeTag) {
1480
- const integrityAttr = path.findAttribute("integrity");
1779
+ if (
1780
+ elementStart >= 0 &&
1781
+ elementName === nativeTag &&
1782
+ elementKind === "script-module"
1783
+ ) {
1784
+ const typeAttr = path.findAttribute("type");
1481
1785
  if (
1482
- integrityAttr !== 0 &&
1483
- path.attributeNameStart(integrityAttr) >= 0
1786
+ typeAttr !== 0 &&
1787
+ path.attributeValueStart(typeAttr) >= 0
1484
1788
  ) {
1485
- let rs = path.attributeNameStart(integrityAttr);
1486
- while (
1487
- rs > 0 &&
1488
- isASCIIWhitespace(source.charCodeAt(rs - 1))
1489
- ) {
1490
- rs--;
1491
- }
1492
- let re;
1493
- const ivs = path.attributeValueStart(integrityAttr);
1494
- if (ivs === -1) {
1495
- re = path.attributeNameEnd(integrityAttr);
1496
- } else {
1497
- const q = source.charCodeAt(ivs - 1);
1498
- const ive = path.attributeValueEnd(integrityAttr);
1499
- re =
1500
- q === CC_QUOTATION || q === CC_APOSTROPHE
1501
- ? ive + 1
1502
- : ive;
1503
- }
1504
- integrityRange = [
1505
- rs - elementStart,
1506
- re - elementStart
1789
+ typeValueRange = [
1790
+ path.attributeValueStart(typeAttr) - elementStart,
1791
+ path.attributeValueEnd(typeAttr) - elementStart
1507
1792
  ];
1508
1793
  }
1509
- if (elementKind === "script-module") {
1510
- const typeAttr = path.findAttribute("type");
1511
- if (
1512
- typeAttr !== 0 &&
1513
- path.attributeValueStart(typeAttr) >= 0
1514
- ) {
1515
- typeValueRange = [
1516
- path.attributeValueStart(typeAttr) - elementStart,
1517
- path.attributeValueEnd(typeAttr) - elementStart
1518
- ];
1519
- }
1520
- }
1521
1794
  }
1522
1795
  const dep = new HtmlEntryDependency(
1523
1796
  request,
@@ -1531,9 +1804,12 @@ class HtmlParser extends Parser {
1531
1804
  copyableAttrsText,
1532
1805
  nameEnd,
1533
1806
  hasOwnCrossOrigin,
1534
- firstScriptStart,
1807
+ firstBlockingScriptStart,
1535
1808
  integrityRange,
1536
- typeValueRange
1809
+ typeValueRange,
1810
+ forceInline,
1811
+ headSeen && headStart > 0 ? headStart : -1,
1812
+ headSeen && headLastEnd > 0 ? headLastEnd : -1
1537
1813
  );
1538
1814
  setLoc(dep, s, e);
1539
1815
  module.addPresentationalDependency(dep);
@@ -1559,6 +1835,16 @@ class HtmlParser extends Parser {
1559
1835
  ).push({ request, entryName, type });
1560
1836
  } else {
1561
1837
  const dep = new HtmlSourceDependency(request, [s, e]);
1838
+ ResourceHintPlugin.applyDefaults(
1839
+ dep,
1840
+ ResourceHintPlugin.matchUrlHints(urlHints, request)
1841
+ );
1842
+ // `<!-- webpackPrefetch: true -->` (etc.) immediately
1843
+ // before this tag wins over the project-wide default.
1844
+ if (pendingHints) {
1845
+ ResourceHintPlugin.applyParsedHints(dep, pendingHints);
1846
+ pendingHints = undefined;
1847
+ }
1562
1848
  setLoc(dep, s, e);
1563
1849
  module.addDependency(dep);
1564
1850
  module.addCodeGenerationDependency(dep);
@@ -1569,15 +1855,76 @@ class HtmlParser extends Parser {
1569
1855
  }
1570
1856
  }
1571
1857
  },
1572
- exit: () => {
1858
+ exit: (path) => {
1859
+ if (path.namespace() === NS_HTML) {
1860
+ const name = path.tagName();
1861
+ if (name === "template") {
1862
+ if (templateDepth > 0) templateDepth--;
1863
+ if (headTemplateDepth > 0) headTemplateDepth--;
1864
+ } else if (name === "head") {
1865
+ inHead = false;
1866
+ }
1867
+ }
1573
1868
  pendingWebpackIgnore = undefined;
1869
+ pendingWebpackInline = undefined;
1870
+ pendingHints = undefined;
1574
1871
  }
1575
1872
  }
1576
1873
  })
1577
1874
  // Skip AST output this walk never reads: `text` (script/style bodies are
1578
- // read by offset via `contentEnd`) and `doctype`; keep `comments` for
1875
+ // read by offset via `contentEnd`) and `doctype` — unless head injection
1876
+ // needs the doctype as an insertion anchor; keep `comments` for
1579
1877
  // magic comments (`webpackIgnore`).
1580
- .process(source, { skip: { text: true, doctype: true } });
1878
+ .process(source, {
1879
+ fragmentContext: this.fragmentContext,
1880
+ skip: { text: true, doctype: headOptions === undefined }
1881
+ });
1882
+
1883
+ // `output.html` head injection: existing tags win, injected values are
1884
+ // escaped, insertions at the same offset are merged to keep their order.
1885
+ if (headOptions !== undefined && headSeen) {
1886
+ const { title, meta, base } = headOptions;
1887
+ let startTags = "";
1888
+ if (meta && meta.charset && headCharsetEnd === -1) {
1889
+ startTags += `<meta charset="${escapeAttribute(meta.charset)}">`;
1890
+ }
1891
+ // <base> must follow the charset declaration per spec
1892
+ let baseAfterCharset = "";
1893
+ if (base && !headHasBase) {
1894
+ if (headCharsetEnd !== -1) baseAfterCharset = baseTag(base);
1895
+ else startTags += baseTag(base);
1896
+ }
1897
+ let endTags = "";
1898
+ if (meta) {
1899
+ for (const [name, value] of Object.entries(meta)) {
1900
+ if (
1901
+ name === "charset" ||
1902
+ (headMetaNames !== undefined &&
1903
+ headMetaNames.has(name.toLowerCase()))
1904
+ ) {
1905
+ continue;
1906
+ }
1907
+ endTags += metaTag(name, value);
1908
+ }
1909
+ }
1910
+ if (title && !headHasTitle) {
1911
+ endTags += `<title>${escapeText(title)}</title>`;
1912
+ }
1913
+ /** @type {[number, string][]} */
1914
+ const inserts = [];
1915
+ if (startTags) inserts.push([headStart, startTags]);
1916
+ if (baseAfterCharset) inserts.push([headCharsetEnd, baseAfterCharset]);
1917
+ if (endTags) inserts.push([headLastEnd, endTags]);
1918
+ inserts.sort((a, b) => a[0] - b[0]);
1919
+ for (let i = 0; i < inserts.length; i++) {
1920
+ const at = inserts[i][0];
1921
+ let tags = inserts[i][1];
1922
+ while (i + 1 < inserts.length && inserts[i + 1][0] === at) {
1923
+ tags += inserts[++i][1];
1924
+ }
1925
+ module.addPresentationalDependency(new ConstDependency(tags, at));
1926
+ }
1927
+ }
1581
1928
 
1582
1929
  const buildInfo = /** @type {HtmlModuleBuildInfo} */ (module.buildInfo);
1583
1930
  buildInfo.strict = true;
@@ -1587,18 +1934,25 @@ class HtmlParser extends Parser {
1587
1934
  // and `script-module` groups are chained via a leader-only dependOn
1588
1935
  // so they share a runtime; `modulepreload` and `stylesheet` entries
1589
1936
  // are emitted as independent entries since `<link rel=modulepreload>`
1590
- // must preload without running and stylesheets have no runtime.
1937
+ // must preload without running and stylesheets have no runtime. `html`
1938
+ // links are independent page entries too.
1591
1939
  if (
1592
1940
  scriptEntries.length > 0 ||
1593
1941
  scriptModuleEntries.length > 0 ||
1594
1942
  modulePreloadEntries.length > 0 ||
1595
- stylesheetEntries.length > 0
1943
+ stylesheetEntries.length > 0 ||
1944
+ htmlLinkEntries.length > 0 ||
1945
+ preloadEntries.length > 0 ||
1946
+ prefetchEntries.length > 0
1596
1947
  ) {
1597
- buildInfo.htmlEntryScripts = {
1948
+ buildInfo.htmlEntries = {
1598
1949
  script: scriptEntries,
1599
1950
  "script-module": scriptModuleEntries,
1600
1951
  modulepreload: modulePreloadEntries,
1601
- stylesheet: stylesheetEntries
1952
+ stylesheet: stylesheetEntries,
1953
+ html: htmlLinkEntries,
1954
+ preload: preloadEntries,
1955
+ prefetch: prefetchEntries
1602
1956
  };
1603
1957
  }
1604
1958
 
@@ -1611,4 +1965,6 @@ class HtmlParser extends Parser {
1611
1965
  }
1612
1966
  }
1613
1967
 
1968
+ HtmlParser.buildHeadTags = buildHeadTags;
1969
+
1614
1970
  module.exports = HtmlParser;