webpack 5.108.3 → 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 (191) 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 +50 -8
  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/FlagDependencyExportsPlugin.js +55 -8
  16. package/lib/HotModuleReplacementPlugin.js +18 -15
  17. package/lib/LazyBarrel.js +30 -3
  18. package/lib/Module.js +3 -0
  19. package/lib/ModuleFilenameHelpers.js +2 -2
  20. package/lib/ModuleTypeConstants.js +8 -1
  21. package/lib/MultiCompiler.js +13 -2
  22. package/lib/MultiWatching.js +1 -0
  23. package/lib/NodeStuffPlugin.js +51 -33
  24. package/lib/NormalModule.js +169 -405
  25. package/lib/NormalModuleFactory.js +76 -2
  26. package/lib/ProgressPlugin.js +211 -119
  27. package/lib/ProvidePlugin.js +2 -2
  28. package/lib/RuntimeGlobals.js +47 -0
  29. package/lib/RuntimePlugin.js +33 -0
  30. package/lib/RuntimeTemplate.js +304 -16
  31. package/lib/UseStrictPlugin.js +1 -1
  32. package/lib/WebpackIsIncludedPlugin.js +1 -1
  33. package/lib/WebpackOptionsApply.js +24 -19
  34. package/lib/asset/AssetGenerator.js +110 -28
  35. package/lib/asset/AssetModulesPlugin.js +92 -2
  36. package/lib/asset/WebManifestGenerator.js +164 -0
  37. package/lib/asset/WebManifestParser.js +130 -0
  38. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  39. package/lib/async-modules/isGeneratorLowered.js +26 -0
  40. package/lib/cache/PackFileCacheStrategy.js +9 -3
  41. package/lib/config/browserslistTargetHandler.js +40 -0
  42. package/lib/config/defaults.js +222 -32
  43. package/lib/config/normalization.js +26 -0
  44. package/lib/config/target.js +9 -1
  45. package/lib/css/CssGenerator.js +73 -73
  46. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  47. package/lib/css/CssModulesPlugin.js +92 -53
  48. package/lib/css/CssParser.js +843 -302
  49. package/lib/css/syntax.js +1219 -483
  50. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  51. package/lib/dependencies/AMDPlugin.js +2 -2
  52. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  53. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  54. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  55. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  56. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  57. package/lib/dependencies/CommonJsPlugin.js +2 -2
  58. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  59. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  60. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  61. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  62. package/lib/dependencies/CssIcssExportDependency.js +190 -87
  63. package/lib/dependencies/CssUrlDependency.js +35 -4
  64. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  65. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  66. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  67. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  68. package/lib/dependencies/HarmonyImportDependency.js +12 -1
  69. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +34 -107
  70. package/lib/dependencies/HarmonyImportGuard.js +192 -20
  71. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  72. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  73. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  74. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  75. package/lib/dependencies/ImportDependency.js +43 -1
  76. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  77. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  78. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  79. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  80. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  81. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  82. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  83. package/lib/dependencies/ImportParserPlugin.js +87 -26
  84. package/lib/dependencies/ImportPhase.js +10 -8
  85. package/lib/dependencies/LoaderPlugin.js +5 -1
  86. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  87. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  88. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  89. package/lib/dependencies/SystemPlugin.js +2 -4
  90. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  91. package/lib/dependencies/URLDependency.js +141 -21
  92. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  93. package/lib/dependencies/WorkerDependency.js +86 -18
  94. package/lib/dependencies/WorkletDependency.js +278 -0
  95. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  96. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  97. package/lib/errors/LoaderLoadingError.js +20 -0
  98. package/lib/errors/ModuleParseError.js +39 -22
  99. package/lib/errors/NodeStuffInWebError.js +1 -1
  100. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  101. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  102. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  103. package/lib/hmr/lazyCompilationBackend.js +32 -8
  104. package/lib/html/HtmlGenerator.js +940 -91
  105. package/lib/html/HtmlModule.js +3 -2
  106. package/lib/html/HtmlModulesPlugin.js +625 -170
  107. package/lib/html/HtmlParser.js +812 -340
  108. package/lib/html/favicon.svg +1 -0
  109. package/lib/html/syntax.js +2116 -834
  110. package/lib/ids/IdHelpers.js +1 -1
  111. package/lib/index.js +6 -0
  112. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  113. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  114. package/lib/javascript/JavascriptGenerator.js +197 -49
  115. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  116. package/lib/javascript/JavascriptParser.js +711 -355
  117. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  118. package/lib/javascript/syntax.js +4380 -0
  119. package/lib/library/ModuleLibraryPlugin.js +38 -1
  120. package/lib/loaders/LoaderRunner.js +714 -0
  121. package/lib/loaders/loadLoader.js +100 -0
  122. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
  123. package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
  124. package/lib/optimize/ConcatenatedModule.js +120 -16
  125. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  126. package/lib/optimize/InnerGraphPlugin.js +50 -30
  127. package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
  128. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  129. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  130. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  131. package/lib/prefetch/ResourceHintPlugin.js +663 -0
  132. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  133. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  134. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  135. package/lib/rules/RuleSetCompiler.js +114 -0
  136. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  137. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  138. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  139. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  140. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  141. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  142. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  143. package/lib/schemes/HttpUriPlugin.js +40 -1
  144. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  145. package/lib/serialization/FileMiddleware.js +29 -3
  146. package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
  147. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  148. package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
  149. package/lib/stats/StatsFactory.js +15 -8
  150. package/lib/url/URLParserPlugin.js +98 -19
  151. package/lib/util/AsyncQueue.js +122 -72
  152. package/lib/util/LocConverter.js +9 -8
  153. package/lib/util/SetHelpers.js +1 -1
  154. package/lib/util/SourceProcessor.js +41 -28
  155. package/lib/util/StackedMap.js +122 -48
  156. package/lib/util/binarySearchBounds.js +2 -2
  157. package/lib/util/concatenate.js +40 -14
  158. package/lib/util/createHooksRegistry.js +13 -5
  159. package/lib/util/dataURL.js +2 -2
  160. package/lib/util/deterministicGrouping.js +7 -5
  161. package/lib/util/formatSize.js +6 -2
  162. package/lib/util/globUtils.js +710 -0
  163. package/lib/util/identifier.js +23 -6
  164. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  165. package/lib/util/internalSerializables.js +88 -71
  166. package/lib/util/magicComment.js +121 -25
  167. package/lib/util/numberHash.js +2 -2
  168. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  169. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  170. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  171. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  172. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  173. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  174. package/lib/webpack.js +65 -0
  175. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  176. package/module.d.ts +21 -0
  177. package/package.json +14 -13
  178. package/schemas/WebpackOptions.check.js +1 -1
  179. package/schemas/WebpackOptions.json +759 -36
  180. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  181. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  182. package/schemas/plugins/ProgressPlugin.json +17 -1
  183. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  184. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  185. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  186. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  187. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  188. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  189. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  190. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  191. package/types.d.ts +1300 -325
@@ -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");
@@ -35,14 +28,18 @@ const {
35
28
  webpackCommentRegExp
36
29
  } = require("../util/magicComment");
37
30
  const {
31
+ NS_HTML,
38
32
  NS_SVG,
39
33
  NodeType,
40
34
  SVG_TAG_ADJUST,
41
35
  SourceProcessor,
42
- buildHtmlAst,
43
- decodeHtmlEntities,
44
- decodeHtmlEntitiesWithMap,
45
- findAttr,
36
+ decodeEntities,
37
+ escapeAttribute,
38
+ escapeText,
39
+ isAsciiWhitespace,
40
+ parseCssUrls,
41
+ parseMsapplicationTask,
42
+ parseSrc,
46
43
  parseSrcset
47
44
  } = require("./syntax");
48
45
 
@@ -70,31 +67,6 @@ const {
70
67
  */
71
68
  /** @typedef {(source: string, context: HtmlTemplateContext) => string} HtmlTemplateFunction */
72
69
 
73
- const HORIZONTAL_TAB = "\u0009".charCodeAt(0);
74
- const NEWLINE = "\u000A".charCodeAt(0);
75
- const FORM_FEED = "\u000C".charCodeAt(0);
76
- const CARRIAGE_RETURN = "\u000D".charCodeAt(0);
77
- const SPACE = "\u0020".charCodeAt(0);
78
-
79
- /**
80
- * @param {number} char char
81
- * @returns {boolean} true when ASCII whitespace, otherwise false
82
- */
83
- function isASCIIWhitespace(char) {
84
- return (
85
- // Horizontal tab
86
- char === HORIZONTAL_TAB ||
87
- // New line
88
- char === NEWLINE ||
89
- // Form feed
90
- char === FORM_FEED ||
91
- // Carriage return
92
- char === CARRIAGE_RETURN ||
93
- // Space
94
- char === SPACE
95
- );
96
- }
97
-
98
70
  /** @typedef {import("./syntax").ParsedSource} ParsedSource */
99
71
 
100
72
  // Cheap pre-filter for a `style="..."` attribute: only route it through the
@@ -113,6 +85,67 @@ const FUNC_IRI_URL_REGEXP = /url\(/i;
113
85
  // formatting/text markup (`<p>hi</p>`) rewrites to itself, so skip it.
114
86
  const SRCDOC_ASSET_REGEXP = /[=]|url\(|@import/i;
115
87
 
88
+ // A URL carrying its own scheme (`https:`, `data:`, …) ignores the document
89
+ // base per the URL spec, so `<base href>` never rewrites it.
90
+ const ABSOLUTE_URL_SCHEME_REGEXP = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
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
+
116
149
  // CSP/fetch attributes copied verbatim onto a synthesized sibling `<link>` /
117
150
  // `<script>` (`HtmlEntryDependency`). Fixed output order, independent of
118
151
  // source order.
@@ -120,144 +153,58 @@ const COPYABLE_SIBLING_ATTRS = ["nonce", "crossorigin", "referrerpolicy"];
120
153
 
121
154
  const CC_QUOTATION = '"'.charCodeAt(0);
122
155
  const CC_APOSTROPHE = "'".charCodeAt(0);
156
+ const CC_SLASH = "/".charCodeAt(0);
123
157
 
124
158
  /**
125
159
  * Byte-exact source span of an attribute including the single leading
126
160
  * whitespace (` name`, ` name=value`, ` name="value"`), mirroring the
127
161
  * tokenizer's end-of-attribute rule (`valueEnd + 1` past the closing quote).
162
+ * @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
128
163
  * @param {string} source HTML source
129
- * @param {import("./syntax").HtmlAttribute} attr attribute
164
+ * @param {import("./syntax").HtmlAttributeRef} attr attribute ref
130
165
  * @returns {string} the attribute's source slice
131
166
  */
132
- const attrSourceSpan = (source, attr) => {
167
+ const attrSourceSpan = (path, source, attr) => {
168
+ const valueStart = path.attributeValueStart(attr);
169
+ const valueEnd = path.attributeValueEnd(attr);
133
170
  const end =
134
- attr.valueStart === -1
135
- ? attr.nameEnd
136
- : source.charCodeAt(attr.valueStart - 1) === CC_QUOTATION ||
137
- source.charCodeAt(attr.valueStart - 1) === CC_APOSTROPHE
138
- ? attr.valueEnd + 1
139
- : attr.valueEnd;
140
- return source.slice(attr.nameStart - 1, end);
171
+ valueStart === -1
172
+ ? path.attributeNameEnd(attr)
173
+ : source.charCodeAt(valueStart - 1) === CC_QUOTATION ||
174
+ source.charCodeAt(valueStart - 1) === CC_APOSTROPHE
175
+ ? valueEnd + 1
176
+ : valueEnd;
177
+ return source.slice(path.attributeNameStart(attr) - 1, end);
141
178
  };
142
179
 
143
- // eslint-disable-next-line no-control-regex
144
- const IGNORE_CHARS_REGEXP = /[\u0000-\u001F\u007F-\u009F\u00A0]/g;
145
-
146
180
  /**
147
- * @param {string} input input
148
- * @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`
149
188
  */
150
- const parseSrc = (input) => {
151
- const len = input.length;
152
- if (len === 0) throw new Error("Must be non-empty");
153
-
154
- let start = 0;
155
- let end = len;
156
-
157
- while (start < end) {
158
- const code = input.charCodeAt(start);
159
- if (code > 32 && code !== 160) break;
160
- start++;
161
- }
162
-
163
- if (start === end) throw new Error("Must be non-empty");
164
-
165
- while (end > start) {
166
- const code = input.charCodeAt(end - 1);
167
- if (code > 32 && code !== 160) break;
168
- end--;
189
+ const captureIntegrityRange = (path, source, elementStart) => {
190
+ const integrityAttr = path.findAttribute("integrity");
191
+ if (integrityAttr === 0 || path.attributeNameStart(integrityAttr) < 0) {
192
+ return null;
169
193
  }
170
-
171
- let value = input.slice(start, end);
172
-
173
- if (IGNORE_CHARS_REGEXP.test(value)) {
174
- value = value.replace(IGNORE_CHARS_REGEXP, "");
175
- if (value.length === 0) throw new Error("Must be non-empty");
176
- }
177
-
178
- return [[value, start, end]];
179
- };
180
-
181
- /**
182
- * Extracts the `icon-uri` value of an `msapplication-task` meta content
183
- * (`name=…;action-uri=…;icon-uri=…`) — the other parts are page URLs, not assets.
184
- * @param {string} input input
185
- * @returns {ParsedSource[]} parsed icon-uri
186
- */
187
- const parseMsapplicationTask = (input) => {
188
- const len = input.length;
189
- let pos = 0;
190
- while (pos < len) {
191
- let sep = input.indexOf(";", pos);
192
- if (sep === -1) sep = len;
193
- const eq = input.indexOf("=", pos);
194
- if (eq !== -1 && eq < sep) {
195
- const key = input.slice(pos, eq).trim().toLowerCase();
196
- if (key === "icon-uri") {
197
- let start = eq + 1;
198
- let end = sep;
199
- while (start < end && isASCIIWhitespace(input.charCodeAt(start))) {
200
- start++;
201
- }
202
- while (end > start && isASCIIWhitespace(input.charCodeAt(end - 1))) {
203
- end--;
204
- }
205
- if (start === end) return [];
206
- return [[input.slice(start, end), start, end]];
207
- }
208
- }
209
- pos = sep + 1;
194
+ let rs = path.attributeNameStart(integrityAttr);
195
+ while (rs > 0 && isAsciiWhitespace(source.charCodeAt(rs - 1))) {
196
+ rs--;
210
197
  }
211
- return [];
212
- };
213
-
214
- /**
215
- * Extracts `url(...)` references from a CSS value (an SVG presentation
216
- * attribute). Reuses webpack's CSS lexer so quoting/escaping match the CSS
217
- * spec; returns the `parseSrc` shape so the shared emit path maps and rewrites
218
- * the spans. Unquoted `url(path)` rewrites the content span; quoted
219
- * `url("path")` rewrites the inner string span (quotes preserved).
220
- * @param {string} input attribute value (a CSS component-value list)
221
- * @returns {ParsedSource[]} url references
222
- */
223
- const parseCssUrls = (input) => {
224
- const ts = new TokenStream(input);
225
- /** @type {ParsedSource[]} */
226
- const result = [];
227
- for (;;) {
228
- const t = ts.consume();
229
- if (t.type === TT_EOF) break;
230
- if (t.type === TT_URL) {
231
- if (t.contentEnd > t.contentStart) {
232
- result.push([
233
- input.slice(t.contentStart, t.contentEnd),
234
- t.contentStart,
235
- t.contentEnd
236
- ]);
237
- }
238
- } else if (
239
- t.type === TT_FUNCTION &&
240
- equalsLowerCase(input.slice(t.start, t.end - 1), "url")
241
- ) {
242
- let s = ts.consume();
243
- while (s.type === TT_WHITESPACE) s = ts.consume();
244
- if (s.type === TT_STRING) {
245
- const quote = input.charCodeAt(s.start);
246
- const innerStart = s.start + 1;
247
- // Drop the closing quote, unless the string is unterminated at EOF.
248
- const innerEnd =
249
- input.charCodeAt(s.end - 1) === quote ? s.end - 1 : s.end;
250
- if (innerEnd > innerStart) {
251
- result.push([
252
- input.slice(innerStart, innerEnd),
253
- innerStart,
254
- innerEnd
255
- ]);
256
- }
257
- }
258
- }
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;
259
206
  }
260
- return result;
207
+ return [rs - elementStart, re - elementStart];
261
208
  };
262
209
 
263
210
  /**
@@ -283,6 +230,8 @@ const META = new Map([
283
230
  "msapplication-task",
284
231
  "twitter:image",
285
232
  "twitter:image:src",
233
+ // Twitter player card: URL to the raw video/media stream
234
+ "twitter:player:stream",
286
235
  // Legacy preview-image hint
287
236
  "thumbnail"
288
237
  ])
@@ -338,9 +287,7 @@ const ALLOWED_LINK_RELS = new Set([
338
287
  "apple-touch-icon",
339
288
  "apple-touch-icon-precomposed",
340
289
  "apple-touch-startup-image",
341
- // TODO the manifest file is emitted as an asset, but its JSON
342
- // `icons`/`screenshots`/`shortcuts[].icons` URLs aren't parsed — needs a
343
- // dedicated webmanifest module type (parser + generator), not a table entry.
290
+ // Routed to the `asset/webmanifest` module type, which parses its icon URLs.
344
291
  "manifest",
345
292
  "prefetch",
346
293
  "preload",
@@ -451,14 +398,17 @@ const isLinkStylesheet = (attributes) => {
451
398
  return rel.trim().toLowerCase().split(/\s+/).includes("stylesheet");
452
399
  };
453
400
 
454
- /** @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 */
455
402
  /** Entry types: a `type` whose value is loaded as its own compilation entry chunk. */
456
- /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet"} EntrySourceType */
403
+ /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} EntrySourceType */
457
404
  /** @typedef {SourceType | ((attrs: Map<string, string>, css: boolean) => SourceType)} SourceTypeOrResolver */
458
405
  /** @typedef {(attributes: Map<string, string>, value: string) => boolean} SourceFilter */
459
406
  /** @typedef {{ tag?: string, attribute: string, type: SourceType, filter?: SourceFilter }} SourceEntry */
460
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). */
461
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 */
462
412
  /** A source whose value is the element's text content (a `<style>`/`<script>` body) rather than an attribute. */
463
413
  /** @typedef {{ type: SourceTypeOrResolver, filter?: (attributes: Map<string, string>) => boolean }} ContentSourceItem */
464
414
 
@@ -494,6 +444,24 @@ const LINK_HREF = {
494
444
  type: (attrs, css) => {
495
445
  if (isLinkModulePreload(attrs)) return "modulepreload";
496
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
+ }
497
465
  return "src";
498
466
  },
499
467
  filter: filterLinkUnion
@@ -581,15 +549,22 @@ const DEFAULT_SOURCES_BY_TAG = dict({
581
549
  // `xlink:href` reference another element/resource; fragment-only `#id`
582
550
  // values are left untouched, so only external `file.svg#id` is rewritten.
583
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 }),
584
555
  "color-profile": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
556
+ cursor: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
585
557
  feImage: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
586
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 }),
587
561
  image: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
588
562
  linearGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
589
563
  mpath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
590
564
  pattern: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
591
565
  radialGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
592
566
  textPath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
567
+ tref: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
593
568
  use: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC })
594
569
  // CSS `url(...)` references in SVG presentation attributes (fill, stroke,
595
570
  // clip-path, …) are handled separately during the walk (`parseCssUrls`).
@@ -650,9 +625,9 @@ const ANY_TAG = "";
650
625
  * Folds the any-tag sources into every per-tag bucket (tag-specific entries
651
626
  * win) and stores them under `ANY_TAG`, so the whole source model is one
652
627
  * object and the walk's per-attribute lookup is a single property read.
653
- * @param {Record<string, Record<string, SourceItem>>} byTag per-tag sources
654
- * @param {Record<string, SourceItem>} any any-tag sources
655
- * @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
656
631
  */
657
632
  const foldAnySources = (byTag, any) => {
658
633
  for (const tag of Object.keys(byTag)) {
@@ -665,12 +640,12 @@ const foldAnySources = (byTag, any) => {
665
640
  // Default table (per-tag defaults + any-tag sources folded in) and the
666
641
  // `sources: false` table (only the always-on `style=""` attribute). Both are
667
642
  // precomputed so the common cases reference them with zero per-parser work.
668
- /** @type {Record<string, Record<string, SourceItem>>} */
643
+ /** @type {SourceTable} */
669
644
  const DEFAULT_SOURCES_FOLDED = foldAnySources(
670
645
  dict(DEFAULT_SOURCES_BY_TAG),
671
646
  DEFAULT_ANY_SOURCES
672
647
  );
673
- /** @type {Record<string, Record<string, SourceItem>>} */
648
+ /** @type {SourceTable} */
674
649
  const DISABLED_SOURCES_FOLDED = foldAnySources(dict(), ALWAYS_ANY_SOURCES);
675
650
 
676
651
  /**
@@ -727,9 +702,21 @@ class HtmlParser extends Parser {
727
702
  // the module source before it is stored and parsed.
728
703
  /** @type {HtmlTemplateFunction | undefined} */
729
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;
730
717
  // One source model: a per-tag table that also holds the any-tag sources
731
718
  // under `ANY_TAG`. The common cases reference precomputed tables.
732
- /** @type {Record<string, Record<string, SourceItem>>} */
719
+ /** @type {SourceTable} */
733
720
  this.sourcesByTag = DEFAULT_SOURCES_FOLDED;
734
721
 
735
722
  const sources = options.sources;
@@ -748,19 +735,25 @@ class HtmlParser extends Parser {
748
735
  // User entries override regardless of position. `dict()` keeps every
749
736
  // table null-prototype (see its doc), and per-tag writes rebuild the
750
737
  // bucket so the aliased default buckets stay intact.
751
- /** @type {Record<string, Record<string, SourceItem>>} */
738
+ /** @type {SourceTable} */
752
739
  const byTag = sources.includes("...")
753
740
  ? dict(DEFAULT_SOURCES_BY_TAG)
754
741
  : dict();
755
- /** @type {Record<string, SourceItem>} */
742
+ /** @type {SourceBucket} */
756
743
  const any = dict(DEFAULT_ANY_SOURCES);
757
744
  for (const entry of sources) {
758
745
  if (entry === "...") continue;
759
- /** @type {SourceItem} */
760
- const item = {
761
- type: entry.type,
762
- filter: typeof entry.filter === "function" ? entry.filter : undefined
763
- };
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
+ };
764
757
  const attr = entry.attribute.toLowerCase();
765
758
  if (entry.tag === undefined) {
766
759
  any[attr] = item;
@@ -775,7 +768,7 @@ class HtmlParser extends Parser {
775
768
  }
776
769
  }
777
770
  }
778
- /** @type {Record<string, Record<string, SourceItem>>} */
771
+ /** @type {SourceTable} */
779
772
  this.sourcesByTag = foldAnySources(byTag, any);
780
773
  }
781
774
 
@@ -864,6 +857,7 @@ class HtmlParser extends Parser {
864
857
  const { hashFunction, module: outputModule } = compilation.outputOptions;
865
858
  const context = compilation.compiler.context;
866
859
  const css = Boolean(compilation.options.experiments.css);
860
+ const urlHints = this._urlHints;
867
861
 
868
862
  // Stable, per-HTML-module prefix used when generating entry names for
869
863
  // script src / modulepreload references so they don't collide across
@@ -881,6 +875,43 @@ class HtmlParser extends Parser {
881
875
  .digest("hex")
882
876
  .slice(0, 8);
883
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
+
884
915
  // Script src / modulepreload references are collected per-type
885
916
  // during the walk; HtmlModulesPlugin later turns them into real
886
917
  // entries. `script` and `script-module` entries are chained via a
@@ -889,26 +920,57 @@ class HtmlParser extends Parser {
889
920
  // must preload without running, so they can never become a runtime
890
921
  // leader that other entries would import.
891
922
  /**
892
- * @typedef {object} EntryScriptInfo
923
+ * @typedef {object} HtmlEntryInfo
893
924
  * @property {string} request
894
925
  * @property {string} entryName
895
- * @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
896
928
  */
897
- /** @type {EntryScriptInfo[]} */
929
+ /** @type {HtmlEntryInfo[]} */
898
930
  const scriptEntries = [];
899
- /** @type {EntryScriptInfo[]} */
931
+ /** @type {HtmlEntryInfo[]} */
900
932
  const scriptModuleEntries = [];
901
- /** @type {EntryScriptInfo[]} */
933
+ /** @type {HtmlEntryInfo[]} */
902
934
  const modulePreloadEntries = [];
903
- /** @type {EntryScriptInfo[]} */
935
+ /** @type {HtmlEntryInfo[]} */
904
936
  const stylesheetEntries = [];
905
-
906
- // Offset of the first script tag; anchors injected stylesheet `<link>`s
907
- // before it so a later entry's CSS still loads ahead of every script.
908
- 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;
909
954
 
910
955
  let nextEntryIndex = 0;
911
956
 
957
+ // `<base href>` resolves the relative URLs that follow it. A relative base
958
+ // (`./assets/`) rewrites them into a subdirectory — still bundled; a
959
+ // root-relative or absolute base (`/`, `https://cdn/`) points them outside
960
+ // the build, so those URLs are left untouched. Resolved in the walk below
961
+ // from the first `<base href>` seen (`documentBase` undefined until then).
962
+ /** @type {string | undefined} */
963
+ let documentBase;
964
+ /** @type {string | undefined} */
965
+ let baseDir;
966
+ let baseIsExternal = false;
967
+ // Prepended to the emitted URLs' auto-public-path undo path so the base
968
+ // doesn't misdirect them: the browser resolves rewritten (relative)
969
+ // output URLs against the base dir, so one `../` per base segment cancels
970
+ // it. Undefined for absolute publicPath (no undo path is emitted).
971
+ /** @type {string | undefined} */
972
+ let baseUrlPrefix;
973
+
912
974
  /**
913
975
  * Tracks the `webpackIgnore` value from the most recent comment that
914
976
  * appears before the next tag. Reset whenever a tag is emitted or a
@@ -916,48 +978,54 @@ class HtmlParser extends Parser {
916
978
  * @type {boolean | undefined}
917
979
  */
918
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;
919
989
 
920
990
  const magicCommentContext = this.magicCommentContext;
921
991
 
922
992
  /**
923
- * @param {import("./syntax").HtmlAttribute | undefined} typeAttr type attribute
993
+ * @param {import("./syntax").HtmlPath} path walk path (used only for attribute-ref reads)
994
+ * @param {import("./syntax").HtmlAttributeRef} typeAttr type attribute ref (0 = none)
924
995
  * @param {number} nameEnd end offset of the tag name
925
996
  * @param {string} type type of the script
926
997
  * @param {string} input source string
927
998
  */
928
- const reconcileScriptTypeAttr = (typeAttr, nameEnd, type, input) => {
999
+ const reconcileScriptTypeAttr = (path, typeAttr, nameEnd, type, input) => {
1000
+ const valueStart =
1001
+ typeAttr !== 0 ? path.attributeValueStart(typeAttr) : -1;
1002
+ const valueEnd = typeAttr !== 0 ? path.attributeValueEnd(typeAttr) : -1;
929
1003
  if (outputModule && type === "script") {
930
1004
  // Chunk is an ES module; upgrade the tag.
931
- if (typeAttr && typeAttr.valueStart !== -1) {
1005
+ if (typeAttr !== 0 && valueStart !== -1) {
932
1006
  module.addPresentationalDependency(
933
- new ConstDependency("module", [
934
- typeAttr.valueStart,
935
- typeAttr.valueEnd
936
- ])
1007
+ new ConstDependency("module", [valueStart, valueEnd])
937
1008
  );
938
1009
  } else {
939
1010
  module.addPresentationalDependency(
940
1011
  new ConstDependency(' type="module"', nameEnd)
941
1012
  );
942
1013
  }
943
- } else if (!outputModule && type === "script-module" && typeAttr) {
1014
+ } else if (!outputModule && type === "script-module" && typeAttr !== 0) {
944
1015
  // Chunk is a classic IIFE; drop `type="module"` so the
945
1016
  // browser doesn't load it under module semantics.
946
1017
  let attrEnd;
947
- if (typeAttr.valueStart === -1) {
948
- attrEnd = typeAttr.nameEnd;
949
- } else if (
950
- input[typeAttr.valueEnd] === '"' ||
951
- input[typeAttr.valueEnd] === "'"
952
- ) {
953
- attrEnd = typeAttr.valueEnd + 1;
1018
+ if (valueStart === -1) {
1019
+ attrEnd = path.attributeNameEnd(typeAttr);
1020
+ } else if (input[valueEnd] === '"' || input[valueEnd] === "'") {
1021
+ attrEnd = valueEnd + 1;
954
1022
  } else {
955
- attrEnd = typeAttr.valueEnd;
1023
+ attrEnd = valueEnd;
956
1024
  }
957
- let attrStart = typeAttr.nameStart;
1025
+ let attrStart = path.attributeNameStart(typeAttr);
958
1026
  if (
959
1027
  attrStart > 0 &&
960
- isASCIIWhitespace(input.charCodeAt(attrStart - 1))
1028
+ isAsciiWhitespace(input.charCodeAt(attrStart - 1))
961
1029
  ) {
962
1030
  attrStart -= 1;
963
1031
  }
@@ -986,31 +1054,109 @@ class HtmlParser extends Parser {
986
1054
  dep.setLoc(s.line, s.column, e.line, e.column);
987
1055
  };
988
1056
 
1057
+ /**
1058
+ * Classifies the first `<base href>` seen in the walk, setting
1059
+ * `documentBase`/`baseDir`/`baseIsExternal`/`baseUrlPrefix` for the URLs
1060
+ * that follow it.
1061
+ * @param {import("./syntax").HtmlPath} path walk path (for attribute reads)
1062
+ * @param {import("./syntax").HtmlAttributeRef} hrefAttr the base's href attribute
1063
+ */
1064
+ const resolveDocumentBase = (path, hrefAttr) => {
1065
+ documentBase = decodeEntities(path.attributeValue(hrefAttr), true).trim();
1066
+ if (!documentBase) return;
1067
+ if (
1068
+ ABSOLUTE_URL_SCHEME_REGEXP.test(documentBase) ||
1069
+ documentBase.charCodeAt(0) === CC_SLASH
1070
+ ) {
1071
+ baseIsExternal = true;
1072
+ return;
1073
+ }
1074
+ // Normalize the base path into its descending directory segments,
1075
+ // resolving `.`/`..` like the URL spec. The final segment is the
1076
+ // referenced file (dropped) unless the href ends with `/` or a
1077
+ // dot-segment; `up` counts `..`s that climb above the document dir.
1078
+ const parts = documentBase.split("/");
1079
+ /** @type {string[]} */
1080
+ const dirs = [];
1081
+ let up = 0;
1082
+ for (let i = 0; i < parts.length; i++) {
1083
+ const part = parts[i];
1084
+ if (part === "" || part === ".") continue;
1085
+ if (part === "..") {
1086
+ if (dirs.length > 0) dirs.pop();
1087
+ else up++;
1088
+ continue;
1089
+ }
1090
+ // A trailing plain segment (no following `/`) is the file, not a dir.
1091
+ if (i === parts.length - 1) continue;
1092
+ dirs.push(part);
1093
+ }
1094
+ baseDir = `${"../".repeat(up)}${dirs.length > 0 ? `${dirs.join("/")}/` : ""}`;
1095
+ // A base above the document dir can't be cancelled with `../` (the
1096
+ // document's own dir name is unknown) — leave the output URLs
1097
+ // un-prefixed; they still resolve under an absolute publicPath.
1098
+ if (up === 0 && dirs.length > 0) {
1099
+ baseUrlPrefix = "../".repeat(dirs.length);
1100
+ }
1101
+ };
1102
+
1103
+ // Lazy decoded-attribute map for the current element — parse-scoped
1104
+ // to avoid a closure and memo slot per element.
1105
+ /** @type {import("./syntax").HtmlPath | undefined} */
1106
+ let attrMapPath;
1107
+ let attrMapCount = 0;
1108
+ /** @type {Map<string, string> | undefined} */
1109
+ let currentAttributesMap;
1110
+ const getAttributesMap = () => {
1111
+ if (currentAttributesMap) return currentAttributesMap;
1112
+ currentAttributesMap = new Map();
1113
+ const path = /** @type {import("./syntax").HtmlPath} */ (attrMapPath);
1114
+ for (let i = 0; i < attrMapCount; i++) {
1115
+ const attr = path.attributeAt(i);
1116
+ // Decoded values — filters and type resolvers compare what
1117
+ // the browser sees (e.g. `rel="&#105;con"` means `icon`)
1118
+ currentAttributesMap.set(
1119
+ path.attributeName(attr),
1120
+ decodeEntities(path.attributeValue(attr), true)
1121
+ );
1122
+ }
1123
+ return currentAttributesMap;
1124
+ };
1125
+
989
1126
  // TODO implement full HTML parser (WASM)
990
1127
  // The walker descends into children (and `<template>` content) itself;
991
1128
  // the Element `exit` clears a pending `webpackIgnore` once an element's
992
1129
  // children are done (the old `walkChildren` behaviour).
993
1130
  new SourceProcessor()
994
1131
  .use({
995
- [NodeType.Comment]: (n) => {
996
- const node = /** @type {import("./syntax").HtmlComment} */ (n);
1132
+ [NodeType.Comment]: (path) => {
1133
+ const start = path.start();
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
+ }
997
1139
  // Only proper `<!-- ... -->` comments carry magic comments.
998
1140
  if (
999
- node.end - node.start < 7 ||
1000
- source.charCodeAt(node.start) !== 0x3c ||
1001
- source.charCodeAt(node.start + 1) !== 0x21 ||
1002
- source.charCodeAt(node.start + 2) !== 0x2d ||
1003
- source.charCodeAt(node.start + 3) !== 0x2d ||
1004
- source.charCodeAt(node.end - 1) !== 0x3e ||
1005
- source.charCodeAt(node.end - 2) !== 0x2d ||
1006
- source.charCodeAt(node.end - 3) !== 0x2d
1141
+ end - start < 7 ||
1142
+ source.charCodeAt(start) !== 0x3c ||
1143
+ source.charCodeAt(start + 1) !== 0x21 ||
1144
+ source.charCodeAt(start + 2) !== 0x2d ||
1145
+ source.charCodeAt(start + 3) !== 0x2d ||
1146
+ source.charCodeAt(end - 1) !== 0x3e ||
1147
+ source.charCodeAt(end - 2) !== 0x2d ||
1148
+ source.charCodeAt(end - 3) !== 0x2d
1007
1149
  ) {
1008
1150
  pendingWebpackIgnore = undefined;
1151
+ pendingWebpackInline = undefined;
1152
+ pendingHints = undefined;
1009
1153
  return;
1010
1154
  }
1011
- const value = node.data;
1155
+ const value = path.data();
1012
1156
  if (!webpackCommentRegExp.test(value)) {
1013
1157
  pendingWebpackIgnore = undefined;
1158
+ pendingWebpackInline = undefined;
1159
+ pendingHints = undefined;
1014
1160
  return;
1015
1161
  }
1016
1162
  /** @type {Record<string, EXPECTED_ANY>} */
@@ -1018,8 +1164,8 @@ class HtmlParser extends Parser {
1018
1164
  try {
1019
1165
  options = parseMagicComment(value, magicCommentContext);
1020
1166
  } catch (err) {
1021
- const { line: sl, column: sc } = locConverter.get(node.start);
1022
- const { line: el, column: ec } = locConverter.get(node.end);
1167
+ const { line: sl, column: sc } = locConverter.get(start);
1168
+ const { line: el, column: ec } = locConverter.get(end);
1023
1169
  module.addWarning(
1024
1170
  new CommentCompilationWarning(
1025
1171
  `Compilation error while processing magic comment(-s): /*${value}*/: ${
@@ -1032,15 +1178,28 @@ class HtmlParser extends Parser {
1032
1178
  )
1033
1179
  );
1034
1180
  pendingWebpackIgnore = undefined;
1181
+ pendingWebpackInline = undefined;
1182
+ pendingHints = undefined;
1035
1183
  return;
1036
1184
  }
1037
- 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
+ ) {
1038
1192
  pendingWebpackIgnore = undefined;
1193
+ pendingWebpackInline = undefined;
1194
+ pendingHints = undefined;
1039
1195
  return;
1040
1196
  }
1041
- if (typeof options.webpackIgnore !== "boolean") {
1042
- const { line: sl, column: sc } = locConverter.get(node.start);
1043
- const { line: el, column: ec } = locConverter.get(node.end);
1197
+ if (
1198
+ options.webpackIgnore !== undefined &&
1199
+ typeof options.webpackIgnore !== "boolean"
1200
+ ) {
1201
+ const { line: sl, column: sc } = locConverter.get(start);
1202
+ const { line: el, column: ec } = locConverter.get(end);
1044
1203
  module.addWarning(
1045
1204
  new UnsupportedFeatureWarning(
1046
1205
  `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
@@ -1051,44 +1210,203 @@ class HtmlParser extends Parser {
1051
1210
  )
1052
1211
  );
1053
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;
1054
1235
  return;
1055
1236
  }
1056
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
+ }
1057
1259
  },
1058
- [NodeType.Doctype]: () => {
1260
+ [NodeType.Doctype]: (path) => {
1261
+ // only visited when head injection is active (see `skip` below)
1262
+ if (doctypeEnd === -1) doctypeEnd = path.end();
1059
1263
  pendingWebpackIgnore = undefined;
1264
+ pendingWebpackInline = undefined;
1265
+ pendingHints = undefined;
1060
1266
  },
1061
1267
  [NodeType.Element]: {
1062
1268
  // Children (and `<template>` content) are walked by the
1063
1269
  // processor after `enter`; `exit` clears a pending
1064
1270
  // `webpackIgnore` once they're done.
1065
- enter: (n) => {
1066
- const node = /** @type {import("./syntax").HtmlElement} */ (n);
1271
+ enter: (path) => {
1067
1272
  const ignore = pendingWebpackIgnore === true;
1273
+ const forceInline = pendingWebpackInline === true;
1068
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
+ }
1069
1382
 
1070
1383
  if (ignore) {
1071
1384
  return;
1072
1385
  }
1073
1386
 
1074
- const elementName = node.tagName;
1075
- const attrs = node.attributes;
1076
-
1077
- /** @type {Map<string, string> | undefined} */
1078
- let attributesMap;
1079
- const getAttributesMap = () => {
1080
- if (attributesMap) return attributesMap;
1081
- attributesMap = new Map();
1082
- for (const attr of attrs) {
1083
- // Decoded values — filters and type resolvers compare what
1084
- // the browser sees (e.g. `rel="&#105;con"` means `icon`)
1085
- attributesMap.set(
1086
- attr.name,
1087
- decodeHtmlEntities(attr.value, true)
1088
- );
1089
- }
1090
- return attributesMap;
1091
- };
1387
+ const elementName = path.tagName();
1388
+ const attributeCount = path.attributeCount();
1389
+ const elementStart = path.start();
1390
+ const tagEnd = path.tagEnd();
1391
+ const nameEnd = path.nameEnd();
1392
+
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).
1396
+ if (
1397
+ documentBase === undefined &&
1398
+ templateDepth === 0 &&
1399
+ elementName === "base" &&
1400
+ path.namespace() === NS_HTML
1401
+ ) {
1402
+ const hrefAttr = path.findAttribute("href");
1403
+ if (hrefAttr !== 0) resolveDocumentBase(path, hrefAttr);
1404
+ }
1405
+
1406
+ // Rebind the parse-scoped lazy attribute map to this element.
1407
+ attrMapPath = path;
1408
+ attrMapCount = attributeCount;
1409
+ currentAttributesMap = undefined;
1092
1410
 
1093
1411
  // Each matched attribute is a source; the element body (inline
1094
1412
  // `<style>`/`<script>`) is one more — content rather than an attribute.
@@ -1097,10 +1415,10 @@ class HtmlParser extends Parser {
1097
1415
  this.sourcesByTag[elementName] || this.sourcesByTag[ANY_TAG];
1098
1416
  const bodyItem = CONTENT_SOURCES[elementName];
1099
1417
  // Iterate the attributes, then one extra step for the element
1100
- // body (`i === attrs.length`); `attrs[i]` is only read on the
1101
- // attribute steps, so the access stays in bounds.
1102
- for (let i = 0; i < attrs.length + 1; i++) {
1103
- const content = i === attrs.length;
1418
+ // body (`i === attributeCount`); the attribute ref is only read on the
1419
+ // attribute steps, so it stays in bounds.
1420
+ for (let i = 0; i < attributeCount + 1; i++) {
1421
+ const content = i === attributeCount;
1104
1422
  /** @type {SourceType} */
1105
1423
  let type;
1106
1424
  /** @type {string} */
@@ -1109,23 +1427,18 @@ class HtmlParser extends Parser {
1109
1427
  let start;
1110
1428
  /** @type {number} */
1111
1429
  let end;
1112
- /** @type {import("./syntax").HtmlAttribute | undefined} */
1113
- let attr;
1430
+ /** @type {import("./syntax").HtmlAttributeRef} */
1431
+ let attr = 0;
1114
1432
  if (content) {
1115
1433
  if (!bodyItem) continue;
1116
1434
  if (bodyItem.filter && !bodyItem.filter(getAttributesMap())) {
1117
1435
  continue;
1118
1436
  }
1119
- /** @type {number | undefined} */
1120
- let bodyStart;
1121
- let bodyEnd = node.tagEnd;
1122
- for (const child of node.children) {
1123
- if (child.type === NodeType.Text) {
1124
- if (bodyStart === undefined) bodyStart = child.start;
1125
- bodyEnd = child.end;
1126
- }
1127
- }
1128
- if (bodyStart === undefined) continue;
1437
+ // `skip.text` drops the body `Text` node; the raw content
1438
+ // span is [`tagEnd`, `contentEnd`] on the element.
1439
+ const bodyStart = tagEnd;
1440
+ const bodyEnd = path.contentEnd();
1441
+ if (bodyEnd <= bodyStart) continue;
1129
1442
  value = source.slice(bodyStart, bodyEnd);
1130
1443
  if (value.trim() === "") continue;
1131
1444
  start = bodyStart;
@@ -1135,32 +1448,32 @@ class HtmlParser extends Parser {
1135
1448
  ? bodyItem.type(getAttributesMap(), css)
1136
1449
  : bodyItem.type;
1137
1450
  } else {
1138
- attr = attrs[i];
1139
- const item = sources[attr.name];
1451
+ attr = path.attributeAt(i);
1452
+ const item = sources[path.attributeName(attr)];
1140
1453
  if (!item) continue;
1141
1454
  if (
1142
1455
  item.namespace !== undefined &&
1143
- node.namespace !== item.namespace
1456
+ path.namespace() !== item.namespace
1144
1457
  ) {
1145
1458
  continue;
1146
1459
  }
1147
1460
  // Adoption-agency clones carry no offsets; skip blank values.
1148
- if (attr.valueStart === undefined || attr.valueStart === -1) {
1461
+ if (path.attributeValueStart(attr) === -1) {
1149
1462
  continue;
1150
1463
  }
1151
- value = attr.value;
1464
+ value = path.attributeValue(attr);
1152
1465
  if (!value || !/\S/.test(value)) continue;
1153
1466
  const filter = item.filter;
1154
1467
  if (filter) {
1155
1468
  // The filter also sees the decoded value (the browser tokenizes it
1156
1469
  // decoded, e.g. `&#117;rl(`), so a value-only check needn't re-read it.
1157
1470
  const decoded = value.includes("&")
1158
- ? decodeHtmlEntities(value, true)
1471
+ ? decodeEntities(value, true)
1159
1472
  : value;
1160
1473
  if (!filter(getAttributesMap(), decoded)) continue;
1161
1474
  }
1162
- start = attr.valueStart;
1163
- end = attr.valueEnd;
1475
+ start = path.attributeValueStart(attr);
1476
+ end = path.attributeValueEnd(attr);
1164
1477
  type =
1165
1478
  typeof item.type === "function"
1166
1479
  ? item.type(getAttributesMap(), css)
@@ -1175,9 +1488,7 @@ class HtmlParser extends Parser {
1175
1488
  case "stylesheet-style":
1176
1489
  case "stylesheet-style-attribute": {
1177
1490
  if (!css) break;
1178
- const cssText = content
1179
- ? value
1180
- : decodeHtmlEntities(value, true);
1491
+ const cssText = content ? value : decodeEntities(value, true);
1181
1492
  if (cssText.trim() === "") break;
1182
1493
  const dep = new HtmlInlineStyleDependency(
1183
1494
  dataUri("text/css", cssText),
@@ -1196,7 +1507,7 @@ class HtmlParser extends Parser {
1196
1507
  // whose URL is `about:srcdoc`, so its base URL is inherited from
1197
1508
  // this document — asset URLs resolve against this file's context.
1198
1509
  case "srcdoc": {
1199
- const htmlText = decodeHtmlEntities(value, true);
1510
+ const htmlText = decodeEntities(value, true);
1200
1511
  // Only spin up a nested module when there is markup that can
1201
1512
  // reference an asset; text/formatting-only markup is identical
1202
1513
  // after rewriting (see `SRCDOC_ASSET_REGEXP`).
@@ -1225,7 +1536,7 @@ class HtmlParser extends Parser {
1225
1536
  const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1226
1537
  const dep = new HtmlInlineScriptDependency(
1227
1538
  request,
1228
- node.nameEnd,
1539
+ nameEnd,
1229
1540
  [start, end],
1230
1541
  entryName,
1231
1542
  scriptType === "script-module" ? "esm" : "commonjs"
@@ -1233,8 +1544,9 @@ class HtmlParser extends Parser {
1233
1544
  setLoc(dep, start, end);
1234
1545
  module.addPresentationalDependency(dep);
1235
1546
  reconcileScriptTypeAttr(
1236
- findAttr(attrs, "type"),
1237
- node.nameEnd,
1547
+ path,
1548
+ path.findAttribute("type"),
1549
+ nameEnd,
1238
1550
  scriptType,
1239
1551
  source
1240
1552
  );
@@ -1254,13 +1566,13 @@ class HtmlParser extends Parser {
1254
1566
  case "css-url":
1255
1567
  case "msapplication-task":
1256
1568
  case "modulepreload":
1257
- case "stylesheet": {
1569
+ case "stylesheet":
1570
+ case "html":
1571
+ case "preload":
1572
+ case "prefetch": {
1258
1573
  // Parse the value into one or more URLs (decoding character
1259
1574
  // references, mapping spans back to raw offsets), then emit a plain
1260
1575
  // asset reference or an entry chunk per URL.
1261
- const a = /** @type {import("./syntax").HtmlAttribute} */ (
1262
- attr
1263
- );
1264
1576
  const parse =
1265
1577
  type === "srcset"
1266
1578
  ? parseSrcset
@@ -1273,7 +1585,7 @@ class HtmlParser extends Parser {
1273
1585
  /** @type {number[] | undefined} */
1274
1586
  let map;
1275
1587
  if (value.includes("&")) {
1276
- ({ text, map } = decodeHtmlEntitiesWithMap(value, true));
1588
+ ({ text, map } = decodeEntities(value, true, true));
1277
1589
  if (!/\S/.test(text)) break;
1278
1590
  }
1279
1591
  /** @type {ParsedSource[] | undefined} */
@@ -1287,9 +1599,9 @@ class HtmlParser extends Parser {
1287
1599
  new ModuleDependencyError(
1288
1600
  module,
1289
1601
  new WebpackError(
1290
- `Bad value for attribute "${
1291
- a.name
1292
- }" on element "${elementName}": ${
1602
+ `Bad value for attribute "${path.attributeName(
1603
+ attr
1604
+ )}" on element "${elementName}": ${
1293
1605
  /** @type {Error} */ (err).message
1294
1606
  }`
1295
1607
  ),
@@ -1305,8 +1617,84 @@ class HtmlParser extends Parser {
1305
1617
  for (const [url, us, ue] of parsed) {
1306
1618
  // Internal `url(#id)` / fragment-only refs aren't assets.
1307
1619
  if (!url || url.startsWith("#")) continue;
1620
+ // Resolve the request against `<base href>`. Only relative
1621
+ // URLs are affected (scheme / `//` / `/` URLs ignore the
1622
+ // base); an external base drops them from the build.
1623
+ let request = url;
1624
+ if (
1625
+ documentBase &&
1626
+ url.charCodeAt(0) !== CC_SLASH &&
1627
+ !ABSOLUTE_URL_SCHEME_REGEXP.test(url)
1628
+ ) {
1629
+ if (baseIsExternal) continue;
1630
+ request = baseDir + url;
1631
+ }
1308
1632
  const s = start + (map ? map[us] : us);
1309
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
+ }
1310
1698
  // `src`/`srcset`/`css-url`/`msapplication-task` are plain assets;
1311
1699
  // the rest are narrowed to entry types here and share one
1312
1700
  // `HtmlEntryDependency`, recorded for HtmlModulesPlugin.
@@ -1345,11 +1733,12 @@ class HtmlParser extends Parser {
1345
1733
  // CSP/fetch attributes the template copies onto siblings.
1346
1734
  let copyableAttrsText = "";
1347
1735
  let hasOwnCrossOrigin = false;
1348
- if (node.start >= 0) {
1736
+ if (elementStart >= 0) {
1349
1737
  for (const copyableName of COPYABLE_SIBLING_ATTRS) {
1350
- const copyableAttr = findAttr(attrs, copyableName);
1351
- if (copyableAttr) {
1738
+ const copyableAttr = path.findAttribute(copyableName);
1739
+ if (copyableAttr !== 0) {
1352
1740
  copyableAttrsText += attrSourceSpan(
1741
+ path,
1353
1742
  source,
1354
1743
  copyableAttr
1355
1744
  );
@@ -1359,70 +1748,68 @@ class HtmlParser extends Parser {
1359
1748
  }
1360
1749
  }
1361
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).
1362
1758
  if (
1363
- firstScriptStart === -1 &&
1364
- (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))
1365
1765
  ) {
1366
- firstScriptStart = node.start;
1766
+ firstBlockingScriptStart = elementStart;
1367
1767
  }
1368
1768
  // Sibling-clone attribute edits, captured from the parsed
1369
1769
  // attributes (offsets relative to the tag) so the template
1370
1770
  // needn't re-parse the tag text: `integrity` is dropped
1371
1771
  // (content-specific) and `script-module` clones are forced
1372
1772
  // to `type="module"` (its value range, else inserted).
1373
- /** @type {Range | null} */
1374
- let integrityRange = null;
1773
+ const integrityRange =
1774
+ elementStart >= 0 && elementName === nativeTag
1775
+ ? captureIntegrityRange(path, source, elementStart)
1776
+ : null;
1375
1777
  /** @type {Range | null} */
1376
1778
  let typeValueRange = null;
1377
- if (node.start >= 0 && elementName === nativeTag) {
1378
- const integrityAttr = findAttr(attrs, "integrity");
1379
- if (integrityAttr && integrityAttr.nameStart >= 0) {
1380
- let rs = integrityAttr.nameStart;
1381
- while (
1382
- rs > 0 &&
1383
- isASCIIWhitespace(source.charCodeAt(rs - 1))
1384
- ) {
1385
- rs--;
1386
- }
1387
- let re;
1388
- if (integrityAttr.valueStart === -1) {
1389
- re = integrityAttr.nameEnd;
1390
- } else {
1391
- const q = source.charCodeAt(
1392
- integrityAttr.valueStart - 1
1393
- );
1394
- re =
1395
- q === CC_QUOTATION || q === CC_APOSTROPHE
1396
- ? integrityAttr.valueEnd + 1
1397
- : integrityAttr.valueEnd;
1398
- }
1399
- integrityRange = [rs - node.start, re - node.start];
1400
- }
1401
- if (elementKind === "script-module") {
1402
- const typeAttr = findAttr(attrs, "type");
1403
- if (typeAttr && typeAttr.valueStart >= 0) {
1404
- typeValueRange = [
1405
- typeAttr.valueStart - node.start,
1406
- typeAttr.valueEnd - node.start
1407
- ];
1408
- }
1779
+ if (
1780
+ elementStart >= 0 &&
1781
+ elementName === nativeTag &&
1782
+ elementKind === "script-module"
1783
+ ) {
1784
+ const typeAttr = path.findAttribute("type");
1785
+ if (
1786
+ typeAttr !== 0 &&
1787
+ path.attributeValueStart(typeAttr) >= 0
1788
+ ) {
1789
+ typeValueRange = [
1790
+ path.attributeValueStart(typeAttr) - elementStart,
1791
+ path.attributeValueEnd(typeAttr) - elementStart
1792
+ ];
1409
1793
  }
1410
1794
  }
1411
1795
  const dep = new HtmlEntryDependency(
1412
- url,
1796
+ request,
1413
1797
  [s, e],
1414
1798
  entryName,
1415
1799
  entryCategory,
1416
1800
  elementKind,
1417
- node.start,
1418
- node.tagEnd,
1801
+ elementStart,
1802
+ tagEnd,
1419
1803
  elementName === nativeTag,
1420
1804
  copyableAttrsText,
1421
- node.nameEnd,
1805
+ nameEnd,
1422
1806
  hasOwnCrossOrigin,
1423
- firstScriptStart,
1807
+ firstBlockingScriptStart,
1424
1808
  integrityRange,
1425
- typeValueRange
1809
+ typeValueRange,
1810
+ forceInline,
1811
+ headSeen && headStart > 0 ? headStart : -1,
1812
+ headSeen && headLastEnd > 0 ? headLastEnd : -1
1426
1813
  );
1427
1814
  setLoc(dep, s, e);
1428
1815
  module.addPresentationalDependency(dep);
@@ -1431,8 +1818,9 @@ class HtmlParser extends Parser {
1431
1818
  (type === "script" || type === "script-module")
1432
1819
  ) {
1433
1820
  reconcileScriptTypeAttr(
1434
- findAttr(attrs, "type"),
1435
- node.nameEnd,
1821
+ path,
1822
+ path.findAttribute("type"),
1823
+ nameEnd,
1436
1824
  type,
1437
1825
  source
1438
1826
  );
@@ -1444,9 +1832,19 @@ class HtmlParser extends Parser {
1444
1832
  : type === "stylesheet"
1445
1833
  ? stylesheetEntries
1446
1834
  : modulePreloadEntries
1447
- ).push({ request: url, entryName, type });
1835
+ ).push({ request, entryName, type });
1448
1836
  } else {
1449
- const dep = new HtmlSourceDependency(url, [s, e]);
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
+ }
1450
1848
  setLoc(dep, s, e);
1451
1849
  module.addDependency(dep);
1452
1850
  module.addCodeGenerationDependency(dep);
@@ -1457,32 +1855,104 @@ class HtmlParser extends Parser {
1457
1855
  }
1458
1856
  }
1459
1857
  },
1460
- 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
+ }
1461
1868
  pendingWebpackIgnore = undefined;
1869
+ pendingWebpackInline = undefined;
1870
+ pendingHints = undefined;
1462
1871
  }
1463
1872
  }
1464
1873
  })
1465
- .process(source, { ast: buildHtmlAst(source) });
1874
+ // Skip AST output this walk never reads: `text` (script/style bodies are
1875
+ // read by offset via `contentEnd`) and `doctype` — unless head injection
1876
+ // needs the doctype as an insertion anchor; keep `comments` for
1877
+ // magic comments (`webpackIgnore`).
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
+ }
1466
1928
 
1467
1929
  const buildInfo = /** @type {HtmlModuleBuildInfo} */ (module.buildInfo);
1468
1930
  buildInfo.strict = true;
1931
+ if (baseUrlPrefix !== undefined) buildInfo.baseUrlPrefix = baseUrlPrefix;
1469
1932
  // Hand off the collected entries to HtmlModulesPlugin; it creates the
1470
1933
  // real compilation entries during the finishMake hook. The `script`
1471
1934
  // and `script-module` groups are chained via a leader-only dependOn
1472
1935
  // so they share a runtime; `modulepreload` and `stylesheet` entries
1473
1936
  // are emitted as independent entries since `<link rel=modulepreload>`
1474
- // 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.
1475
1939
  if (
1476
1940
  scriptEntries.length > 0 ||
1477
1941
  scriptModuleEntries.length > 0 ||
1478
1942
  modulePreloadEntries.length > 0 ||
1479
- stylesheetEntries.length > 0
1943
+ stylesheetEntries.length > 0 ||
1944
+ htmlLinkEntries.length > 0 ||
1945
+ preloadEntries.length > 0 ||
1946
+ prefetchEntries.length > 0
1480
1947
  ) {
1481
- buildInfo.htmlEntryScripts = {
1948
+ buildInfo.htmlEntries = {
1482
1949
  script: scriptEntries,
1483
1950
  "script-module": scriptModuleEntries,
1484
1951
  modulepreload: modulePreloadEntries,
1485
- stylesheet: stylesheetEntries
1952
+ stylesheet: stylesheetEntries,
1953
+ html: htmlLinkEntries,
1954
+ preload: preloadEntries,
1955
+ prefetch: prefetchEntries
1486
1956
  };
1487
1957
  }
1488
1958
 
@@ -1495,4 +1965,6 @@ class HtmlParser extends Parser {
1495
1965
  }
1496
1966
  }
1497
1967
 
1968
+ HtmlParser.buildHeadTags = buildHeadTags;
1969
+
1498
1970
  module.exports = HtmlParser;