webpack 5.106.2 → 5.107.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 (220) hide show
  1. package/README.md +2 -2
  2. package/lib/APIPlugin.js +1 -1
  3. package/lib/Cache.js +3 -6
  4. package/lib/CompatibilityPlugin.js +8 -7
  5. package/lib/Compilation.js +34 -26
  6. package/lib/Compiler.js +4 -13
  7. package/lib/ContextModule.js +2 -2
  8. package/lib/DefinePlugin.js +2 -2
  9. package/lib/Dependency.js +22 -1
  10. package/lib/DependencyTemplate.js +2 -1
  11. package/lib/EnvironmentPlugin.js +1 -1
  12. package/lib/EvalSourceMapDevToolPlugin.js +8 -9
  13. package/lib/ExternalModule.js +76 -15
  14. package/lib/ExternalModuleFactoryPlugin.js +5 -0
  15. package/lib/FileSystemInfo.js +187 -72
  16. package/lib/Generator.js +3 -3
  17. package/lib/HotModuleReplacementPlugin.js +26 -8
  18. package/lib/IgnorePlugin.js +2 -1
  19. package/lib/Module.js +19 -18
  20. package/lib/ModuleFactory.js +1 -1
  21. package/lib/ModuleSourceTypeConstants.js +31 -1
  22. package/lib/ModuleTypeConstants.js +12 -3
  23. package/lib/MultiCompiler.js +2 -2
  24. package/lib/NodeStuffPlugin.js +1 -1
  25. package/lib/NormalModule.js +13 -31
  26. package/lib/NormalModuleFactory.js +10 -2
  27. package/lib/Parser.js +1 -1
  28. package/lib/ProgressPlugin.js +129 -56
  29. package/lib/RuntimeGlobals.js +5 -5
  30. package/lib/RuntimeModule.js +9 -7
  31. package/lib/RuntimePlugin.js +11 -0
  32. package/lib/WarnCaseSensitiveModulesPlugin.js +70 -2
  33. package/lib/WarnDeprecatedOptionPlugin.js +1 -1
  34. package/lib/WarnNoModeSetPlugin.js +16 -1
  35. package/lib/Watching.js +2 -3
  36. package/lib/WebpackError.js +3 -77
  37. package/lib/WebpackIsIncludedPlugin.js +1 -1
  38. package/lib/WebpackOptionsApply.js +13 -1
  39. package/lib/asset/AssetBytesGenerator.js +6 -2
  40. package/lib/asset/AssetGenerator.js +22 -8
  41. package/lib/asset/AssetModulesPlugin.js +3 -1
  42. package/lib/asset/AssetSourceGenerator.js +6 -2
  43. package/lib/buildChunkGraph.js +4 -6
  44. package/lib/cache/PackFileCacheStrategy.js +4 -4
  45. package/lib/cli.js +3 -1
  46. package/lib/config/defaults.js +197 -10
  47. package/lib/config/normalization.js +3 -1
  48. package/lib/css/CssGenerator.js +320 -105
  49. package/lib/css/CssInjectStyleRuntimeModule.js +44 -42
  50. package/lib/css/CssLoadingRuntimeModule.js +22 -4
  51. package/lib/{CssModule.js → css/CssModule.js} +15 -15
  52. package/lib/css/CssModulesPlugin.js +166 -86
  53. package/lib/css/CssParser.js +566 -269
  54. package/lib/css/walkCssTokens.js +148 -2
  55. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +1 -1
  56. package/lib/dependencies/CommonJsDependencyHelpers.js +63 -0
  57. package/lib/dependencies/CommonJsExportRequireDependency.js +54 -10
  58. package/lib/dependencies/CommonJsExportsParserPlugin.js +1 -1
  59. package/lib/dependencies/CommonJsFullRequireDependency.js +32 -9
  60. package/lib/dependencies/CommonJsImportsParserPlugin.js +4 -3
  61. package/lib/dependencies/CommonJsRequireDependency.js +67 -4
  62. package/lib/dependencies/ContextDependency.js +1 -1
  63. package/lib/dependencies/ContextDependencyHelpers.js +1 -1
  64. package/lib/dependencies/CreateRequireParserPlugin.js +1 -1
  65. package/lib/dependencies/CriticalDependencyWarning.js +1 -1
  66. package/lib/dependencies/CssIcssExportDependency.js +332 -67
  67. package/lib/dependencies/CssIcssImportDependency.js +49 -7
  68. package/lib/dependencies/CssIcssSymbolDependency.js +11 -3
  69. package/lib/dependencies/CssImportDependency.js +8 -0
  70. package/lib/dependencies/CssUrlDependency.js +25 -0
  71. package/lib/dependencies/HarmonyDetectionParserPlugin.js +1 -1
  72. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +8 -7
  73. package/lib/dependencies/HarmonyExportExpressionDependency.js +22 -14
  74. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +110 -3
  75. package/lib/dependencies/HarmonyImportDependency.js +10 -2
  76. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +22 -1
  77. package/lib/dependencies/HarmonyImportSpecifierDependency.js +1 -1
  78. package/lib/{HarmonyLinkingError.js → dependencies/HarmonyLinkingError.js} +5 -3
  79. package/lib/dependencies/HtmlInlineScriptDependency.js +133 -0
  80. package/lib/dependencies/HtmlInlineStyleDependency.js +101 -0
  81. package/lib/dependencies/HtmlScriptSrcDependency.js +318 -0
  82. package/lib/dependencies/HtmlSourceDependency.js +127 -0
  83. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +1 -1
  84. package/lib/dependencies/ImportParserPlugin.js +2 -2
  85. package/lib/dependencies/ImportPhase.js +1 -1
  86. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +1 -1
  87. package/lib/{RequireJsStuffPlugin.js → dependencies/RequireJsStuffPlugin.js} +7 -7
  88. package/lib/dependencies/SystemPlugin.js +1 -1
  89. package/lib/dependencies/WebAssemblyImportDependency.js +1 -1
  90. package/lib/dependencies/WorkerPlugin.js +2 -2
  91. package/lib/{DelegatedModule.js → dll/DelegatedModule.js} +31 -31
  92. package/lib/{DelegatedModuleFactoryPlugin.js → dll/DelegatedModuleFactoryPlugin.js} +4 -4
  93. package/lib/{DelegatedPlugin.js → dll/DelegatedPlugin.js} +2 -2
  94. package/lib/{DllEntryPlugin.js → dll/DllEntryPlugin.js} +4 -4
  95. package/lib/{DllModule.js → dll/DllModule.js} +24 -24
  96. package/lib/{DllModuleFactory.js → dll/DllModuleFactory.js} +4 -4
  97. package/lib/{DllPlugin.js → dll/DllPlugin.js} +6 -5
  98. package/lib/{DllReferencePlugin.js → dll/DllReferencePlugin.js} +14 -14
  99. package/lib/{LibManifestPlugin.js → dll/LibManifestPlugin.js} +9 -9
  100. package/lib/{AsyncDependencyToInitialChunkError.js → errors/AsyncDependencyToInitialChunkError.js} +2 -2
  101. package/lib/errors/BuildCycleError.js +1 -1
  102. package/lib/{ChunkRenderError.js → errors/ChunkRenderError.js} +1 -1
  103. package/lib/{CodeGenerationError.js → errors/CodeGenerationError.js} +1 -1
  104. package/lib/{CommentCompilationWarning.js → errors/CommentCompilationWarning.js} +3 -3
  105. package/lib/{ConcurrentCompilationError.js → errors/ConcurrentCompilationError.js} +4 -2
  106. package/lib/{EnvironmentNotSupportAsyncWarning.js → errors/EnvironmentNotSupportAsyncWarning.js} +4 -4
  107. package/lib/{HookWebpackError.js → errors/HookWebpackError.js} +5 -5
  108. package/lib/{IgnoreErrorModuleFactory.js → errors/IgnoreErrorModuleFactory.js} +4 -4
  109. package/lib/{InvalidDependenciesModuleWarning.js → errors/InvalidDependenciesModuleWarning.js} +3 -3
  110. package/lib/errors/JSONParseError.js +114 -0
  111. package/lib/{ModuleBuildError.js → errors/ModuleBuildError.js} +5 -5
  112. package/lib/{ModuleDependencyError.js → errors/ModuleDependencyError.js} +2 -2
  113. package/lib/{ModuleDependencyWarning.js → errors/ModuleDependencyWarning.js} +4 -4
  114. package/lib/{ModuleError.js → errors/ModuleError.js} +5 -5
  115. package/lib/{ModuleHashingError.js → errors/ModuleHashingError.js} +1 -1
  116. package/lib/{ModuleNotFoundError.js → errors/ModuleNotFoundError.js} +2 -2
  117. package/lib/{ModuleParseError.js → errors/ModuleParseError.js} +8 -6
  118. package/lib/{ModuleRestoreError.js → errors/ModuleRestoreError.js} +1 -1
  119. package/lib/{ModuleStoreError.js → errors/ModuleStoreError.js} +1 -1
  120. package/lib/{ModuleWarning.js → errors/ModuleWarning.js} +5 -5
  121. package/lib/{NodeStuffInWebError.js → errors/NodeStuffInWebError.js} +4 -4
  122. package/lib/errors/NonErrorEmittedError.js +28 -0
  123. package/lib/{UnhandledSchemeError.js → errors/UnhandledSchemeError.js} +2 -2
  124. package/lib/{UnsupportedFeatureWarning.js → errors/UnsupportedFeatureWarning.js} +3 -3
  125. package/lib/errors/WebpackError.js +84 -0
  126. package/lib/html/HtmlGenerator.js +379 -0
  127. package/lib/html/HtmlModulesPlugin.js +433 -0
  128. package/lib/html/HtmlParser.js +1489 -0
  129. package/lib/html/walkHtmlTokens.js +2733 -0
  130. package/lib/ids/IdHelpers.js +2 -1
  131. package/lib/index.js +34 -15
  132. package/lib/javascript/JavascriptModulesPlugin.js +89 -8
  133. package/lib/javascript/JavascriptParser.js +197 -16
  134. package/lib/javascript/JavascriptParserHelpers.js +1 -1
  135. package/lib/json/JsonParser.js +7 -16
  136. package/lib/library/AbstractLibraryPlugin.js +1 -1
  137. package/lib/library/EnableLibraryPlugin.js +1 -1
  138. package/lib/{FalseIIFEUmdWarning.js → library/FalseIIFEUmdWarning.js} +1 -1
  139. package/lib/library/ModuleLibraryPlugin.js +74 -0
  140. package/lib/node/NodeEnvironmentPlugin.js +4 -2
  141. package/lib/node/nodeConsole.js +113 -64
  142. package/lib/optimize/ConcatenatedModule.js +51 -6
  143. package/lib/optimize/InnerGraph.js +1 -1
  144. package/lib/optimize/InnerGraphPlugin.js +11 -1
  145. package/lib/optimize/MinMaxSizeWarning.js +4 -4
  146. package/lib/optimize/ModuleConcatenationPlugin.js +15 -7
  147. package/lib/optimize/RealContentHashPlugin.js +89 -26
  148. package/lib/optimize/SideEffectsFlagPlugin.js +111 -3
  149. package/lib/optimize/SplitChunksPlugin.js +1 -1
  150. package/lib/performance/AssetsOverSizeLimitWarning.js +2 -2
  151. package/lib/performance/EntrypointsOverSizeLimitWarning.js +2 -2
  152. package/lib/performance/NoAsyncChunksWarning.js +5 -3
  153. package/lib/performance/SizeLimitsPlugin.js +1 -1
  154. package/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js +4 -1
  155. package/lib/rules/UseEffectRulePlugin.js +4 -3
  156. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +119 -13
  157. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +35 -0
  158. package/lib/schemes/DataUriPlugin.js +13 -1
  159. package/lib/schemes/VirtualUrlPlugin.js +1 -1
  160. package/lib/serialization/SerializerMiddleware.js +2 -2
  161. package/lib/sharing/ConsumeSharedPlugin.js +2 -2
  162. package/lib/sharing/ConsumeSharedRuntimeModule.js +8 -4
  163. package/lib/sharing/ProvideSharedModule.js +1 -1
  164. package/lib/sharing/ProvideSharedPlugin.js +1 -1
  165. package/lib/sharing/resolveMatchedConfigs.js +1 -1
  166. package/lib/stats/DefaultStatsFactoryPlugin.js +2 -2
  167. package/lib/stats/DefaultStatsPresetPlugin.js +1 -1
  168. package/lib/stats/DefaultStatsPrinterPlugin.js +1 -1
  169. package/lib/stats/StatsFactory.js +1 -1
  170. package/lib/typescript/TypeScriptPlugin.js +210 -0
  171. package/lib/url/URLParserPlugin.js +2 -2
  172. package/lib/util/AsyncQueue.js +2 -2
  173. package/lib/util/Hash.js +2 -2
  174. package/lib/util/LocConverter.js +53 -0
  175. package/lib/util/SortableSet.js +1 -1
  176. package/lib/util/cleverMerge.js +2 -2
  177. package/lib/util/comparators.js +3 -3
  178. package/lib/util/concatenate.js +3 -3
  179. package/lib/util/conventions.js +42 -1
  180. package/lib/util/createMappings.js +118 -0
  181. package/lib/{formatLocation.js → util/formatLocation.js} +2 -2
  182. package/lib/{SizeFormatHelpers.js → util/formatSize.js} +3 -1
  183. package/lib/util/fs.js +8 -8
  184. package/lib/util/hash/md4.js +1 -1
  185. package/lib/util/hash/xxhash64.js +1 -1
  186. package/lib/util/identifier.js +48 -0
  187. package/lib/util/internalSerializables.js +35 -19
  188. package/lib/util/magicComment.js +10 -7
  189. package/lib/util/parseJson.js +2 -73
  190. package/lib/util/source.js +21 -0
  191. package/lib/util/topologicalSort.js +69 -0
  192. package/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js +2 -2
  193. package/lib/wasm-async/AsyncWebAssemblyParser.js +1 -1
  194. package/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js +5 -3
  195. package/lib/wasm-sync/WasmFinalizeExportsPlugin.js +1 -1
  196. package/lib/wasm-sync/WebAssemblyInInitialChunkError.js +5 -3
  197. package/lib/webpack.js +3 -1
  198. package/package.json +22 -20
  199. package/schemas/WebpackOptions.check.js +1 -1
  200. package/schemas/WebpackOptions.json +118 -3
  201. package/schemas/plugins/{DllPlugin.check.d.ts → HtmlGeneratorOptions.check.d.ts} +1 -1
  202. package/schemas/plugins/HtmlGeneratorOptions.check.js +6 -0
  203. package/schemas/plugins/HtmlGeneratorOptions.json +3 -0
  204. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  205. package/schemas/plugins/ProgressPlugin.json +22 -0
  206. package/schemas/plugins/{DllReferencePlugin.check.d.ts → css/CssAutoOrModuleParserOptions.check.d.ts} +1 -1
  207. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +6 -0
  208. package/schemas/plugins/css/CssAutoOrModuleParserOptions.json +3 -0
  209. package/schemas/plugins/dll/DllPlugin.check.d.ts +7 -0
  210. package/schemas/plugins/dll/DllReferencePlugin.check.d.ts +7 -0
  211. package/types.d.ts +810 -101
  212. package/lib/CaseSensitiveModulesWarning.js +0 -80
  213. package/lib/GraphHelpers.js +0 -49
  214. package/lib/NoModeWarning.js +0 -23
  215. package/lib/css/CssMergeStyleSheetsRuntimeModule.js +0 -57
  216. /package/lib/{AbstractMethodError.js → errors/AbstractMethodError.js} +0 -0
  217. /package/schemas/plugins/{DllPlugin.check.js → dll/DllPlugin.check.js} +0 -0
  218. /package/schemas/plugins/{DllPlugin.json → dll/DllPlugin.json} +0 -0
  219. /package/schemas/plugins/{DllReferencePlugin.check.js → dll/DllReferencePlugin.check.js} +0 -0
  220. /package/schemas/plugins/{DllReferencePlugin.json → dll/DllReferencePlugin.json} +0 -0
@@ -0,0 +1,1489 @@
1
+ /*
2
+ MIT License http://www.opensource.org/licenses/mit-license.php
3
+ */
4
+
5
+ "use strict";
6
+
7
+ const vm = require("vm");
8
+ const Parser = require("../Parser");
9
+ const ConstDependency = require("../dependencies/ConstDependency");
10
+ const HtmlInlineScriptDependency = require("../dependencies/HtmlInlineScriptDependency");
11
+ const HtmlInlineStyleDependency = require("../dependencies/HtmlInlineStyleDependency");
12
+ const HtmlScriptSrcDependency = require("../dependencies/HtmlScriptSrcDependency");
13
+ const HtmlSourceDependency = require("../dependencies/HtmlSourceDependency");
14
+ const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
15
+ const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
16
+ const ModuleDependencyError = require("../errors/ModuleDependencyError");
17
+ const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
18
+ const WebpackError = require("../errors/WebpackError");
19
+ const LocConverter = require("../util/LocConverter");
20
+ const createHash = require("../util/createHash");
21
+ const { contextify } = require("../util/identifier");
22
+ const {
23
+ createMagicCommentContext,
24
+ webpackCommentRegExp
25
+ } = require("../util/magicComment");
26
+ const walkHtmlTokens = require("./walkHtmlTokens");
27
+
28
+ /** @typedef {import("../Module").BuildInfo} BuildInfo */
29
+ /** @typedef {import("../Module").BuildMeta} BuildMeta */
30
+ /** @typedef {import("../Parser").ParserState} ParserState */
31
+ /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
32
+
33
+ const HORIZONTAL_TAB = "\u0009".charCodeAt(0);
34
+ const NEWLINE = "\u000A".charCodeAt(0);
35
+ const FORM_FEED = "\u000C".charCodeAt(0);
36
+ const CARRIAGE_RETURN = "\u000D".charCodeAt(0);
37
+ const SPACE = "\u0020".charCodeAt(0);
38
+ const COMMA = ",".charCodeAt(0);
39
+ const LEFT_PARENTHESIS = "(".charCodeAt(0);
40
+ const RIGHT_PARENTHESIS = ")".charCodeAt(0);
41
+ const SMALL_LETTER_W = "w".charCodeAt(0);
42
+ const SMALL_LETTER_X = "x".charCodeAt(0);
43
+ const SMALL_LETTER_H = "h".charCodeAt(0);
44
+
45
+ /**
46
+ * @param {number} char char
47
+ * @returns {boolean} true when ASCII whitespace, otherwise false
48
+ */
49
+ function isASCIIWhitespace(char) {
50
+ return (
51
+ // Horizontal tab
52
+ char === HORIZONTAL_TAB ||
53
+ // New line
54
+ char === NEWLINE ||
55
+ // Form feed
56
+ char === FORM_FEED ||
57
+ // Carriage return
58
+ char === CARRIAGE_RETURN ||
59
+ // Space
60
+ char === SPACE
61
+ );
62
+ }
63
+
64
+ /** @typedef {[string, number, number]} ParsedSource */
65
+
66
+ // eslint-disable-next-line no-control-regex
67
+ const IGNORE_CHARS_REGEXP = /[\u0000-\u001F\u007F-\u009F\u00A0]/g;
68
+
69
+ /**
70
+ * @param {string} input input
71
+ * @returns {ParsedSource[]} parsed src
72
+ */
73
+ const parseSrc = (input) => {
74
+ const len = input.length;
75
+ if (len === 0) throw new Error("Must be non-empty");
76
+
77
+ let start = 0;
78
+ let end = len;
79
+
80
+ while (start < end) {
81
+ const code = input.charCodeAt(start);
82
+ if (code > 32 && code !== 160) break;
83
+ start++;
84
+ }
85
+
86
+ if (start === end) throw new Error("Must be non-empty");
87
+
88
+ while (end > start) {
89
+ const code = input.charCodeAt(end - 1);
90
+ if (code > 32 && code !== 160) break;
91
+ end--;
92
+ }
93
+
94
+ let value = input.slice(start, end);
95
+
96
+ if (IGNORE_CHARS_REGEXP.test(value)) {
97
+ value = value.replace(IGNORE_CHARS_REGEXP, "");
98
+ if (value.length === 0) throw new Error("Must be non-empty");
99
+ }
100
+
101
+ return [[value, start, end]];
102
+ };
103
+
104
+ // HTML `<style>` content is rawtext: it ends at the first `</style>`
105
+ // where the tag name is followed by whitespace, `>` or `/`. The
106
+ // lookahead (rather than a consuming character class) keeps the match
107
+ // from running past the first `>` into a later tag — the
108
+ // `[^>]*` only consumes any (rarely-seen) end-tag attributes before the
109
+ // closing `>` of the end tag itself.
110
+ const STYLE_END_REGEXP = /<\/style(?=[\s/>])[^>]*>/gi;
111
+
112
+ // (Don't use \s, to avoid matching non-breaking space)
113
+ // eslint-disable-next-line no-control-regex
114
+ const LEADING_SPACES_REGEXP = /^[ \t\n\r\u000C]+/;
115
+ // eslint-disable-next-line no-control-regex
116
+ const LEADING_COMMAS_OR_SPACES_REGEXP = /^[, \t\n\r\u000C]+/;
117
+ // eslint-disable-next-line no-control-regex
118
+ const LEADING_NOT_SPACES = /^[^ \t\n\r\u000C]+/;
119
+ const TRAILING_COMMAS_REGEXP = /[,]+$/;
120
+ const NON_NEGATIVE_INTEGER_REGEXP = /^\d+$/;
121
+ // ( Positive or negative or unsigned integers or decimals, without or without exponents.
122
+ // Must include at least one digit.
123
+ // According to spec tests any decimal point must be followed by a digit.
124
+ // No leading plus sign is allowed.)
125
+ // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
126
+ const FLOATING_POINT_REGEXP =
127
+ /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
128
+
129
+ /**
130
+ * @param {string} input input
131
+ * @returns {ParsedSource[]} parsed srcset
132
+ */
133
+ const parseSrcset = (input) => {
134
+ // 1. Let input be the value passed to this algorithm.
135
+ const inputLength = input.length;
136
+
137
+ /** @type {string | undefined} */
138
+ let url;
139
+ /** @type {string[]} */
140
+ let descriptors;
141
+ /** @type {string} */
142
+ let currentDescriptor;
143
+ /** @type {string} */
144
+ let state;
145
+ /** @type {number} */
146
+ let charCode;
147
+ /** @type {number} */
148
+ let position = 0;
149
+ /** @type {number} */
150
+ let start;
151
+
152
+ /** @type {[string, number, number][]} */
153
+ const candidates = [];
154
+
155
+ /**
156
+ * @param {RegExp} regExp reg exp to collect characters
157
+ * @returns {string | undefined} characters
158
+ */
159
+ function collectCharacters(regExp) {
160
+ /** @type {string} */
161
+ let chars;
162
+ const match = regExp.exec(input.slice(Math.max(0, position)));
163
+
164
+ if (match) {
165
+ [chars] = match;
166
+ position += chars.length;
167
+
168
+ return chars;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * @returns {void}
174
+ */
175
+ function parseDescriptors() {
176
+ // 9. Descriptor parser: Let error be no.
177
+ let pError = false;
178
+
179
+ // 10. Let width be absent.
180
+ // 11. Let density be absent.
181
+ // 12. Let future-compat-h be absent. (We're implementing it now as h)
182
+ /** @type {number | undefined} */
183
+ let width;
184
+ /** @type {number | undefined} */
185
+ let density;
186
+ /** @type {number | undefined} */
187
+ let height;
188
+ /** @type {string | undefined} */
189
+ let desc;
190
+
191
+ // 13. For each descriptor in descriptors, run the appropriate set of steps
192
+ // from the following list:
193
+ for (let i = 0; i < descriptors.length; i++) {
194
+ desc = descriptors[i];
195
+
196
+ const lastChar = desc[desc.length - 1].charCodeAt(0);
197
+ const value = desc.slice(0, Math.max(0, desc.length - 1));
198
+
199
+ // If the descriptor consists of a valid non-negative integer followed by
200
+ // a U+0077 LATIN SMALL LETTER W character
201
+ if (
202
+ NON_NEGATIVE_INTEGER_REGEXP.test(value) &&
203
+ lastChar === SMALL_LETTER_W
204
+ ) {
205
+ // If width and density are not both absent, then let error be yes.
206
+ if (width || density) {
207
+ pError = true;
208
+ }
209
+
210
+ const intVal = Number.parseInt(value, 10);
211
+
212
+ // Apply the rules for parsing non-negative integers to the descriptor.
213
+ // If the result is zero, let error be yes.
214
+ // Otherwise, let width be the result.
215
+ if (intVal === 0) {
216
+ pError = true;
217
+ } else {
218
+ width = intVal;
219
+ }
220
+ }
221
+ // If the descriptor consists of a valid floating-point number followed by
222
+ // a U+0078 LATIN SMALL LETTER X character
223
+ else if (
224
+ FLOATING_POINT_REGEXP.test(value) &&
225
+ lastChar === SMALL_LETTER_X
226
+ ) {
227
+ // If width, density and future-compat-h are not all absent, then let error
228
+ // be yes.
229
+ if (width || density || height) {
230
+ pError = true;
231
+ }
232
+
233
+ const floatVal = Number.parseFloat(value);
234
+
235
+ // Apply the rules for parsing floating-point number values to the descriptor.
236
+ // If the result is less than zero, let error be yes. Otherwise, let density
237
+ // be the result.
238
+ if (floatVal < 0) {
239
+ pError = true;
240
+ } else {
241
+ density = floatVal;
242
+ }
243
+ }
244
+ // If the descriptor consists of a valid non-negative integer followed by
245
+ // a U+0068 LATIN SMALL LETTER H character
246
+ else if (
247
+ NON_NEGATIVE_INTEGER_REGEXP.test(value) &&
248
+ lastChar === SMALL_LETTER_H
249
+ ) {
250
+ // If height and density are not both absent, then let error be yes.
251
+ if (height || density) {
252
+ pError = true;
253
+ }
254
+
255
+ const intVal = Number.parseInt(value, 10);
256
+
257
+ // Apply the rules for parsing non-negative integers to the descriptor.
258
+ // If the result is zero, let error be yes. Otherwise, let future-compat-h
259
+ // be the result.
260
+ if (intVal === 0) {
261
+ pError = true;
262
+ } else {
263
+ height = intVal;
264
+ }
265
+
266
+ // Anything else, Let error be yes.
267
+ } else {
268
+ pError = true;
269
+ }
270
+ }
271
+
272
+ // 15. If error is still no, then append a new image source to candidates whose
273
+ // URL is url, associated with a width width if not absent and a pixel
274
+ // density density if not absent. Otherwise, there is a parse error.
275
+ if (!pError) {
276
+ candidates.push([
277
+ /** @type {string} */ (url),
278
+ start,
279
+ start + /** @type {string} */ (url).length
280
+ ]);
281
+ } else {
282
+ throw new Error(
283
+ `Invalid srcset descriptor found in '${input}' at '${desc}'`
284
+ );
285
+ }
286
+ }
287
+
288
+ /**
289
+ * @returns {void}
290
+ */
291
+ function tokenize() {
292
+ // 8.1. Descriptor tokenizer: Skip whitespace
293
+ collectCharacters(LEADING_SPACES_REGEXP);
294
+
295
+ // 8.2. Let current descriptor be the empty string.
296
+ currentDescriptor = "";
297
+
298
+ // 8.3. Let state be in descriptor.
299
+ state = "in descriptor";
300
+
301
+ while (true) {
302
+ // 8.4. Let charCode be the character at position.
303
+ charCode = input.charCodeAt(position);
304
+
305
+ // Do the following depending on the value of state.
306
+ // For the purpose of this step, "EOF" is a special character representing
307
+ // that position is past the end of input.
308
+
309
+ // In descriptor
310
+ if (state === "in descriptor") {
311
+ // Do the following, depending on the value of charCode:
312
+
313
+ // Space character
314
+ // If current descriptor is not empty, append current descriptor to
315
+ // descriptors and let current descriptor be the empty string.
316
+ // Set state to after descriptor.
317
+ if (isASCIIWhitespace(charCode)) {
318
+ if (currentDescriptor) {
319
+ descriptors.push(currentDescriptor);
320
+ currentDescriptor = "";
321
+ state = "after descriptor";
322
+ }
323
+ }
324
+ // U+002C COMMA (,)
325
+ // Advance position to the next character in input. If current descriptor
326
+ // is not empty, append current descriptor to descriptors. Jump to the step
327
+ // labeled descriptor parser.
328
+ else if (charCode === COMMA) {
329
+ position += 1;
330
+
331
+ if (currentDescriptor) {
332
+ descriptors.push(currentDescriptor);
333
+ }
334
+
335
+ parseDescriptors();
336
+
337
+ return;
338
+ }
339
+ // U+0028 LEFT PARENTHESIS (()
340
+ // Append charCode to current descriptor. Set state to in parens.
341
+ else if (charCode === LEFT_PARENTHESIS) {
342
+ currentDescriptor += input.charAt(position);
343
+ state = "in parens";
344
+ }
345
+ // EOF
346
+ // If current descriptor is not empty, append current descriptor to
347
+ // descriptors. Jump to the step labeled descriptor parser.
348
+ else if (Number.isNaN(charCode)) {
349
+ if (currentDescriptor) {
350
+ descriptors.push(currentDescriptor);
351
+ }
352
+
353
+ parseDescriptors();
354
+
355
+ return;
356
+
357
+ // Anything else
358
+ // Append charCode to current descriptor.
359
+ } else {
360
+ currentDescriptor += input.charAt(position);
361
+ }
362
+ }
363
+ // In parens
364
+ else if (state === "in parens") {
365
+ // U+0029 RIGHT PARENTHESIS ())
366
+ // Append charCode to current descriptor. Set state to in descriptor.
367
+ if (charCode === RIGHT_PARENTHESIS) {
368
+ currentDescriptor += input.charAt(position);
369
+ state = "in descriptor";
370
+ }
371
+ // EOF
372
+ // Append current descriptor to descriptors. Jump to the step labeled
373
+ // descriptor parser.
374
+ else if (Number.isNaN(charCode)) {
375
+ descriptors.push(currentDescriptor);
376
+ parseDescriptors();
377
+ return;
378
+ }
379
+ // Anything else
380
+ // Append charCode to current descriptor.
381
+ else {
382
+ currentDescriptor += input.charAt(position);
383
+ }
384
+ }
385
+ // After descriptor
386
+ else if (state === "after descriptor") {
387
+ // Do the following, depending on the value of charCode:
388
+ if (isASCIIWhitespace(charCode)) {
389
+ // Space character: Stay in this state.
390
+ }
391
+ // EOF: Jump to the step labeled descriptor parser.
392
+ else if (Number.isNaN(charCode)) {
393
+ parseDescriptors();
394
+ return;
395
+ }
396
+ // Anything else
397
+ // Set state to in descriptor. Set position to the previous character in input.
398
+ else {
399
+ state = "in descriptor";
400
+ position -= 1;
401
+ }
402
+ }
403
+
404
+ // Advance position to the next character in input.
405
+ position += 1;
406
+ }
407
+ }
408
+
409
+ // 3. Let candidates be an initially empty source set.
410
+ // const candidates = []; // Moved to top
411
+
412
+ // 4. Splitting loop: Collect a sequence of characters that are space
413
+ // characters or U+002C COMMA characters. If any U+002C COMMA characters
414
+ // were collected, that is a parse error.
415
+
416
+ while (true) {
417
+ collectCharacters(LEADING_COMMAS_OR_SPACES_REGEXP);
418
+
419
+ // 5. If position is past the end of input, return candidates and abort these steps.
420
+ if (position >= inputLength) {
421
+ if (candidates.length === 0) {
422
+ throw new Error("Must contain one or more image candidate strings");
423
+ }
424
+
425
+ // (we're done, this is the sole return path)
426
+ return candidates;
427
+ }
428
+
429
+ // 6. Collect a sequence of characters that are not space characters,
430
+ // and let that be url.
431
+ start = position;
432
+ url = collectCharacters(LEADING_NOT_SPACES);
433
+
434
+ // 7. Let descriptors be a new empty list.
435
+ descriptors = [];
436
+
437
+ // 8. If url ends with a U+002C COMMA character (,), follow these sub steps:
438
+ // (1). Remove all trailing U+002C COMMA characters from url. If this removed
439
+ // more than one character, that is a parse error.
440
+ if (url && url.charCodeAt(url.length - 1) === COMMA) {
441
+ url = url.replace(TRAILING_COMMAS_REGEXP, "");
442
+
443
+ // (Jump ahead to step 9 to skip tokenization and just push the candidate).
444
+ parseDescriptors();
445
+ }
446
+ // Otherwise, follow these sub steps:
447
+ else {
448
+ tokenize();
449
+ }
450
+
451
+ // 16. Return to the step labeled splitting loop.
452
+ }
453
+ };
454
+
455
+ /**
456
+ * @param {Map<string, string>} attributes attributes
457
+ * @param {string} name name
458
+ * @returns {string | undefined} attribute value
459
+ */
460
+ const getAttributeValue = (attributes, name) => attributes.get(name);
461
+
462
+ /** @type {Map<string, Set<string>>} */
463
+ const META = new Map([
464
+ [
465
+ "name",
466
+ new Set([
467
+ // msapplication-TileImage
468
+ "msapplication-tileimage",
469
+ "msapplication-square70x70logo",
470
+ "msapplication-square150x150logo",
471
+ "msapplication-wide310x150logo",
472
+ "msapplication-square310x310logo",
473
+ "msapplication-config",
474
+ // TODO Do we need to parser it?
475
+ // "msapplication-task",
476
+ "twitter:image"
477
+ ])
478
+ ],
479
+ [
480
+ "property",
481
+ new Set([
482
+ "og:image",
483
+ "og:image:url",
484
+ "og:image:secure_url",
485
+ "og:audio",
486
+ "og:audio:secure_url",
487
+ "og:video",
488
+ "og:video:secure_url",
489
+ "vk:image"
490
+ ])
491
+ ],
492
+ [
493
+ "itemprop",
494
+ new Set([
495
+ "image",
496
+ "logo",
497
+ "screenshot",
498
+ "thumbnailurl",
499
+ "contenturl",
500
+ "downloadurl",
501
+ "duringmedia",
502
+ "embedurl",
503
+ "installurl",
504
+ "layoutimage"
505
+ ])
506
+ ]
507
+ ]);
508
+
509
+ /**
510
+ * @param {Map<string, string>} attributes attributes
511
+ * @returns {boolean} true when need to parse, otherwise false
512
+ */
513
+ const filterLinkItemprop = (attributes) => {
514
+ const value = getAttributeValue(attributes, "itemprop");
515
+ if (!value) return false;
516
+ const allowedAttributes = META.get("itemprop");
517
+ if (!allowedAttributes) return false;
518
+
519
+ return allowedAttributes.has(value.trim().toLowerCase());
520
+ };
521
+
522
+ /**
523
+ * @param {Map<string, string>} attributes attributes
524
+ * @returns {boolean} true when need to parse, otherwise false
525
+ */
526
+ const filterLinkHref = (attributes) => {
527
+ const rel = getAttributeValue(attributes, "rel");
528
+ if (!rel) return false;
529
+ const usedRels = rel.trim().toLowerCase().split(" ").filter(Boolean);
530
+ const allowedRels = [
531
+ "stylesheet",
532
+ "icon",
533
+ "mask-icon",
534
+ "apple-touch-icon",
535
+ "apple-touch-icon-precomposed",
536
+ "apple-touch-startup-image",
537
+ "manifest",
538
+ "prefetch",
539
+ "preload",
540
+ "modulepreload"
541
+ ];
542
+
543
+ return allowedRels.some((value) => usedRels.includes(value));
544
+ };
545
+
546
+ /**
547
+ * @param {Map<string, string>} attributes attributes
548
+ * @returns {boolean} true when need to parse, otherwise false
549
+ */
550
+ const filterLinkUnion = (attributes) =>
551
+ filterLinkHref(attributes) || filterLinkItemprop(attributes);
552
+
553
+ /**
554
+ * @param {Map<string, string>} attributes attributes
555
+ * @returns {boolean} true when need to parse, otherwise false
556
+ */
557
+ const filterMetaContent = (attributes) => {
558
+ for (const item of META) {
559
+ const [key, allowedNames] = item;
560
+ const name = getAttributeValue(attributes, key);
561
+ if (!name) continue;
562
+
563
+ return allowedNames.has(name.trim().toLowerCase());
564
+ }
565
+
566
+ return false;
567
+ };
568
+
569
+ /**
570
+ * @param {Map<string, string>} attributes attributes
571
+ * @returns {boolean} true when the script element opts into ES module semantics
572
+ */
573
+ const isModuleScript = (attributes) => {
574
+ const type = getAttributeValue(attributes, "type");
575
+ if (!type) return false;
576
+ return type.trim().toLowerCase() === "module";
577
+ };
578
+
579
+ // HTML `<script>` `type` values that the browser treats as executable
580
+ // JavaScript. Anything outside this set (e.g. `application/ld+json`,
581
+ // `importmap`, `application/wasm`) is a data block — webpack must not
582
+ // try to bundle it as a JS entry; it should pass through as an asset URL.
583
+ const JS_SCRIPT_TYPES = new Set([
584
+ "",
585
+ "module",
586
+ "text/javascript",
587
+ "application/javascript",
588
+ "text/ecmascript",
589
+ "application/ecmascript"
590
+ ]);
591
+
592
+ /**
593
+ * @param {Map<string, string>} attributes attributes
594
+ * @returns {boolean} true when the script element's `type` is executable JS
595
+ */
596
+ const isExecutableJsScript = (attributes) => {
597
+ const type = getAttributeValue(attributes, "type");
598
+ if (type === undefined) return true;
599
+ return JS_SCRIPT_TYPES.has(type.trim().toLowerCase());
600
+ };
601
+
602
+ /**
603
+ * @param {Map<string, string>} attributes attributes
604
+ * @returns {boolean} true when the link points at an ES module that should be bundled as an entry chunk
605
+ */
606
+ const isLinkModulePreload = (attributes) => {
607
+ const rel = getAttributeValue(attributes, "rel");
608
+ if (!rel) return false;
609
+ return rel.trim().toLowerCase().split(/\s+/).includes("modulepreload");
610
+ };
611
+
612
+ /**
613
+ * @param {Map<string, string>} attributes attributes
614
+ * @returns {boolean} true when the link is a `<link rel="stylesheet">` that should be bundled as a CSS entry chunk
615
+ */
616
+ const isLinkStylesheet = (attributes) => {
617
+ const rel = getAttributeValue(attributes, "rel");
618
+ if (!rel) return false;
619
+ return rel.trim().toLowerCase().split(/\s+/).includes("stylesheet");
620
+ };
621
+
622
+ /** @type {Map<string, Map<string, { parse: (input: string) => ParsedSource[] | undefined, filter?: (attributes: Map<string, string>) => boolean, entry?: boolean | ((attributes: Map<string, string>) => boolean), entryCategory?: string }>>} */
623
+ const DEFAULT_SOURCES = new Map([
624
+ [
625
+ "audio",
626
+ new Map([
627
+ [
628
+ "src",
629
+ {
630
+ parse: parseSrc
631
+ }
632
+ ]
633
+ ])
634
+ ],
635
+ [
636
+ "embed",
637
+ new Map([
638
+ [
639
+ "src",
640
+ {
641
+ parse: parseSrc
642
+ }
643
+ ]
644
+ ])
645
+ ],
646
+ [
647
+ "img",
648
+ new Map([
649
+ [
650
+ "src",
651
+ {
652
+ parse: parseSrc
653
+ }
654
+ ],
655
+ [
656
+ "srcset",
657
+ {
658
+ parse: parseSrcset
659
+ }
660
+ ]
661
+ ])
662
+ ],
663
+ [
664
+ "input",
665
+ new Map([
666
+ [
667
+ "src",
668
+ {
669
+ parse: parseSrc
670
+ }
671
+ ]
672
+ ])
673
+ ],
674
+ [
675
+ "link",
676
+ new Map([
677
+ [
678
+ "href",
679
+ {
680
+ parse: parseSrc,
681
+ filter: filterLinkUnion,
682
+ entry: isLinkModulePreload,
683
+ entryCategory: "esm"
684
+ }
685
+ ],
686
+ [
687
+ "imagesrcset",
688
+ {
689
+ parse: parseSrcset,
690
+ filter: filterLinkHref
691
+ }
692
+ ]
693
+ ])
694
+ ],
695
+ [
696
+ "meta",
697
+ new Map([
698
+ [
699
+ "content",
700
+ {
701
+ parse: parseSrc,
702
+ filter: filterMetaContent
703
+ }
704
+ ]
705
+ ])
706
+ ],
707
+ [
708
+ "object",
709
+ new Map([
710
+ [
711
+ "data",
712
+ {
713
+ parse: parseSrc
714
+ }
715
+ ]
716
+ ])
717
+ ],
718
+ [
719
+ "script",
720
+ new Map([
721
+ [
722
+ "src",
723
+ {
724
+ parse: parseSrc,
725
+ // Only executable-JS scripts become entries. Non-JS
726
+ // `<script>` types (e.g. `application/ld+json`,
727
+ // `importmap`) fall through to HtmlSourceDependency so
728
+ // the browser keeps seeing them as data blocks, with
729
+ // the asset URL rewritten like any other resource.
730
+ entry: isExecutableJsScript
731
+ }
732
+ ]
733
+ ])
734
+ ],
735
+ [
736
+ "source",
737
+ new Map([
738
+ [
739
+ "src",
740
+ {
741
+ parse: parseSrc
742
+ }
743
+ ],
744
+ [
745
+ "srcset",
746
+ {
747
+ parse: parseSrcset
748
+ }
749
+ ]
750
+ ])
751
+ ],
752
+ [
753
+ "track",
754
+ new Map([
755
+ [
756
+ "src",
757
+ {
758
+ parse: parseSrc
759
+ }
760
+ ]
761
+ ])
762
+ ],
763
+ [
764
+ "video",
765
+ new Map([
766
+ [
767
+ "poster",
768
+ {
769
+ parse: parseSrc
770
+ }
771
+ ],
772
+ [
773
+ "src",
774
+ {
775
+ parse: parseSrc
776
+ }
777
+ ]
778
+ ])
779
+ ],
780
+ // SVG
781
+ [
782
+ "image",
783
+ new Map([
784
+ [
785
+ "xlink:href",
786
+ {
787
+ parse: parseSrc
788
+ }
789
+ ],
790
+ [
791
+ "href",
792
+ {
793
+ parse: parseSrc
794
+ }
795
+ ]
796
+ ])
797
+ ],
798
+ [
799
+ "use",
800
+ new Map([
801
+ [
802
+ "xlink:href",
803
+ {
804
+ parse: parseSrc
805
+ }
806
+ ],
807
+ [
808
+ "href",
809
+ {
810
+ parse: parseSrc
811
+ }
812
+ ]
813
+ ])
814
+ ]
815
+ ]);
816
+
817
+ class HtmlParser extends Parser {
818
+ /**
819
+ * Creates an instance of HtmlParser.
820
+ * @param {(string | typeof import("../util/Hash"))=} hashFunction algorithm or constructor used by `output.hashFunction`; falls back to the default when omitted
821
+ * @param {string=} context compilation context used to contextify the HTML module's identifier when seeding the entry-name hash
822
+ * @param {boolean=} outputModule whether `output.module` is enabled; when true, classic `<script src>` tags get `type="module"` auto-inserted so the rewritten src can load the emitted ES module chunk
823
+ * @param {boolean=} css whether `experiments.css` is enabled; when true, inline `<style>` bodies are routed through the CSS pipeline as `data:text/css` modules
824
+ */
825
+ constructor(hashFunction, context, outputModule, css) {
826
+ super();
827
+ this.magicCommentContext = createMagicCommentContext();
828
+ this.hashFunction = hashFunction;
829
+ this.context = context;
830
+ this.outputModule = outputModule;
831
+ this.css = css;
832
+ }
833
+
834
+ /**
835
+ * Parses the provided source and updates the parser state.
836
+ * @param {string | Buffer | PreparsedAst} source the source to parse
837
+ * @param {ParserState} state the parser state
838
+ * @returns {ParserState} the parser state
839
+ */
840
+ parse(source, state) {
841
+ if (Buffer.isBuffer(source)) {
842
+ source = source.toString("utf8");
843
+ } else if (typeof source === "object") {
844
+ throw new Error("webpackAst is unexpected for the HtmlParser");
845
+ }
846
+ if (source[0] === "\uFEFF") {
847
+ source = source.slice(1);
848
+ }
849
+
850
+ const locConverter = new LocConverter(source);
851
+
852
+ const module = state.module;
853
+
854
+ // Stable, per-HTML-module prefix used when generating entry names for
855
+ // script src / modulepreload references so they don't collide across
856
+ // HTML modules in the same compilation. We hash the module's resource
857
+ // path (a plain absolute path) — going through `contextify` against
858
+ // the compilation root keeps the hash machine-stable for the same
859
+ // project layout. Note: `module.identifier()` returns `html|<path>`
860
+ // for HTML modules, which doesn't start with `/`, so contextify would
861
+ // leave it absolute. `module.resource` is the bare path.
862
+ /** @type {string} */
863
+ const resource =
864
+ /** @type {EXPECTED_ANY} */ (module).resource || module.identifier();
865
+ const moduleHash = createHash(this.hashFunction || "md4")
866
+ .update(this.context ? contextify(this.context, resource) : resource)
867
+ .digest("hex")
868
+ .slice(0, 8);
869
+
870
+ /** @typedef {{ nameStart: number, nameEnd: number, valueStart: number, valueEnd: number }} AttrToken */
871
+
872
+ /** @type {AttrToken[]} */
873
+ const pendingAttributes = [];
874
+
875
+ /**
876
+ * Reconciles the rewritten `<script>` tag's `type` attribute with the
877
+ * emitted chunk's actual format. Used by both the `<script src>` and
878
+ * inline `<script>` paths so the two stay in sync.
879
+ * @param {AttrToken | undefined} typeAttr existing `type` attribute, if any
880
+ * @param {number} nameEnd position right after `<script` (for inserts)
881
+ * @param {"classic" | "esm-script"} kind chunk kind decided by the parser
882
+ * @param {string} input full source string
883
+ * @returns {void}
884
+ */
885
+ const reconcileScriptTypeAttr = (typeAttr, nameEnd, kind, input) => {
886
+ if (this.outputModule && kind === "classic") {
887
+ // Chunk is an ES module; upgrade the tag.
888
+ if (typeAttr && typeAttr.valueStart !== -1) {
889
+ module.addPresentationalDependency(
890
+ new ConstDependency("module", [
891
+ typeAttr.valueStart,
892
+ typeAttr.valueEnd
893
+ ])
894
+ );
895
+ } else {
896
+ module.addPresentationalDependency(
897
+ new ConstDependency(' type="module"', nameEnd)
898
+ );
899
+ }
900
+ } else if (!this.outputModule && kind === "esm-script" && typeAttr) {
901
+ // Chunk is a classic IIFE; drop `type="module"` so the
902
+ // browser doesn't load it under module semantics.
903
+ let attrEnd;
904
+ if (typeAttr.valueStart === -1) {
905
+ attrEnd = typeAttr.nameEnd;
906
+ } else if (
907
+ input[typeAttr.valueEnd] === '"' ||
908
+ input[typeAttr.valueEnd] === "'"
909
+ ) {
910
+ attrEnd = typeAttr.valueEnd + 1;
911
+ } else {
912
+ attrEnd = typeAttr.valueEnd;
913
+ }
914
+ // Consume one leading whitespace char so we don't leave a
915
+ // double space between `<script` and the next attribute.
916
+ let attrStart = typeAttr.nameStart;
917
+ if (
918
+ attrStart > 0 &&
919
+ isASCIIWhitespace(input.charCodeAt(attrStart - 1))
920
+ ) {
921
+ attrStart -= 1;
922
+ }
923
+ module.addPresentationalDependency(
924
+ new ConstDependency("", [attrStart, attrEnd])
925
+ );
926
+ }
927
+ };
928
+
929
+ // Inline `<script>` body extraction is deferred to the matching
930
+ // `closeTag` event so the walker's script-data state machine
931
+ // (including its escaped/double-escaped sub-states) decides where
932
+ // the body ends. This is the spec-compliant way to find the close
933
+ // — a plain `</script>` regex would split too early inside
934
+ // `<!--<script>…</script>-->` patterns.
935
+ /** @type {null | { contentStart: number, attrs: Map<string, string>, typeAttr: AttrToken | undefined, nameEnd: number }} */
936
+ let pendingInlineScript = null;
937
+
938
+ // Script src / modulepreload references are collected per-category
939
+ // during the walk; HtmlModulesPlugin later turns them into real
940
+ // entries. Classic <script src> and <script type="module" src> are
941
+ // chained via a leader-only dependOn so they share a runtime.
942
+ // `<link rel="modulepreload">` entries are kept independent — they
943
+ // must preload without running, so they can never become a runtime
944
+ // leader that other entries would import.
945
+ /**
946
+ * @typedef {object} EntryScriptInfo
947
+ * @property {string} request
948
+ * @property {string} entryName
949
+ * @property {"classic" | "esm-script" | "modulepreload" | "stylesheet"} kind
950
+ */
951
+ /** @type {EntryScriptInfo[]} */
952
+ const classicEntries = [];
953
+ /** @type {EntryScriptInfo[]} */
954
+ const esmScriptEntries = [];
955
+ /** @type {EntryScriptInfo[]} */
956
+ const modulePreloadEntries = [];
957
+ /** @type {EntryScriptInfo[]} */
958
+ const stylesheetEntries = [];
959
+
960
+ let nextEntryIndex = 0;
961
+
962
+ /**
963
+ * Tracks the `webpackIgnore` value from the most recent comment that
964
+ * appears before the next tag. Reset whenever a tag is emitted or a
965
+ * comment without a `webpackIgnore` value is encountered.
966
+ * @type {boolean | undefined}
967
+ */
968
+ let pendingWebpackIgnore;
969
+
970
+ const magicCommentContext = this.magicCommentContext;
971
+
972
+ // TODO implement full HTML parser (WASM)
973
+ walkHtmlTokens(source, 0, {
974
+ comment: (input, start, end) => {
975
+ // Only proper `<!-- ... -->` comments carry magic comments.
976
+ // `walkHtmlTokens` also dispatches this callback for bogus
977
+ // comments such as `<!DOCTYPE …>` and `<?…>`, which must not
978
+ // be parsed as magic comments.
979
+ if (
980
+ end - start < 7 ||
981
+ input.charCodeAt(start) !== 0x3c /* < */ ||
982
+ input.charCodeAt(start + 1) !== 0x21 /* ! */ ||
983
+ input.charCodeAt(start + 2) !== 0x2d /* - */ ||
984
+ input.charCodeAt(start + 3) !== 0x2d /* - */ ||
985
+ input.charCodeAt(end - 1) !== 0x3e /* > */ ||
986
+ input.charCodeAt(end - 2) !== 0x2d /* - */ ||
987
+ input.charCodeAt(end - 3) !== 0x2d /* - */
988
+ ) {
989
+ pendingWebpackIgnore = undefined;
990
+ return end;
991
+ }
992
+ const contentStart = start + 4;
993
+ const contentEnd = end - 3;
994
+ const value = input.slice(contentStart, contentEnd);
995
+ if (!webpackCommentRegExp.test(value)) {
996
+ pendingWebpackIgnore = undefined;
997
+ return end;
998
+ }
999
+ /** @type {Record<string, EXPECTED_ANY>} */
1000
+ let options;
1001
+ try {
1002
+ options = vm.runInContext(
1003
+ `(function(){return {${value}};})()`,
1004
+ magicCommentContext
1005
+ );
1006
+ } catch (err) {
1007
+ const { line: sl, column: sc } = locConverter.get(start);
1008
+ const { line: el, column: ec } = locConverter.get(end);
1009
+ module.addWarning(
1010
+ new CommentCompilationWarning(
1011
+ `Compilation error while processing magic comment(-s): /*${value}*/: ${
1012
+ /** @type {Error} */ (err).message
1013
+ }`,
1014
+ {
1015
+ start: { line: sl, column: sc },
1016
+ end: { line: el, column: ec }
1017
+ }
1018
+ )
1019
+ );
1020
+ pendingWebpackIgnore = undefined;
1021
+ return end;
1022
+ }
1023
+ if (options.webpackIgnore === undefined) {
1024
+ pendingWebpackIgnore = undefined;
1025
+ return end;
1026
+ }
1027
+ if (typeof options.webpackIgnore !== "boolean") {
1028
+ const { line: sl, column: sc } = locConverter.get(start);
1029
+ const { line: el, column: ec } = locConverter.get(end);
1030
+ module.addWarning(
1031
+ new UnsupportedFeatureWarning(
1032
+ `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
1033
+ {
1034
+ start: { line: sl, column: sc },
1035
+ end: { line: el, column: ec }
1036
+ }
1037
+ )
1038
+ );
1039
+ pendingWebpackIgnore = undefined;
1040
+ return end;
1041
+ }
1042
+ pendingWebpackIgnore = options.webpackIgnore;
1043
+ return end;
1044
+ },
1045
+ attribute: (
1046
+ input,
1047
+ nameStart,
1048
+ nameEnd,
1049
+ valueStart,
1050
+ valueEnd,
1051
+ quoteType
1052
+ ) => {
1053
+ pendingAttributes.push({ nameStart, nameEnd, valueStart, valueEnd });
1054
+ if (valueStart === -1) return nameEnd;
1055
+ return quoteType !== walkHtmlTokens.QUOTE_NONE
1056
+ ? valueEnd + 1
1057
+ : valueEnd;
1058
+ },
1059
+ closeTag: (input, start, end, nameStart, nameEnd) => {
1060
+ pendingWebpackIgnore = undefined;
1061
+ if (pendingInlineScript) {
1062
+ const elName = input.slice(nameStart, nameEnd).toLowerCase();
1063
+ if (elName === "script") {
1064
+ const ps = pendingInlineScript;
1065
+ pendingInlineScript = null;
1066
+ const contentStart = ps.contentStart;
1067
+ const contentEnd = start; // start of `</script>`
1068
+ const jsContent = input.slice(contentStart, contentEnd);
1069
+ if (jsContent.trim() === "") return end;
1070
+
1071
+ // Base64-encode the JS body so the data URI round-trips
1072
+ // arbitrary JavaScript text, including non-ASCII source
1073
+ // (`decodeDataURI` decodes non-base64 bodies as ASCII,
1074
+ // which would corrupt Unicode string literals or
1075
+ // identifiers).
1076
+ const request = `data:text/javascript;base64,${Buffer.from(
1077
+ jsContent,
1078
+ "utf8"
1079
+ ).toString("base64")}`;
1080
+
1081
+ const useEsmEntry = isModuleScript(ps.attrs);
1082
+ const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1083
+ /** @type {"classic" | "esm-script"} */
1084
+ const kind = useEsmEntry ? "esm-script" : "classic";
1085
+ const { line: sl, column: sc } = locConverter.get(contentStart);
1086
+ const { line: el, column: ec } = locConverter.get(contentEnd);
1087
+ const dep = new HtmlInlineScriptDependency(
1088
+ request,
1089
+ ps.nameEnd,
1090
+ [contentStart, contentEnd],
1091
+ entryName,
1092
+ useEsmEntry ? "esm" : "commonjs"
1093
+ );
1094
+ dep.setLoc(sl, sc, el, ec);
1095
+ module.addPresentationalDependency(dep);
1096
+ reconcileScriptTypeAttr(ps.typeAttr, ps.nameEnd, kind, input);
1097
+ const collection =
1098
+ kind === "classic" ? classicEntries : esmScriptEntries;
1099
+ collection.push({ request, entryName, kind });
1100
+ }
1101
+ }
1102
+ return end;
1103
+ },
1104
+ openTag: (input, start, end, nameStart, nameEnd) => {
1105
+ const ignore = pendingWebpackIgnore === true;
1106
+ pendingWebpackIgnore = undefined;
1107
+ if (ignore) {
1108
+ // For `<script>` and `<style>` we don't emit a dependency,
1109
+ // but we must NOT advance past the close tag either: the
1110
+ // walker is already in script-data/rawtext state for
1111
+ // those tags and will consume the body and emit the
1112
+ // matching `closeTag` itself. Returning a position past
1113
+ // `</script>`/`</style>` would leave the walker stuck in
1114
+ // rawtext mode, swallowing later markup.
1115
+ pendingAttributes.length = 0;
1116
+ return end;
1117
+ }
1118
+ const elementName = input.slice(nameStart, nameEnd).toLowerCase();
1119
+
1120
+ // `<style>` is rawtext: capture the inline CSS and hand it to
1121
+ // the CSS pipeline as a virtual `data:text/css` module with
1122
+ // `exportType: "text"`. We use the regex only to discover the
1123
+ // `</style>` position so we can slice the body; the walker is
1124
+ // already in rawtext state for `<style>` and will emit the
1125
+ // matching `closeTag` event itself, so we must return `end`
1126
+ // (the position right after the opening tag's `>`) rather
1127
+ // than advancing past `</style>`. Only `<style>` tags whose
1128
+ // `type` attribute is absent, empty, or `text/css` are
1129
+ // processed; other types are left untouched.
1130
+ if (elementName === "style") {
1131
+ /** @type {string | undefined} */
1132
+ let typeValue;
1133
+ for (const attr of pendingAttributes) {
1134
+ const attrName = input
1135
+ .slice(attr.nameStart, attr.nameEnd)
1136
+ .toLowerCase();
1137
+ if (attrName === "type") {
1138
+ typeValue =
1139
+ attr.valueStart !== -1
1140
+ ? input.slice(attr.valueStart, attr.valueEnd)
1141
+ : "";
1142
+ break;
1143
+ }
1144
+ }
1145
+ pendingAttributes.length = 0;
1146
+
1147
+ STYLE_END_REGEXP.lastIndex = end;
1148
+ const closeMatch = STYLE_END_REGEXP.exec(input);
1149
+ if (!closeMatch) return end;
1150
+ const contentStart = end;
1151
+ const contentEnd = closeMatch.index;
1152
+
1153
+ const trimmedType =
1154
+ typeValue !== undefined ? typeValue.trim().toLowerCase() : "";
1155
+ if (
1156
+ typeValue !== undefined &&
1157
+ trimmedType !== "" &&
1158
+ trimmedType !== "text/css"
1159
+ ) {
1160
+ return end;
1161
+ }
1162
+
1163
+ // Inline-style processing requires the CSS pipeline; when
1164
+ // `experiments.css` is off, leave the body alone — the
1165
+ // walker is in rawtext mode for `<style>` and will skip
1166
+ // over the body to the matching `</style>` itself.
1167
+ if (!this.css) {
1168
+ return end;
1169
+ }
1170
+
1171
+ const cssContent = input.slice(contentStart, contentEnd);
1172
+ if (cssContent.trim() === "") {
1173
+ return end;
1174
+ }
1175
+
1176
+ // URL-encode the CSS body so the data URI parser's `(.*)$`
1177
+ // body group matches even when the source has newlines or
1178
+ // other characters that would otherwise break the regex.
1179
+ const request = `data:text/css,${encodeURIComponent(cssContent)}`;
1180
+
1181
+ const { line: sl, column: sc } = locConverter.get(contentStart);
1182
+ const { line: el, column: ec } = locConverter.get(contentEnd);
1183
+ const dep = new HtmlInlineStyleDependency(request, [
1184
+ contentStart,
1185
+ contentEnd
1186
+ ]);
1187
+ dep.setLoc(sl, sc, el, ec);
1188
+ module.addDependency(dep);
1189
+ module.addCodeGenerationDependency(dep);
1190
+ return end;
1191
+ }
1192
+
1193
+ const sources = DEFAULT_SOURCES.get(elementName);
1194
+
1195
+ if (!sources) {
1196
+ pendingAttributes.length = 0;
1197
+ return end;
1198
+ }
1199
+
1200
+ /** @type {Map<string, string> | undefined} */
1201
+ let attributesMap;
1202
+ const getAttributesMap = () => {
1203
+ if (attributesMap) return attributesMap;
1204
+ attributesMap = new Map();
1205
+ for (const attr of pendingAttributes) {
1206
+ const name = input
1207
+ .slice(attr.nameStart, attr.nameEnd)
1208
+ .toLowerCase();
1209
+ const value =
1210
+ attr.valueStart !== -1
1211
+ ? input.slice(attr.valueStart, attr.valueEnd)
1212
+ : "";
1213
+ attributesMap.set(name, value);
1214
+ }
1215
+ return attributesMap;
1216
+ };
1217
+
1218
+ for (const attr of pendingAttributes) {
1219
+ const attributeName = input
1220
+ .slice(attr.nameStart, attr.nameEnd)
1221
+ .toLowerCase();
1222
+ const sourceItem = sources.get(attributeName);
1223
+
1224
+ if (!sourceItem) continue;
1225
+
1226
+ // TODO(html-entities): We should ideally decode entities here using
1227
+ // `walkHtmlTokens.decodeHtmlEntities(input.slice(...))` so that URLs
1228
+ // like `image.png?a=1&amp;b=2` are correctly resolved as `&`.
1229
+ // However, doing so currently breaks `srcset` parsing tests (e.g. `errors.js`)
1230
+ // which explicitly expect whitespace entities like `&#x9;` to NOT be decoded
1231
+ // before the srcset parser runs. A follow-up PR should implement selective
1232
+ // decoding for specific URL attributes.
1233
+ const attributeValue =
1234
+ attr.valueStart !== -1
1235
+ ? input.slice(attr.valueStart, attr.valueEnd)
1236
+ : "";
1237
+
1238
+ if (!attributeValue) continue;
1239
+
1240
+ if (
1241
+ typeof sourceItem.filter === "function" &&
1242
+ !sourceItem.filter(getAttributesMap())
1243
+ ) {
1244
+ continue;
1245
+ }
1246
+
1247
+ /** @type {ParsedSource[] | undefined} */
1248
+ let parsedAttributeValue;
1249
+
1250
+ try {
1251
+ parsedAttributeValue = sourceItem.parse(attributeValue);
1252
+ } catch (err) {
1253
+ const { line: sl, column: sc } = locConverter.get(attr.valueStart);
1254
+ const { line: el, column: ec } = locConverter.get(attr.valueEnd);
1255
+
1256
+ module.addError(
1257
+ new ModuleDependencyError(
1258
+ module,
1259
+ new WebpackError(
1260
+ `Bad value for attribute "${attributeName}" on element "${elementName}": ${
1261
+ /** @type {Error} */ (err).message
1262
+ }`
1263
+ ),
1264
+ {
1265
+ start: { line: sl, column: sc },
1266
+ end: { line: el, column: ec }
1267
+ }
1268
+ )
1269
+ );
1270
+ }
1271
+
1272
+ if (!parsedAttributeValue) continue;
1273
+
1274
+ // `<link rel="stylesheet">` is upgraded to an entry only when
1275
+ // `experiments.css` is on — that's the mode where webpack can
1276
+ // bundle the CSS into its own chunk. Without it, the
1277
+ // stylesheet href stays a plain asset URL. Scope this to the
1278
+ // `href` attribute only: `<link>` also exposes
1279
+ // `imagesrcset` URLs which must continue to flow through
1280
+ // the regular asset rewriting path even on a stylesheet
1281
+ // link.
1282
+ const isStylesheetEntry =
1283
+ this.css &&
1284
+ elementName === "link" &&
1285
+ attributeName === "href" &&
1286
+ isLinkStylesheet(getAttributesMap());
1287
+ const isEntry =
1288
+ isStylesheetEntry ||
1289
+ sourceItem.entry === true ||
1290
+ (typeof sourceItem.entry === "function" &&
1291
+ sourceItem.entry(getAttributesMap()));
1292
+
1293
+ // `<script type="module" src>` and `<link rel="modulepreload">`
1294
+ // reference ES modules; everything else under `<script src>` is a
1295
+ // classic script. The category drives ESM vs CommonJS resolution
1296
+ // of the entry — the chunk format is controlled by the user via
1297
+ // `output.module` / `experiments.outputModule`.
1298
+ const useEsmEntry =
1299
+ (elementName === "script" && isModuleScript(getAttributesMap())) ||
1300
+ sourceItem.entryCategory === "esm";
1301
+
1302
+ for (const parsedSource of parsedAttributeValue) {
1303
+ const [value, innerStart, innerEnd] = parsedSource;
1304
+ if (value.startsWith("#")) continue;
1305
+ const sourceStart = attr.valueStart + innerStart;
1306
+ const sourceEnd = attr.valueStart + innerEnd;
1307
+ const { line: sl, column: sc } = locConverter.get(sourceStart);
1308
+ const { line: el, column: ec } = locConverter.get(sourceEnd);
1309
+ if (isEntry) {
1310
+ const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1311
+ const isStylesheetLink =
1312
+ elementName === "link" && isLinkStylesheet(getAttributesMap());
1313
+ /** @type {"classic" | "esm-script" | "modulepreload" | "stylesheet"} */
1314
+ const kind =
1315
+ elementName === "link"
1316
+ ? isStylesheetLink
1317
+ ? "stylesheet"
1318
+ : "modulepreload"
1319
+ : useEsmEntry
1320
+ ? "esm-script"
1321
+ : "classic";
1322
+ // With `output.module` enabled, a classic `<script src>` is
1323
+ // upgraded in place to `<script type="module" src>` (see the
1324
+ // ConstDependency insertion below). Account for that in the
1325
+ // dependency's `elementKind` so sibling tags emitted by the
1326
+ // template for additional entry chunks (runtime / split chunks)
1327
+ // also use `type="module"`.
1328
+ const willBeModuleScript =
1329
+ kind === "esm-script" ||
1330
+ (this.outputModule &&
1331
+ kind === "classic" &&
1332
+ elementName === "script");
1333
+ /** @type {"script-classic" | "script-module" | "modulepreload" | "stylesheet"} */
1334
+ const elementKind =
1335
+ kind === "modulepreload"
1336
+ ? "modulepreload"
1337
+ : kind === "stylesheet"
1338
+ ? "stylesheet"
1339
+ : willBeModuleScript
1340
+ ? "script-module"
1341
+ : "script-classic";
1342
+ const dep = new HtmlScriptSrcDependency(
1343
+ value,
1344
+ [sourceStart, sourceEnd],
1345
+ entryName,
1346
+ // `<link rel="stylesheet">` is bundled as a CSS entry —
1347
+ // using a non-"url" category so the default `.css` rule
1348
+ // (which gives the resolved module the CSS module type)
1349
+ // wins over the `dependency: "url"` → asset rule.
1350
+ kind === "stylesheet"
1351
+ ? "css-import"
1352
+ : useEsmEntry
1353
+ ? "esm"
1354
+ : sourceItem.entryCategory,
1355
+ elementKind,
1356
+ start,
1357
+ end
1358
+ );
1359
+ dep.setLoc(sl, sc, el, ec);
1360
+ module.addPresentationalDependency(dep);
1361
+ // Reconcile the rewritten `<script>` tag's `type`
1362
+ // attribute with the chunk's actual format. See
1363
+ // `reconcileScriptTypeAttr` for the rules.
1364
+ if (
1365
+ elementName === "script" &&
1366
+ (kind === "classic" || kind === "esm-script")
1367
+ ) {
1368
+ /** @type {AttrToken | undefined} */
1369
+ let typeAttr;
1370
+ for (const a of pendingAttributes) {
1371
+ if (
1372
+ input.slice(a.nameStart, a.nameEnd).toLowerCase() === "type"
1373
+ ) {
1374
+ typeAttr = a;
1375
+ break;
1376
+ }
1377
+ }
1378
+ reconcileScriptTypeAttr(typeAttr, nameEnd, kind, input);
1379
+ }
1380
+ const collection =
1381
+ kind === "classic"
1382
+ ? classicEntries
1383
+ : kind === "esm-script"
1384
+ ? esmScriptEntries
1385
+ : kind === "stylesheet"
1386
+ ? stylesheetEntries
1387
+ : modulePreloadEntries;
1388
+ collection.push({ request: value, entryName, kind });
1389
+ } else {
1390
+ const dep = new HtmlSourceDependency(value, [
1391
+ sourceStart,
1392
+ sourceEnd
1393
+ ]);
1394
+ dep.setLoc(sl, sc, el, ec);
1395
+ module.addDependency(dep);
1396
+ module.addCodeGenerationDependency(dep);
1397
+ }
1398
+ }
1399
+ }
1400
+
1401
+ // `<script>` is rawtext (the "script data state" in the HTML
1402
+ // tokenizer): its body must never be reparsed as HTML,
1403
+ // regardless of whether the tag has a `src` attribute. The
1404
+ // walker is already in script-data state for `<script>` and
1405
+ // will emit the matching `closeTag` event itself, so we
1406
+ // return `end` and defer body extraction to the closeTag
1407
+ // callback (which gets the spec-correct `</script>` position
1408
+ // even in escaped/double-escaped script-data sub-states).
1409
+ // When the tag has no `src` and its body is non-empty, the
1410
+ // closeTag handler bundles the inline JS as its own entry —
1411
+ // the same pipeline that processes `<script src>` — by
1412
+ // issuing a `data:text/javascript;base64,...` virtual request
1413
+ // and adding a dependency that rewrites the tag to
1414
+ // `<script src="…">` at render time. Only `<script>` tags
1415
+ // whose `type` attribute is absent, empty, or a recognized
1416
+ // JS mimetype are processed as JS; other types (e.g.
1417
+ // `application/ld+json`, `importmap`) are left untouched.
1418
+ if (elementName === "script") {
1419
+ const attrs = getAttributesMap();
1420
+ // Use attribute presence, not value: a `<script src>` with
1421
+ // an empty or valueless `src` still ignores its inline
1422
+ // body in the browser, so we must not bundle the body.
1423
+ const hasSrc = attrs.has("src");
1424
+
1425
+ /** @type {AttrToken | undefined} */
1426
+ let typeAttr;
1427
+ for (const a of pendingAttributes) {
1428
+ if (input.slice(a.nameStart, a.nameEnd).toLowerCase() === "type") {
1429
+ typeAttr = a;
1430
+ break;
1431
+ }
1432
+ }
1433
+ pendingAttributes.length = 0;
1434
+
1435
+ if (hasSrc || !isExecutableJsScript(attrs)) {
1436
+ // `<script src>` body is ignored by the browser, and
1437
+ // non-JS `<script type>` (e.g. importmap, JSON-LD)
1438
+ // passes through unchanged. Either way the walker
1439
+ // consumes the body and emits the close tag itself.
1440
+ return end;
1441
+ }
1442
+
1443
+ pendingInlineScript = {
1444
+ contentStart: end,
1445
+ attrs,
1446
+ typeAttr,
1447
+ nameEnd
1448
+ };
1449
+ return end;
1450
+ }
1451
+
1452
+ pendingAttributes.length = 0;
1453
+ return end;
1454
+ }
1455
+ });
1456
+
1457
+ const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
1458
+ buildInfo.strict = true;
1459
+ // Hand off the collected entries to HtmlModulesPlugin; it creates the
1460
+ // real compilation entries during the finishMake hook. The classic
1461
+ // and esm-script groups are chained via a leader-only dependOn so
1462
+ // they share a runtime; modulepreload entries are emitted as
1463
+ // independent entries since `<link rel=modulepreload>` must preload
1464
+ // without running.
1465
+ if (
1466
+ classicEntries.length > 0 ||
1467
+ esmScriptEntries.length > 0 ||
1468
+ modulePreloadEntries.length > 0 ||
1469
+ stylesheetEntries.length > 0
1470
+ ) {
1471
+ /** @type {Record<string, EntryScriptInfo[]>} */
1472
+ (buildInfo.htmlEntryScripts) = {
1473
+ classic: classicEntries,
1474
+ "esm-script": esmScriptEntries,
1475
+ modulepreload: modulePreloadEntries,
1476
+ stylesheet: stylesheetEntries
1477
+ };
1478
+ }
1479
+
1480
+ const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
1481
+ buildMeta.exportsType = "default";
1482
+
1483
+ state.module.addDependency(new StaticExportsDependency(["default"], true));
1484
+
1485
+ return state;
1486
+ }
1487
+ }
1488
+
1489
+ module.exports = HtmlParser;