webpack 5.107.1 → 5.108.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 (360) hide show
  1. package/hot/dev-server.js +2 -0
  2. package/lib/APIPlugin.js +8 -4
  3. package/lib/CacheFacade.js +7 -0
  4. package/lib/Chunk.js +4 -0
  5. package/lib/ChunkGraph.js +30 -9
  6. package/lib/CircularModulesPlugin.js +190 -0
  7. package/lib/CleanPlugin.js +2 -1
  8. package/lib/Compilation.js +396 -65
  9. package/lib/Compiler.js +52 -14
  10. package/lib/ConcatenationScope.js +10 -3
  11. package/lib/ContextExclusionPlugin.js +1 -0
  12. package/lib/ContextModule.js +92 -47
  13. package/lib/ContextModuleFactory.js +45 -38
  14. package/lib/ContextReplacementPlugin.js +4 -0
  15. package/lib/DefinePlugin.js +89 -15
  16. package/lib/Dependency.js +111 -7
  17. package/lib/EntryOptionPlugin.js +39 -2
  18. package/lib/EntryPlugin.js +2 -0
  19. package/lib/ExportsInfo.js +314 -73
  20. package/lib/ExternalModule.js +82 -34
  21. package/lib/ExternalModuleFactoryPlugin.js +1 -0
  22. package/lib/ExternalsPlugin.js +1 -0
  23. package/lib/FileSystemInfo.js +173 -23
  24. package/lib/FlagAllModulesAsUsedPlugin.js +1 -5
  25. package/lib/FlagDependencyExportsPlugin.js +23 -0
  26. package/lib/FlagDependencyUsagePlugin.js +87 -5
  27. package/lib/FlagEntryExportAsUsedPlugin.js +2 -0
  28. package/lib/Generator.js +8 -0
  29. package/lib/HotModuleReplacementPlugin.js +65 -28
  30. package/lib/IgnorePlugin.js +1 -0
  31. package/lib/InitFragment.js +5 -0
  32. package/lib/JavascriptMetaInfoPlugin.js +7 -5
  33. package/lib/LazyBarrel.js +361 -0
  34. package/lib/LoaderTargetPlugin.js +1 -0
  35. package/lib/Module.js +21 -42
  36. package/lib/ModuleGraph.js +3 -2
  37. package/lib/ModuleProfile.js +27 -1
  38. package/lib/ModuleTemplate.js +2 -0
  39. package/lib/MultiCompiler.js +16 -0
  40. package/lib/MultiStats.js +1 -0
  41. package/lib/MultiWatching.js +2 -0
  42. package/lib/NodeStuffPlugin.js +22 -17
  43. package/lib/NormalModule.js +557 -77
  44. package/lib/NormalModuleReplacementPlugin.js +2 -0
  45. package/lib/PrefetchPlugin.js +2 -0
  46. package/lib/ProgressPlugin.js +9 -1
  47. package/lib/ProvidePlugin.js +1 -0
  48. package/lib/RawModule.js +20 -16
  49. package/lib/RecordIdsPlugin.js +1 -0
  50. package/lib/RuntimeGlobals.js +5 -0
  51. package/lib/RuntimePlugin.js +58 -26
  52. package/lib/RuntimeTemplate.js +278 -21
  53. package/lib/SelfModuleFactory.js +1 -0
  54. package/lib/SourceMapDevToolPlugin.js +105 -30
  55. package/lib/Stats.js +1 -0
  56. package/lib/Template.js +8 -2
  57. package/lib/TemplatedPathPlugin.js +473 -131
  58. package/lib/WarnCaseSensitiveModulesPlugin.js +1 -0
  59. package/lib/WarnDeprecatedOptionPlugin.js +4 -0
  60. package/lib/WarnNoModeSetPlugin.js +1 -0
  61. package/lib/WatchIgnorePlugin.js +1 -0
  62. package/lib/Watching.js +9 -0
  63. package/lib/WebpackOptionsApply.js +50 -5
  64. package/lib/asset/AssetBytesGenerator.js +11 -3
  65. package/lib/asset/AssetGenerator.js +47 -22
  66. package/lib/asset/AssetModule.js +47 -0
  67. package/lib/asset/AssetModulesPlugin.js +14 -14
  68. package/lib/asset/AssetParser.js +2 -2
  69. package/lib/asset/AssetSourceGenerator.js +8 -0
  70. package/lib/asset/RawDataUrlModule.js +12 -13
  71. package/lib/buildChunkGraph.js +88 -9
  72. package/lib/bun/BunTargetPlugin.js +48 -0
  73. package/lib/cache/AddBuildDependenciesPlugin.js +1 -0
  74. package/lib/cache/AddManagedPathsPlugin.js +3 -0
  75. package/lib/cache/IdleFileCachePlugin.js +4 -0
  76. package/lib/cache/MemoryWithGcCachePlugin.js +1 -0
  77. package/lib/cache/PackFileCacheStrategy.js +1 -0
  78. package/lib/cache/ResolverCachePlugin.js +2 -0
  79. package/lib/cache/getLazyHashedEtag.js +9 -2
  80. package/lib/cache/mergeEtags.js +2 -0
  81. package/lib/cli.js +109 -19
  82. package/lib/config/browserslistTargetHandler.js +99 -0
  83. package/lib/config/defaults.js +147 -7
  84. package/lib/config/defineConfig.js +31 -0
  85. package/lib/config/normalization.js +5 -0
  86. package/lib/config/target.js +181 -2
  87. package/lib/container/ContainerEntryModule.js +15 -14
  88. package/lib/container/ContainerExposedDependency.js +2 -2
  89. package/lib/container/ContainerReferencePlugin.js +1 -1
  90. package/lib/container/FallbackDependency.js +5 -7
  91. package/lib/container/FallbackModule.js +10 -9
  92. package/lib/container/RemoteModule.js +20 -10
  93. package/lib/container/RemoteRuntimeModule.js +14 -12
  94. package/lib/css/CssGenerator.js +353 -327
  95. package/lib/css/CssInjectStyleRuntimeModule.js +36 -8
  96. package/lib/css/CssLoadingRuntimeModule.js +118 -47
  97. package/lib/css/CssModule.js +52 -23
  98. package/lib/css/CssModulesPlugin.js +107 -124
  99. package/lib/css/CssParser.js +2759 -2205
  100. package/lib/css/syntax.js +2859 -0
  101. package/lib/debug/ProfilingPlugin.js +2 -0
  102. package/lib/deno/DenoTargetPlugin.js +47 -0
  103. package/lib/dependencies/AMDDefineDependency.js +22 -17
  104. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +1 -0
  105. package/lib/dependencies/AMDRequireArrayDependency.js +7 -11
  106. package/lib/dependencies/AMDRequireContextDependency.js +7 -11
  107. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +1 -0
  108. package/lib/dependencies/AMDRequireDependency.js +24 -20
  109. package/lib/dependencies/CachedConstDependency.js +3 -0
  110. package/lib/dependencies/CommonJsDependencyHelpers.js +1 -0
  111. package/lib/dependencies/CommonJsExportRequireDependency.js +79 -31
  112. package/lib/dependencies/CommonJsExportsDependency.js +21 -16
  113. package/lib/dependencies/CommonJsExportsParserPlugin.js +230 -7
  114. package/lib/dependencies/CommonJsFullRequireDependency.js +27 -19
  115. package/lib/dependencies/CommonJsImportsParserPlugin.js +51 -16
  116. package/lib/dependencies/CommonJsPlugin.js +1 -1
  117. package/lib/dependencies/CommonJsRequireContextDependency.js +10 -13
  118. package/lib/dependencies/CommonJsRequireDependency.js +42 -11
  119. package/lib/dependencies/CommonJsSelfReferenceDependency.js +22 -14
  120. package/lib/dependencies/ConstDependency.js +16 -11
  121. package/lib/dependencies/ContextDependency.js +4 -0
  122. package/lib/dependencies/ContextDependencyTemplateAsId.js +1 -1
  123. package/lib/dependencies/ContextElementDependency.js +23 -14
  124. package/lib/dependencies/CreateRequireParserPlugin.js +1 -0
  125. package/lib/dependencies/CreateScriptUrlDependency.js +5 -7
  126. package/lib/dependencies/CriticalDependencyWarning.js +1 -0
  127. package/lib/dependencies/CssIcssExportDependency.js +512 -574
  128. package/lib/dependencies/CssIcssImportDependency.js +9 -9
  129. package/lib/dependencies/CssIcssSymbolDependency.js +22 -15
  130. package/lib/dependencies/CssImportDependency.js +25 -1
  131. package/lib/dependencies/CssUrlDependency.js +23 -14
  132. package/lib/dependencies/DllEntryDependency.js +9 -13
  133. package/lib/dependencies/ExportBindingInitFragment.js +164 -0
  134. package/lib/dependencies/ExportsInfoDependency.js +12 -12
  135. package/lib/dependencies/ExternalModuleDependency.js +8 -5
  136. package/lib/dependencies/ExternalModuleInitFragment.js +7 -5
  137. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +13 -10
  138. package/lib/dependencies/HarmonyAcceptDependency.js +11 -11
  139. package/lib/dependencies/HarmonyAcceptImportDependency.js +1 -0
  140. package/lib/dependencies/HarmonyDetectionParserPlugin.js +47 -22
  141. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +28 -2
  142. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +46 -22
  143. package/lib/dependencies/HarmonyExportExpressionDependency.js +107 -30
  144. package/lib/dependencies/HarmonyExportHeaderDependency.js +7 -9
  145. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +109 -31
  146. package/lib/dependencies/HarmonyExportInitFragment.js +22 -20
  147. package/lib/dependencies/HarmonyExportSpecifierDependency.js +83 -18
  148. package/lib/dependencies/HarmonyExports.js +4 -1
  149. package/lib/dependencies/HarmonyImportDependency.js +24 -0
  150. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +110 -74
  151. package/lib/dependencies/HarmonyImportGuard.js +254 -0
  152. package/lib/dependencies/HarmonyImportSideEffectDependency.js +18 -1
  153. package/lib/dependencies/HarmonyImportSpecifierDependency.js +118 -14
  154. package/lib/dependencies/HarmonyLinkingError.js +1 -0
  155. package/lib/dependencies/HarmonyModulesPlugin.js +1 -0
  156. package/lib/dependencies/{HtmlScriptSrcDependency.js → HtmlEntryDependency.js} +267 -154
  157. package/lib/dependencies/HtmlInlineHtmlDependency.js +107 -0
  158. package/lib/dependencies/HtmlInlineScriptDependency.js +20 -27
  159. package/lib/dependencies/HtmlInlineStyleDependency.js +62 -11
  160. package/lib/dependencies/HtmlSourceDependency.js +18 -0
  161. package/lib/dependencies/ImportContextDependency.js +5 -9
  162. package/lib/dependencies/ImportDependency.js +44 -2
  163. package/lib/dependencies/ImportMetaHotAcceptDependency.js +1 -0
  164. package/lib/dependencies/ImportMetaHotDeclineDependency.js +1 -0
  165. package/lib/dependencies/ImportMetaPlugin.js +74 -30
  166. package/lib/dependencies/ImportParserPlugin.js +19 -0
  167. package/lib/dependencies/ImportWeakDependency.js +1 -0
  168. package/lib/dependencies/JsonExportsDependency.js +9 -9
  169. package/lib/dependencies/LoaderImportDependency.js +1 -0
  170. package/lib/dependencies/LocalModule.js +11 -12
  171. package/lib/dependencies/LocalModuleDependency.js +11 -13
  172. package/lib/dependencies/ModuleDecoratorDependency.js +9 -9
  173. package/lib/dependencies/ModuleDependency.js +7 -0
  174. package/lib/dependencies/ModuleHotAcceptDependency.js +1 -0
  175. package/lib/dependencies/ModuleHotDeclineDependency.js +1 -0
  176. package/lib/dependencies/ModuleInitFragmentDependency.js +15 -11
  177. package/lib/dependencies/ProvidedDependency.js +31 -27
  178. package/lib/dependencies/PureExpressionDependency.js +7 -9
  179. package/lib/dependencies/RequireEnsureDependency.js +12 -13
  180. package/lib/dependencies/RequireHeaderDependency.js +3 -4
  181. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +3 -0
  182. package/lib/dependencies/RequireResolveContextDependency.js +7 -11
  183. package/lib/dependencies/RequireResolveDependency.js +1 -0
  184. package/lib/dependencies/RequireResolveHeaderDependency.js +3 -5
  185. package/lib/dependencies/RuntimeRequirementsDependency.js +6 -7
  186. package/lib/dependencies/StaticExportsDependency.js +10 -10
  187. package/lib/dependencies/SystemPlugin.js +2 -0
  188. package/lib/dependencies/URLContextDependency.js +5 -7
  189. package/lib/dependencies/URLDependency.js +14 -16
  190. package/lib/dependencies/UnsupportedDependency.js +8 -13
  191. package/lib/dependencies/WebAssemblyExportImportedDependency.js +11 -16
  192. package/lib/dependencies/WebAssemblyImportDependency.js +14 -16
  193. package/lib/dependencies/WebpackIsIncludedDependency.js +1 -0
  194. package/lib/dependencies/WorkerDependency.js +19 -8
  195. package/lib/dependencies/WorkerPlugin.js +40 -10
  196. package/lib/dependencies/processExportInfo.js +13 -11
  197. package/lib/dll/DelegatedModule.js +29 -15
  198. package/lib/dll/DelegatedModuleFactoryPlugin.js +1 -0
  199. package/lib/dll/DelegatedPlugin.js +1 -0
  200. package/lib/dll/DllEntryPlugin.js +3 -0
  201. package/lib/dll/DllModule.js +3 -2
  202. package/lib/dll/DllPlugin.js +16 -0
  203. package/lib/dll/DllReferencePlugin.js +3 -0
  204. package/lib/dll/LibManifestPlugin.js +1 -0
  205. package/lib/electron/ElectronTargetPlugin.js +22 -4
  206. package/lib/errors/ConcurrentCompilationError.js +1 -0
  207. package/lib/errors/HookWebpackError.js +11 -13
  208. package/lib/errors/IgnoreErrorModuleFactory.js +1 -0
  209. package/lib/errors/InvalidDependenciesModuleWarning.js +2 -0
  210. package/lib/errors/JSONParseError.js +9 -8
  211. package/lib/errors/ModuleBuildError.js +16 -13
  212. package/lib/errors/ModuleDependencyError.js +8 -1
  213. package/lib/errors/ModuleDependencyWarning.js +8 -1
  214. package/lib/errors/ModuleError.js +5 -11
  215. package/lib/errors/ModuleHashingError.js +4 -0
  216. package/lib/errors/ModuleNotFoundError.js +3 -0
  217. package/lib/errors/ModuleParseError.js +5 -11
  218. package/lib/errors/ModuleRestoreError.js +2 -0
  219. package/lib/errors/ModuleStoreError.js +3 -0
  220. package/lib/errors/ModuleWarning.js +7 -11
  221. package/lib/errors/NodeStuffInWebError.js +1 -0
  222. package/lib/errors/NonErrorEmittedError.js +2 -0
  223. package/lib/errors/UnhandledSchemeError.js +1 -0
  224. package/lib/esm/ModuleChunkLoadingPlugin.js +0 -1
  225. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +28 -23
  226. package/lib/hmr/HotModuleReplacement.runtime.js +175 -30
  227. package/lib/hmr/HotModuleReplacementRuntimeModule.js +7 -0
  228. package/lib/hmr/JavascriptHotModuleReplacement.runtime.js +173 -26
  229. package/lib/hmr/LazyCompilationPlugin.js +134 -6
  230. package/lib/html/HtmlGenerator.js +292 -50
  231. package/lib/html/HtmlModule.js +40 -0
  232. package/lib/html/HtmlModulesPlugin.js +287 -60
  233. package/lib/html/HtmlParser.js +1108 -1099
  234. package/lib/html/{walkHtmlTokens.js → syntax.js} +4701 -212
  235. package/lib/ids/IdHelpers.js +4 -2
  236. package/lib/index.js +19 -0
  237. package/lib/javascript/BasicEvaluatedExpression.js +1 -0
  238. package/lib/javascript/EnableChunkLoadingPlugin.js +1 -0
  239. package/lib/javascript/JavascriptGenerator.js +6 -2
  240. package/lib/javascript/JavascriptModule.js +54 -0
  241. package/lib/javascript/JavascriptModulesPlugin.js +257 -102
  242. package/lib/javascript/JavascriptParser.js +286 -159
  243. package/lib/json/JsonModule.js +40 -0
  244. package/lib/json/JsonModulesPlugin.js +7 -0
  245. package/lib/json/JsonParser.js +4 -2
  246. package/lib/library/AssignLibraryPlugin.js +5 -3
  247. package/lib/library/ExportPropertyLibraryPlugin.js +1 -0
  248. package/lib/library/FalseIIFEUmdWarning.js +1 -0
  249. package/lib/library/ModuleLibraryPlugin.js +54 -29
  250. package/lib/library/SystemLibraryPlugin.js +3 -3
  251. package/lib/node/NodeTargetPlugin.js +1 -0
  252. package/lib/node/NodeWatchFileSystem.js +38 -22
  253. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +10 -9
  254. package/lib/node/RequireChunkLoadingRuntimeModule.js +18 -16
  255. package/lib/optimize/ConcatenatedModule.js +397 -78
  256. package/lib/optimize/ConstExportsPlugin.js +211 -0
  257. package/lib/optimize/InlineExports.js +178 -0
  258. package/lib/optimize/InnerGraph.js +372 -275
  259. package/lib/optimize/InnerGraphPlugin.js +196 -99
  260. package/lib/optimize/ModuleConcatenationPlugin.js +56 -25
  261. package/lib/optimize/RealContentHashPlugin.js +14 -1
  262. package/lib/optimize/SideEffectsFlagPlugin.js +234 -64
  263. package/lib/runtime/AsyncModuleRuntimeModule.js +32 -30
  264. package/lib/runtime/AutoPublicPathRuntimeModule.js +11 -5
  265. package/lib/runtime/CompatGetDefaultExportRuntimeModule.js +1 -1
  266. package/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js +6 -4
  267. package/lib/runtime/DefinePropertyGettersRuntimeModule.js +33 -4
  268. package/lib/runtime/GetChunkFilenameRuntimeModule.js +82 -3
  269. package/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js +1 -1
  270. package/lib/runtime/HasOwnPropertyRuntimeModule.js +1 -1
  271. package/lib/runtime/LoadScriptRuntimeModule.js +18 -13
  272. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +39 -26
  273. package/lib/runtime/MakeNamespaceObjectRuntimeModule.js +1 -1
  274. package/lib/runtime/OnChunksLoadedRuntimeModule.js +4 -4
  275. package/lib/runtime/RelativeUrlRuntimeModule.js +3 -2
  276. package/lib/runtime/StartupChunkDependenciesRuntimeModule.js +1 -1
  277. package/lib/runtime/StartupEntrypointRuntimeModule.js +4 -3
  278. package/lib/runtime/WorkerRuntimeModule.js +33 -0
  279. package/lib/schemes/HttpUriPlugin.js +3 -2
  280. package/lib/serialization/AggregateErrorSerializer.js +7 -6
  281. package/lib/serialization/ArraySerializer.js +4 -8
  282. package/lib/serialization/BinaryMiddleware.js +538 -468
  283. package/lib/serialization/DateObjectSerializer.js +2 -2
  284. package/lib/serialization/ErrorObjectSerializer.js +7 -6
  285. package/lib/serialization/MapObjectSerializer.js +5 -9
  286. package/lib/serialization/NullPrototypeObjectSerializer.js +7 -9
  287. package/lib/serialization/ObjectMiddleware.js +50 -13
  288. package/lib/serialization/PlainObjectSerializer.js +9 -8
  289. package/lib/serialization/RegExpObjectSerializer.js +2 -2
  290. package/lib/serialization/Serializer.js +1 -0
  291. package/lib/serialization/SetObjectSerializer.js +4 -8
  292. package/lib/sharing/ConsumeSharedModule.js +6 -7
  293. package/lib/sharing/ConsumeSharedPlugin.js +1 -0
  294. package/lib/sharing/ConsumeSharedRuntimeModule.js +46 -41
  295. package/lib/sharing/ProvideSharedDependency.js +30 -15
  296. package/lib/sharing/ProvideSharedModule.js +30 -11
  297. package/lib/sharing/ProvideSharedPlugin.js +4 -2
  298. package/lib/sharing/SharePlugin.js +3 -0
  299. package/lib/sharing/ShareRuntimeModule.js +18 -16
  300. package/lib/sharing/resolveMatchedConfigs.js +2 -1
  301. package/lib/sharing/utils.js +1 -1
  302. package/lib/stats/DefaultStatsFactoryPlugin.js +4 -6
  303. package/lib/stats/DefaultStatsPrinterPlugin.js +14 -14
  304. package/lib/stats/StatsFactory.js +11 -9
  305. package/lib/stats/StatsPrinter.js +9 -2
  306. package/lib/url/URLParserPlugin.js +5 -2
  307. package/lib/util/AsyncQueue.js +10 -0
  308. package/lib/util/LazyBucketSortedSet.js +5 -0
  309. package/lib/util/LazySet.js +6 -4
  310. package/lib/util/LocConverter.js +11 -1
  311. package/lib/util/Semaphore.js +1 -0
  312. package/lib/util/SourceProcessor.js +106 -0
  313. package/lib/util/TupleSet.js +1 -0
  314. package/lib/util/WeakTupleMap.js +27 -1
  315. package/lib/util/chainedImports.js +3 -3
  316. package/lib/util/comparators.js +2 -2
  317. package/lib/util/concatenate.js +31 -7
  318. package/lib/util/createHash.js +0 -1
  319. package/lib/util/deterministicGrouping.js +19 -12
  320. package/lib/util/extractSourceMap.js +1 -1
  321. package/lib/util/findGraphRoots.js +2 -0
  322. package/lib/util/fs.js +16 -6
  323. package/lib/util/hash/DebugHash.js +1 -0
  324. package/lib/util/hash/hash-digest.js +24 -15
  325. package/lib/util/identifier.js +7 -0
  326. package/lib/util/internalSerializables.js +11 -2
  327. package/lib/util/magicComment.js +42 -0
  328. package/lib/util/makeSerializable.js +1 -0
  329. package/lib/util/nonNumericOnlyHash.js +30 -1
  330. package/lib/util/property.js +7 -1
  331. package/lib/util/publicPathPlaceholder.js +64 -0
  332. package/lib/util/registerExternalSerializer.js +4 -4
  333. package/lib/wasm-async/AsyncWasmModule.js +77 -0
  334. package/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +1 -1
  335. package/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js +1 -96
  336. package/lib/wasm-async/AsyncWebAssemblyParser.js +1 -1
  337. package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +1 -1
  338. package/lib/wasm-sync/SyncWasmModule.js +39 -0
  339. package/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js +1 -0
  340. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +6 -3
  341. package/lib/wasm-sync/WasmFinalizeExportsPlugin.js +2 -1
  342. package/lib/wasm-sync/WebAssemblyGenerator.js +1 -0
  343. package/lib/wasm-sync/WebAssemblyInInitialChunkError.js +2 -0
  344. package/lib/wasm-sync/WebAssemblyModulesPlugin.js +10 -0
  345. package/lib/wasm-sync/WebAssemblyParser.js +7 -3
  346. package/lib/web/JsonpChunkLoadingRuntimeModule.js +45 -39
  347. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +11 -10
  348. package/package.json +36 -28
  349. package/schemas/WebpackOptions.check.js +1 -1
  350. package/schemas/WebpackOptions.json +212 -3
  351. package/schemas/plugins/HtmlGeneratorOptions.check.js +1 -1
  352. package/schemas/plugins/HtmlParserOptions.check.d.ts +7 -0
  353. package/schemas/plugins/HtmlParserOptions.check.js +6 -0
  354. package/schemas/plugins/HtmlParserOptions.json +3 -0
  355. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  356. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  357. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  358. package/types.d.ts +1592 -314
  359. package/lib/css/walkCssTokens.js +0 -2020
  360. package/lib/util/AppendOnlyStackedSet.js +0 -93
@@ -4,43 +4,77 @@
4
4
 
5
5
  "use strict";
6
6
 
7
- const vm = require("vm");
8
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");
9
17
  const ConstDependency = require("../dependencies/ConstDependency");
18
+ const HtmlEntryDependency = require("../dependencies/HtmlEntryDependency");
19
+ const HtmlInlineHtmlDependency = require("../dependencies/HtmlInlineHtmlDependency");
10
20
  const HtmlInlineScriptDependency = require("../dependencies/HtmlInlineScriptDependency");
11
21
  const HtmlInlineStyleDependency = require("../dependencies/HtmlInlineStyleDependency");
12
- const HtmlScriptSrcDependency = require("../dependencies/HtmlScriptSrcDependency");
13
22
  const HtmlSourceDependency = require("../dependencies/HtmlSourceDependency");
14
23
  const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
15
24
  const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
16
25
  const ModuleDependencyError = require("../errors/ModuleDependencyError");
17
26
  const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
18
27
  const WebpackError = require("../errors/WebpackError");
28
+ const LazySet = require("../util/LazySet");
19
29
  const LocConverter = require("../util/LocConverter");
20
30
  const createHash = require("../util/createHash");
21
31
  const { contextify } = require("../util/identifier");
22
32
  const {
23
33
  createMagicCommentContext,
34
+ parseMagicComment,
24
35
  webpackCommentRegExp
25
36
  } = require("../util/magicComment");
26
- const walkHtmlTokens = require("./walkHtmlTokens");
27
-
37
+ const {
38
+ NS_SVG,
39
+ NodeType,
40
+ SVG_TAG_ADJUST,
41
+ SourceProcessor,
42
+ buildHtmlAst,
43
+ decodeHtmlEntities,
44
+ decodeHtmlEntitiesWithMap,
45
+ findAttr,
46
+ parseSrcset
47
+ } = require("./syntax");
48
+
49
+ /** @typedef {import("../../declarations/WebpackOptions").HtmlParserOptions} HtmlParserOptions */
50
+ /** @typedef {import("../javascript/JavascriptParser").Range} Range */
51
+ /** @typedef {import("../Module")} Module */
28
52
  /** @typedef {import("../Module").BuildInfo} BuildInfo */
53
+ /** @typedef {import("../Compilation").FileSystemDependencies} FileSystemDependencies */
29
54
  /** @typedef {import("../Module").BuildMeta} BuildMeta */
55
+ /** @typedef {import("../NormalModule")} NormalModule */
30
56
  /** @typedef {import("../Parser").ParserState} ParserState */
31
57
  /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
58
+ /** @typedef {import("./HtmlModule").HtmlModuleBuildInfo} HtmlModuleBuildInfo */
59
+
60
+ /**
61
+ * @typedef {object} HtmlTemplateContext
62
+ * @property {Module} module the html module being transformed
63
+ * @property {string} resource absolute path of the module's resource
64
+ * @property {(dependency: string) => void} addDependency register a file (e.g. a template partial) as a build dependency so editing it triggers a rebuild
65
+ * @property {(dependency: string) => void} addContextDependency register a directory as a build dependency
66
+ * @property {(dependency: string) => void} addMissingDependency register a not-yet-existing path as a build dependency so creating it triggers a rebuild
67
+ * @property {(dependency: string) => void} addBuildDependency register a build dependency (e.g. a template engine config) so changing it invalidates the cache
68
+ * @property {(warning: Error | string) => void} emitWarning report a non-fatal warning on the module
69
+ * @property {(error: Error | string) => void} emitError report an error on the module
70
+ */
71
+ /** @typedef {(source: string, context: HtmlTemplateContext) => string} HtmlTemplateFunction */
32
72
 
33
73
  const HORIZONTAL_TAB = "\u0009".charCodeAt(0);
34
74
  const NEWLINE = "\u000A".charCodeAt(0);
35
75
  const FORM_FEED = "\u000C".charCodeAt(0);
36
76
  const CARRIAGE_RETURN = "\u000D".charCodeAt(0);
37
77
  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
78
 
45
79
  /**
46
80
  * @param {number} char char
@@ -61,7 +95,50 @@ function isASCIIWhitespace(char) {
61
95
  );
62
96
  }
63
97
 
64
- /** @typedef {[string, number, number]} ParsedSource */
98
+ /** @typedef {import("./syntax").ParsedSource} ParsedSource */
99
+
100
+ // Cheap pre-filter for a `style="..."` attribute: only route it through the
101
+ // CSS pipeline when it can hold a URL-bearing function (`url()` / `src()` /
102
+ // `image()` / `image-set()`), otherwise the processed text equals the input.
103
+ const STYLE_ATTR_URL_REGEXP = /url\(|src\(|image\(|image-set\(/i;
104
+
105
+ // Cheap pre-filter for a `css-url` attribute value (an SVG presentation
106
+ // attribute such as `fill`, `clip-path`, …): only tokenize values that can
107
+ // hold a `url(...)` FuncIRI. Most are plain colors/keywords (`#fff`, `red`).
108
+ const FUNC_IRI_URL_REGEXP = /url\(/i;
109
+
110
+ // Cheap pre-filter for `<iframe srcdoc>` markup: only spin up a nested HTML
111
+ // module when the document can reference an asset — via any attribute (`src=`,
112
+ // `href=`, `style=`, …), a CSS `url(...)`, or a CSS `@import`. Pure
113
+ // formatting/text markup (`<p>hi</p>`) rewrites to itself, so skip it.
114
+ const SRCDOC_ASSET_REGEXP = /[=]|url\(|@import/i;
115
+
116
+ // CSP/fetch attributes copied verbatim onto a synthesized sibling `<link>` /
117
+ // `<script>` (`HtmlEntryDependency`). Fixed output order, independent of
118
+ // source order.
119
+ const COPYABLE_SIBLING_ATTRS = ["nonce", "crossorigin", "referrerpolicy"];
120
+
121
+ const CC_QUOTATION = '"'.charCodeAt(0);
122
+ const CC_APOSTROPHE = "'".charCodeAt(0);
123
+
124
+ /**
125
+ * Byte-exact source span of an attribute including the single leading
126
+ * whitespace (` name`, ` name=value`, ` name="value"`), mirroring the
127
+ * tokenizer's end-of-attribute rule (`valueEnd + 1` past the closing quote).
128
+ * @param {string} source HTML source
129
+ * @param {import("./syntax").HtmlAttribute} attr attribute
130
+ * @returns {string} the attribute's source slice
131
+ */
132
+ const attrSourceSpan = (source, attr) => {
133
+ 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);
141
+ };
65
142
 
66
143
  // eslint-disable-next-line no-control-regex
67
144
  const IGNORE_CHARS_REGEXP = /[\u0000-\u001F\u007F-\u009F\u00A0]/g;
@@ -101,355 +178,86 @@ const parseSrc = (input) => {
101
178
  return [[value, start, end]];
102
179
  };
103
180
 
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
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.
130
184
  * @param {string} input input
131
- * @returns {ParsedSource[]} parsed srcset
185
+ * @returns {ParsedSource[]} parsed icon-uri
132
186
  */
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;
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++;
219
201
  }
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;
202
+ while (end > start && isASCIIWhitespace(input.charCodeAt(end - 1))) {
203
+ end--;
242
204
  }
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;
205
+ if (start === end) return [];
206
+ return [[input.slice(start, end), start, end]];
269
207
  }
270
208
  }
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
- }
209
+ pos = sep + 1;
286
210
  }
211
+ return [];
212
+ };
287
213
 
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
- }
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
+ ]);
384
237
  }
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;
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
+ ]);
401
256
  }
402
257
  }
403
-
404
- // Advance position to the next character in input.
405
- position += 1;
406
258
  }
407
259
  }
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
- }
260
+ return result;
453
261
  };
454
262
 
455
263
  /**
@@ -471,9 +279,12 @@ const META = new Map([
471
279
  "msapplication-wide310x150logo",
472
280
  "msapplication-square310x310logo",
473
281
  "msapplication-config",
474
- // TODO Do we need to parser it?
475
- // "msapplication-task",
476
- "twitter:image"
282
+ // Only the `icon-uri` part is an asset, see `parseMsapplicationTask`
283
+ "msapplication-task",
284
+ "twitter:image",
285
+ "twitter:image:src",
286
+ // Legacy preview-image hint
287
+ "thumbnail"
477
288
  ])
478
289
  ],
479
290
  [
@@ -511,14 +322,33 @@ const META = new Map([
511
322
  * @returns {boolean} true when need to parse, otherwise false
512
323
  */
513
324
  const filterLinkItemprop = (attributes) => {
514
- const value = getAttributeValue(attributes, "itemprop");
515
- if (!value) return false;
325
+ const itemprop = getAttributeValue(attributes, "itemprop");
326
+ if (!itemprop) return false;
516
327
  const allowedAttributes = META.get("itemprop");
517
328
  if (!allowedAttributes) return false;
518
329
 
519
- return allowedAttributes.has(value.trim().toLowerCase());
330
+ return allowedAttributes.has(itemprop.trim().toLowerCase());
520
331
  };
521
332
 
333
+ // `<link rel>` values whose `href`/`imagesrcset` webpack treats as a reference.
334
+ const ALLOWED_LINK_RELS = new Set([
335
+ "stylesheet",
336
+ "icon",
337
+ "mask-icon",
338
+ "apple-touch-icon",
339
+ "apple-touch-icon-precomposed",
340
+ "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.
344
+ "manifest",
345
+ "prefetch",
346
+ "preload",
347
+ "modulepreload",
348
+ // Legacy preview-image hint (`<link rel="image_src" href>`)
349
+ "image_src"
350
+ ]);
351
+
522
352
  /**
523
353
  * @param {Map<string, string>} attributes attributes
524
354
  * @returns {boolean} true when need to parse, otherwise false
@@ -526,21 +356,11 @@ const filterLinkItemprop = (attributes) => {
526
356
  const filterLinkHref = (attributes) => {
527
357
  const rel = getAttributeValue(attributes, "rel");
528
358
  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));
359
+ const usedRels = rel.trim().toLowerCase().split(/\s+/);
360
+ for (let i = 0; i < usedRels.length; i++) {
361
+ if (ALLOWED_LINK_RELS.has(usedRels[i])) return true;
362
+ }
363
+ return false;
544
364
  };
545
365
 
546
366
  /**
@@ -559,13 +379,25 @@ const filterMetaContent = (attributes) => {
559
379
  const [key, allowedNames] = item;
560
380
  const name = getAttributeValue(attributes, key);
561
381
  if (!name) continue;
562
-
563
- return allowedNames.has(name.trim().toLowerCase());
382
+ // Check every present attribute, not only the first one
383
+ if (allowedNames.has(name.trim().toLowerCase())) return true;
564
384
  }
565
385
 
566
386
  return false;
567
387
  };
568
388
 
389
+ /**
390
+ * `<param value>` (obsolete `<object>`/`<applet>` child) is only a URL when
391
+ * `valuetype="ref"`; otherwise it's an opaque string.
392
+ * @param {Map<string, string>} attributes attributes
393
+ * @returns {boolean} true when `value` is a URL reference
394
+ */
395
+ const filterParamRef = (attributes) => {
396
+ const valuetype = getAttributeValue(attributes, "valuetype");
397
+ if (!valuetype) return false;
398
+ return valuetype.trim().toLowerCase() === "ref";
399
+ };
400
+
569
401
  /**
570
402
  * @param {Map<string, string>} attributes attributes
571
403
  * @returns {boolean} true when the script element opts into ES module semantics
@@ -619,216 +451,394 @@ const isLinkStylesheet = (attributes) => {
619
451
  return rel.trim().toLowerCase().split(/\s+/).includes("stylesheet");
620
452
  };
621
453
 
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
- ]);
454
+ /** @typedef {"src" | "srcset" | "css-url" | "msapplication-task" | "script" | "script-module" | "modulepreload" | "stylesheet" | "stylesheet-style" | "stylesheet-style-attribute" | "srcdoc"} SourceType */
455
+ /** Entry types: a `type` whose value is loaded as its own compilation entry chunk. */
456
+ /** @typedef {"script" | "script-module" | "modulepreload" | "stylesheet"} EntrySourceType */
457
+ /** @typedef {SourceType | ((attrs: Map<string, string>, css: boolean) => SourceType)} SourceTypeOrResolver */
458
+ /** @typedef {(attributes: Map<string, string>, value: string) => boolean} SourceFilter */
459
+ /** @typedef {{ tag?: string, attribute: string, type: SourceType, filter?: SourceFilter }} SourceEntry */
460
+ /** 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
+ /** @typedef {{ type: SourceTypeOrResolver, filter?: SourceFilter, namespace?: number }} SourceItem */
462
+ /** A source whose value is the element's text content (a `<style>`/`<script>` body) rather than an attribute. */
463
+ /** @typedef {{ type: SourceTypeOrResolver, filter?: (attributes: Map<string, string>) => boolean }} ContentSourceItem */
464
+
465
+ /**
466
+ * Builds a null-prototype dictionary from the given property bags
467
+ * (later bags win; `undefined` bags are skipped). A null prototype is
468
+ * essential here: the tables are indexed by HTML tag and attribute
469
+ * names, so a plain object would let names like `__proto__`,
470
+ * `constructor`, or `toString` resolve to inherited values at lookup
471
+ * time — producing bogus dependencies and letting `sources: false` be
472
+ * bypassed.
473
+ * @param {...(Record<string, EXPECTED_ANY> | undefined)} bags property bags
474
+ * @returns {EXPECTED_ANY} null-prototype dictionary
475
+ */
476
+ const dict = (...bags) => Object.assign(Object.create(null), ...bags);
477
+
478
+ // Shared `SourceItem` singletons used by the built-in defaults —
479
+ // keeping one instance per kind stabilizes V8's hidden classes across
480
+ // lookups in the walk. `srcset` is its own type; the walk picks the
481
+ // `parseSrcset` parser for it and otherwise treats it like `src`.
482
+ /** @type {SourceItem} */
483
+ const PLAIN_SRC = { type: "src", filter: undefined };
484
+ /** @type {SourceItem} */
485
+ const PLAIN_SRCSET = { type: "srcset", filter: undefined };
486
+
487
+ /**
488
+ * `<link href>` is polymorphic: `rel="modulepreload"` → an ESM
489
+ * preload entry, `rel="stylesheet"` (with `experiments.css`) → a CSS
490
+ * entry, otherwise a plain asset URL.
491
+ * @type {SourceItem}
492
+ */
493
+ const LINK_HREF = {
494
+ type: (attrs, css) => {
495
+ if (isLinkModulePreload(attrs)) return "modulepreload";
496
+ if (css && isLinkStylesheet(attrs)) return "stylesheet";
497
+ return "src";
498
+ },
499
+ filter: filterLinkUnion
500
+ };
501
+
502
+ /**
503
+ * `<script src>`: non-JS types (e.g. `application/ld+json`,
504
+ * `importmap`) stay plain asset URLs so the browser keeps seeing them
505
+ * as data blocks; `type="module"` opts into the ESM entry chunk;
506
+ * everything else in `JS_SCRIPT_TYPES` is a classic script.
507
+ * @type {SourceItem}
508
+ */
509
+ const SCRIPT_SRC = {
510
+ type: (attrs) =>
511
+ isExecutableJsScript(attrs)
512
+ ? isModuleScript(attrs)
513
+ ? "script-module"
514
+ : "script"
515
+ : "src",
516
+ filter: undefined
517
+ };
518
+
519
+ /**
520
+ * `<meta content>`: most referenced names hold a single URL (`src`); the
521
+ * `msapplication-task` value is a `;`-delimited list whose `icon-uri` part
522
+ * is the only asset, so it selects the `msapplication-task` parser.
523
+ * @type {SourceItem}
524
+ */
525
+ const META_CONTENT = {
526
+ type: (attrs) => {
527
+ const name = attrs.get("name");
528
+ return name !== undefined &&
529
+ name.trim().toLowerCase() === "msapplication-task"
530
+ ? "msapplication-task"
531
+ : "src";
532
+ },
533
+ filter: filterMetaContent
534
+ };
535
+
536
+ // Built-in lookup table, written directly in its final resolved shape:
537
+ // `DEFAULT_SOURCES_BY_TAG[tag][attribute] = item`. No module-load
538
+ // loop, no separate array representation — every parser created with
539
+ // the default `sources` config just references this table and pays
540
+ // zero per-parser work.
541
+ /** @type {Record<string, Record<string, SourceItem>>} */
542
+ const DEFAULT_SOURCES_BY_TAG = dict({
543
+ // Obsolete Java-applet element; `code`/`object` are single class/object URLs.
544
+ applet: dict({ code: PLAIN_SRC, object: PLAIN_SRC }),
545
+ audio: dict({ src: PLAIN_SRC }),
546
+ // Deprecated presentational `background` attribute — an image URL.
547
+ body: dict({ background: PLAIN_SRC }),
548
+ embed: dict({ src: PLAIN_SRC }),
549
+ // `srcdoc` is an entity-encoded HTML document parsed and rewritten as a
550
+ // nested module — its handling lives in the generic source loop so the
551
+ // `sources` option can re-target it (or other tags/attributes) freely.
552
+ iframe: dict({ srcdoc: { type: "srcdoc", filter: undefined } }),
553
+ img: dict({ src: PLAIN_SRC, srcset: PLAIN_SRCSET }),
554
+ input: dict({ src: PLAIN_SRC }),
555
+ link: dict({
556
+ href: LINK_HREF,
557
+ imagesrcset: { type: "srcset", filter: filterLinkHref }
558
+ }),
559
+ meta: dict({ content: META_CONTENT }),
560
+ // MathML `<mglyph src>` references an image.
561
+ mglyph: dict({ src: PLAIN_SRC }),
562
+ // `classid` is a single object URI (`codebase`/`archive` are a base/list, skipped).
563
+ object: dict({ data: PLAIN_SRC, classid: PLAIN_SRC }),
564
+ // Obsolete `<param valuetype="ref" value="url">` child of `<object>`.
565
+ param: dict({ value: { type: "src", filter: filterParamRef } }),
566
+ // `href`/`xlink:href` reference the source of SVG `<script>` elements
567
+ script: dict({
568
+ src: SCRIPT_SRC,
569
+ href: SCRIPT_SRC,
570
+ "xlink:href": SCRIPT_SRC
571
+ }),
572
+ source: dict({ src: PLAIN_SRC, srcset: PLAIN_SRCSET }),
573
+ // Deprecated presentational `background` attribute — an image URL.
574
+ table: dict({ background: PLAIN_SRC }),
575
+ td: dict({ background: PLAIN_SRC }),
576
+ th: dict({ background: PLAIN_SRC }),
577
+ track: dict({ src: PLAIN_SRC }),
578
+ video: dict({ poster: PLAIN_SRC, src: PLAIN_SRC }),
579
+ // SVG. Tag names match the tree builder's adjusted camelCase
580
+ // (`feImage`/`textPath`/`linearGradient`/`radialGradient`). `href`/
581
+ // `xlink:href` reference another element/resource; fragment-only `#id`
582
+ // values are left untouched, so only external `file.svg#id` is rewritten.
583
+ // `color-profile`'s `xlink:href` points at an external ICC profile file.
584
+ "color-profile": dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
585
+ feImage: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
586
+ filter: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
587
+ image: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
588
+ linearGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
589
+ mpath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
590
+ pattern: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
591
+ radialGradient: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
592
+ textPath: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC }),
593
+ use: dict({ "xlink:href": PLAIN_SRC, href: PLAIN_SRC })
594
+ // CSS `url(...)` references in SVG presentation attributes (fill, stroke,
595
+ // clip-path, …) are handled separately during the walk (`parseCssUrls`).
596
+ });
597
+
598
+ // SVG presentation attributes (`fill`, `stroke`, …) carry CSS `url(...)`
599
+ // FuncIRIs; `parseCssUrls` extracts external refs as assets (internal
600
+ // `url(#id)` is left untouched). They apply to every SVG-namespace element, so
601
+ // the item is `namespace`-gated rather than placed in a tag bucket. The filter
602
+ // is a cheap pre-check skipping the common plain values (`red`, `none`, `#fff`).
603
+ /** @type {SourceItem} */
604
+ const SVG_CSS_URL = {
605
+ type: "css-url",
606
+ filter: (attributes, value) => FUNC_IRI_URL_REGEXP.test(value),
607
+ namespace: NS_SVG
608
+ };
609
+
610
+ // The global `style=""` attribute (a CSS declaration list), available on every
611
+ // tag and even with `sources: false`. Like every `stylesheet-style*` source it
612
+ // only emits when `experiments.css` is on (checked where the dependency is
613
+ // created); the filter is a cheap pre-check skipping declarations without a
614
+ // `url()`/`src()`/… .
615
+ /** @type {SourceItem} */
616
+ const STYLE_ATTRIBUTE = {
617
+ type: "stylesheet-style-attribute",
618
+ filter: (attributes, value) => STYLE_ATTR_URL_REGEXP.test(value)
619
+ };
620
+
621
+ // Any-tag sources — applied to every element as a fallback and folded into
622
+ // each tag bucket, so the walk resolves one object per element and reads one
623
+ // property per attribute. `DEFAULT` (svg presentation + style) is active
624
+ // unless `sources: false`; only the always-on style attribute survives `false`.
625
+ /** @type {Record<string, SourceItem>} */
626
+ const DEFAULT_ANY_SOURCES = dict({ style: STYLE_ATTRIBUTE });
627
+ for (const name of [
628
+ "fill",
629
+ "stroke",
630
+ "clip-path",
631
+ "mask",
632
+ "filter",
633
+ "marker",
634
+ "marker-start",
635
+ "marker-mid",
636
+ "marker-end",
637
+ "cursor"
638
+ ]) {
639
+ DEFAULT_ANY_SOURCES[name] = SVG_CSS_URL;
640
+ }
641
+ /** @type {Record<string, SourceItem>} */
642
+ const ALWAYS_ANY_SOURCES = dict({ style: STYLE_ATTRIBUTE });
643
+
644
+ // Reserved key holding the any-tag sources inside the per-tag table — the
645
+ // schema forbids an empty `tag`, so it never collides with a real tag. The
646
+ // walk falls back to it for elements without their own bucket.
647
+ const ANY_TAG = "";
648
+
649
+ /**
650
+ * Folds the any-tag sources into every per-tag bucket (tag-specific entries
651
+ * win) and stores them under `ANY_TAG`, so the whole source model is one
652
+ * 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
656
+ */
657
+ const foldAnySources = (byTag, any) => {
658
+ for (const tag of Object.keys(byTag)) {
659
+ byTag[tag] = dict(any, byTag[tag]);
660
+ }
661
+ byTag[ANY_TAG] = any;
662
+ return byTag;
663
+ };
664
+
665
+ // Default table (per-tag defaults + any-tag sources folded in) and the
666
+ // `sources: false` table (only the always-on `style=""` attribute). Both are
667
+ // precomputed so the common cases reference them with zero per-parser work.
668
+ /** @type {Record<string, Record<string, SourceItem>>} */
669
+ const DEFAULT_SOURCES_FOLDED = foldAnySources(
670
+ dict(DEFAULT_SOURCES_BY_TAG),
671
+ DEFAULT_ANY_SOURCES
672
+ );
673
+ /** @type {Record<string, Record<string, SourceItem>>} */
674
+ const DISABLED_SOURCES_FOLDED = foldAnySources(dict(), ALWAYS_ANY_SOURCES);
675
+
676
+ /**
677
+ * Inline `<script>` body: classic JS unless `type="module"` opts into ESM.
678
+ * @param {Map<string, string>} attrs attributes
679
+ * @returns {SourceType} the entry type for the inline script
680
+ */
681
+ const scriptContentType = (attrs) =>
682
+ isModuleScript(attrs) ? "script-module" : "script";
683
+
684
+ /**
685
+ * Inline `<script>` body: only an executable JS block with no external source
686
+ * carries content webpack should bundle.
687
+ * @param {Map<string, string>} attrs attributes
688
+ * @returns {boolean} true when the inline body should be bundled
689
+ */
690
+ const scriptContentFilter = (attrs) =>
691
+ !attrs.has("src") &&
692
+ !attrs.has("href") &&
693
+ !attrs.has("xlink:href") &&
694
+ isExecutableJsScript(attrs);
695
+
696
+ /**
697
+ * `<style>` body is a stylesheet only when its `type` is empty or `text/css`.
698
+ * @param {Map<string, string>} attrs attributes
699
+ * @returns {boolean} true when the body is CSS
700
+ */
701
+ const styleContentFilter = (attrs) => {
702
+ const type = attrs.get("type");
703
+ if (type === undefined) return true;
704
+ const t = type.trim().toLowerCase();
705
+ return t === "" || t === "text/css";
706
+ };
707
+
708
+ // Element-body sources, independent of the `sources` option (even `sources:
709
+ // false`): inline `<script>` bodies are always processed; the `<style>` body
710
+ // only emits when `experiments.css` is on (checked where the dependency is
711
+ // created, like every `stylesheet-style*` source).
712
+ /** @type {Record<string, ContentSourceItem>} */
713
+ const CONTENT_SOURCES = dict({
714
+ script: { type: scriptContentType, filter: scriptContentFilter },
715
+ style: { type: "stylesheet-style", filter: styleContentFilter }
716
+ });
816
717
 
817
718
  class HtmlParser extends Parser {
818
719
  /**
819
720
  * 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
721
+ * @param {HtmlParserOptions} options parser options (from `module.parser.html`; always passed by the `createParser` hook)
824
722
  */
825
- constructor(hashFunction, context, outputModule, css) {
723
+ constructor(options) {
826
724
  super();
827
725
  this.magicCommentContext = createMagicCommentContext();
828
- this.hashFunction = hashFunction;
829
- this.context = context;
830
- this.outputModule = outputModule;
831
- this.css = css;
726
+ // Read by HtmlModulesPlugin's `processResult` hook, which transforms
727
+ // the module source before it is stored and parsed.
728
+ /** @type {HtmlTemplateFunction | undefined} */
729
+ this.template = options.template;
730
+ // One source model: a per-tag table that also holds the any-tag sources
731
+ // under `ANY_TAG`. The common cases reference precomputed tables.
732
+ /** @type {Record<string, Record<string, SourceItem>>} */
733
+ this.sourcesByTag = DEFAULT_SOURCES_FOLDED;
734
+
735
+ const sources = options.sources;
736
+ if (sources === undefined || sources === true) return;
737
+ if (sources === false) {
738
+ // Only the always-on `style=""` attribute survives; nothing else is
739
+ // extracted (svg presentation, `<script src>`, `<link>` entries, …).
740
+ this.sourcesByTag = DISABLED_SOURCES_FOLDED;
741
+ return;
742
+ }
743
+
744
+ // User array — `"..."` anywhere opts the per-tag defaults in as the
745
+ // base; the built-in any-tag sources (svg presentation + style) are
746
+ // always present. A user entry with no `tag` is an any-tag source (e.g.
747
+ // `{ attribute: "data-style", type: "stylesheet-style-attribute" }`).
748
+ // User entries override regardless of position. `dict()` keeps every
749
+ // table null-prototype (see its doc), and per-tag writes rebuild the
750
+ // bucket so the aliased default buckets stay intact.
751
+ /** @type {Record<string, Record<string, SourceItem>>} */
752
+ const byTag = sources.includes("...")
753
+ ? dict(DEFAULT_SOURCES_BY_TAG)
754
+ : dict();
755
+ /** @type {Record<string, SourceItem>} */
756
+ const any = dict(DEFAULT_ANY_SOURCES);
757
+ for (const entry of sources) {
758
+ if (entry === "...") continue;
759
+ /** @type {SourceItem} */
760
+ const item = {
761
+ type: entry.type,
762
+ filter: typeof entry.filter === "function" ? entry.filter : undefined
763
+ };
764
+ const attr = entry.attribute.toLowerCase();
765
+ if (entry.tag === undefined) {
766
+ any[attr] = item;
767
+ } else {
768
+ const tag = entry.tag.toLowerCase();
769
+ byTag[tag] = dict(byTag[tag], { [attr]: item });
770
+ // The AST carries adjusted camelCase names for foreign-content
771
+ // tags (e.g. `feImage`) — register the entry under both.
772
+ const adjusted = SVG_TAG_ADJUST[tag];
773
+ if (adjusted !== undefined) {
774
+ byTag[adjusted] = dict(byTag[adjusted], { [attr]: item });
775
+ }
776
+ }
777
+ }
778
+ /** @type {Record<string, Record<string, SourceItem>>} */
779
+ this.sourcesByTag = foldAnySources(byTag, any);
780
+ }
781
+
782
+ /**
783
+ * Runs the `template` option over the source and returns the transformed
784
+ * html. Called from HtmlModulesPlugin's `processResult`, where the return
785
+ * value becomes the module's stored source so the parser (which records
786
+ * dependency offsets against it) and the generator (which renders from
787
+ * `module.originalSource()`) stay in agreement.
788
+ * @param {string | Buffer} source the original source
789
+ * @param {NormalModule} module the html module
790
+ * @returns {string | Buffer} the transformed source
791
+ */
792
+ applyTemplate(source, module) {
793
+ if (!this.template) return source;
794
+ // `processResult` runs after `_doBuild` has initialized these
795
+ // dependency sets, so they are always present here.
796
+ const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
797
+ const fileDependencies = /** @type {FileSystemDependencies} */ (
798
+ buildInfo.fileDependencies
799
+ );
800
+ const contextDependencies = /** @type {FileSystemDependencies} */ (
801
+ buildInfo.contextDependencies
802
+ );
803
+ const missingDependencies = /** @type {FileSystemDependencies} */ (
804
+ buildInfo.missingDependencies
805
+ );
806
+ const transformed = this.template(
807
+ typeof source === "string" ? source : source.toString("utf8"),
808
+ {
809
+ module,
810
+ resource: module.resource,
811
+ addDependency: (dependency) => {
812
+ fileDependencies.add(dependency);
813
+ },
814
+ addContextDependency: (dependency) => {
815
+ contextDependencies.add(dependency);
816
+ },
817
+ addMissingDependency: (dependency) => {
818
+ missingDependencies.add(dependency);
819
+ },
820
+ addBuildDependency: (dependency) => {
821
+ if (buildInfo.buildDependencies === undefined) {
822
+ buildInfo.buildDependencies = new LazySet();
823
+ }
824
+ buildInfo.buildDependencies.add(dependency);
825
+ },
826
+ emitWarning: (warning) =>
827
+ module.addWarning(
828
+ warning instanceof Error ? warning : new WebpackError(warning)
829
+ ),
830
+ emitError: (error) =>
831
+ module.addError(
832
+ error instanceof Error ? error : new WebpackError(error)
833
+ )
834
+ }
835
+ );
836
+ if (typeof transformed !== "string") {
837
+ throw new Error(
838
+ "The `template` html parser option must return a string."
839
+ );
840
+ }
841
+ return transformed;
832
842
  }
833
843
 
834
844
  /**
@@ -850,6 +860,10 @@ class HtmlParser extends Parser {
850
860
  const locConverter = new LocConverter(source);
851
861
 
852
862
  const module = state.module;
863
+ const compilation = state.compilation;
864
+ const { hashFunction, module: outputModule } = compilation.outputOptions;
865
+ const context = compilation.compiler.context;
866
+ const css = Boolean(compilation.options.experiments.css);
853
867
 
854
868
  // Stable, per-HTML-module prefix used when generating entry names for
855
869
  // script src / modulepreload references so they don't collide across
@@ -862,28 +876,57 @@ class HtmlParser extends Parser {
862
876
  /** @type {string} */
863
877
  const resource =
864
878
  /** @type {EXPECTED_ANY} */ (module).resource || module.identifier();
865
- const moduleHash = createHash(this.hashFunction || "md4")
866
- .update(this.context ? contextify(this.context, resource) : resource)
879
+ const moduleHash = createHash(hashFunction || "md4")
880
+ .update(context ? contextify(context, resource) : resource)
867
881
  .digest("hex")
868
882
  .slice(0, 8);
869
883
 
870
- /** @typedef {{ nameStart: number, nameEnd: number, valueStart: number, valueEnd: number }} AttrToken */
884
+ // Script src / modulepreload references are collected per-type
885
+ // during the walk; HtmlModulesPlugin later turns them into real
886
+ // entries. `script` and `script-module` entries are chained via a
887
+ // leader-only dependOn so they share a runtime.
888
+ // `<link rel="modulepreload">` entries are kept independent — they
889
+ // must preload without running, so they can never become a runtime
890
+ // leader that other entries would import.
891
+ /**
892
+ * @typedef {object} EntryScriptInfo
893
+ * @property {string} request
894
+ * @property {string} entryName
895
+ * @property {"script" | "script-module" | "modulepreload" | "stylesheet"} type
896
+ */
897
+ /** @type {EntryScriptInfo[]} */
898
+ const scriptEntries = [];
899
+ /** @type {EntryScriptInfo[]} */
900
+ const scriptModuleEntries = [];
901
+ /** @type {EntryScriptInfo[]} */
902
+ const modulePreloadEntries = [];
903
+ /** @type {EntryScriptInfo[]} */
904
+ 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;
909
+
910
+ let nextEntryIndex = 0;
911
+
912
+ /**
913
+ * Tracks the `webpackIgnore` value from the most recent comment that
914
+ * appears before the next tag. Reset whenever a tag is emitted or a
915
+ * comment without a `webpackIgnore` value is encountered.
916
+ * @type {boolean | undefined}
917
+ */
918
+ let pendingWebpackIgnore;
871
919
 
872
- /** @type {AttrToken[]} */
873
- const pendingAttributes = [];
920
+ const magicCommentContext = this.magicCommentContext;
874
921
 
875
922
  /**
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}
923
+ * @param {import("./syntax").HtmlAttribute | undefined} typeAttr type attribute
924
+ * @param {number} nameEnd end offset of the tag name
925
+ * @param {string} type type of the script
926
+ * @param {string} input source string
884
927
  */
885
- const reconcileScriptTypeAttr = (typeAttr, nameEnd, kind, input) => {
886
- if (this.outputModule && kind === "classic") {
928
+ const reconcileScriptTypeAttr = (typeAttr, nameEnd, type, input) => {
929
+ if (outputModule && type === "script") {
887
930
  // Chunk is an ES module; upgrade the tag.
888
931
  if (typeAttr && typeAttr.valueStart !== -1) {
889
932
  module.addPresentationalDependency(
@@ -897,7 +940,7 @@ class HtmlParser extends Parser {
897
940
  new ConstDependency(' type="module"', nameEnd)
898
941
  );
899
942
  }
900
- } else if (!this.outputModule && kind === "esm-script" && typeAttr) {
943
+ } else if (!outputModule && type === "script-module" && typeAttr) {
901
944
  // Chunk is a classic IIFE; drop `type="module"` so the
902
945
  // browser doesn't load it under module semantics.
903
946
  let attrEnd;
@@ -911,8 +954,6 @@ class HtmlParser extends Parser {
911
954
  } else {
912
955
  attrEnd = typeAttr.valueEnd;
913
956
  }
914
- // Consume one leading whitespace char so we don't leave a
915
- // double space between `<script` and the next attribute.
916
957
  let attrStart = typeAttr.nameStart;
917
958
  if (
918
959
  attrStart > 0 &&
@@ -926,552 +967,520 @@ class HtmlParser extends Parser {
926
967
  }
927
968
  };
928
969
 
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
970
  /**
946
- * @typedef {object} EntryScriptInfo
947
- * @property {string} request
948
- * @property {string} entryName
949
- * @property {"classic" | "esm-script" | "modulepreload" | "stylesheet"} kind
971
+ * @param {string} mime mime type
972
+ * @param {string} text inline text
973
+ * @returns {string} a `data:` request for the text
950
974
  */
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;
975
+ const dataUri = (mime, text) =>
976
+ `data:${mime};base64,${Buffer.from(text, "utf8").toString("base64")}`;
961
977
 
962
978
  /**
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}
979
+ * @param {ConstDependency | HtmlSourceDependency | HtmlEntryDependency | HtmlInlineStyleDependency | HtmlInlineScriptDependency | HtmlInlineHtmlDependency} dep dependency
980
+ * @param {number} start raw start offset
981
+ * @param {number} end raw end offset
967
982
  */
968
- let pendingWebpackIgnore;
969
-
970
- const magicCommentContext = this.magicCommentContext;
983
+ const setLoc = (dep, start, end) => {
984
+ const s = locConverter.get(start);
985
+ const e = locConverter.get(end);
986
+ dep.setLoc(s.line, s.column, e.line, e.column);
987
+ };
971
988
 
972
989
  // 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() : "";
990
+ // The walker descends into children (and `<template>` content) itself;
991
+ // the Element `exit` clears a pending `webpackIgnore` once an element's
992
+ // children are done (the old `walkChildren` behaviour).
993
+ new SourceProcessor()
994
+ .use({
995
+ [NodeType.Comment]: (n) => {
996
+ const node = /** @type {import("./syntax").HtmlComment} */ (n);
997
+ // Only proper `<!-- ... -->` comments carry magic comments.
1155
998
  if (
1156
- typeValue !== undefined &&
1157
- trimmedType !== "" &&
1158
- trimmedType !== "text/css"
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
1159
1007
  ) {
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);
1008
+ pendingWebpackIgnore = undefined;
1009
+ return;
1214
1010
  }
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;
1011
+ const value = node.data;
1012
+ if (!webpackCommentRegExp.test(value)) {
1013
+ pendingWebpackIgnore = undefined;
1014
+ return;
1245
1015
  }
1246
-
1247
- /** @type {ParsedSource[] | undefined} */
1248
- let parsedAttributeValue;
1249
-
1016
+ /** @type {Record<string, EXPECTED_ANY>} */
1017
+ let options;
1250
1018
  try {
1251
- parsedAttributeValue = sourceItem.parse(attributeValue);
1019
+ options = parseMagicComment(value, magicCommentContext);
1252
1020
  } 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
- ),
1021
+ const { line: sl, column: sc } = locConverter.get(node.start);
1022
+ const { line: el, column: ec } = locConverter.get(node.end);
1023
+ module.addWarning(
1024
+ new CommentCompilationWarning(
1025
+ `Compilation error while processing magic comment(-s): /*${value}*/: ${
1026
+ /** @type {Error} */ (err).message
1027
+ }`,
1028
+ {
1029
+ start: { line: sl, column: sc },
1030
+ end: { line: el, column: ec }
1031
+ }
1032
+ )
1033
+ );
1034
+ pendingWebpackIgnore = undefined;
1035
+ return;
1036
+ }
1037
+ if (options.webpackIgnore === undefined) {
1038
+ pendingWebpackIgnore = undefined;
1039
+ return;
1040
+ }
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);
1044
+ module.addWarning(
1045
+ new UnsupportedFeatureWarning(
1046
+ `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
1264
1047
  {
1265
1048
  start: { line: sl, column: sc },
1266
1049
  end: { line: el, column: ec }
1267
1050
  }
1268
1051
  )
1269
1052
  );
1053
+ pendingWebpackIgnore = undefined;
1054
+ return;
1270
1055
  }
1056
+ pendingWebpackIgnore = options.webpackIgnore;
1057
+ },
1058
+ [NodeType.Doctype]: () => {
1059
+ pendingWebpackIgnore = undefined;
1060
+ },
1061
+ [NodeType.Element]: {
1062
+ // Children (and `<template>` content) are walked by the
1063
+ // processor after `enter`; `exit` clears a pending
1064
+ // `webpackIgnore` once they're done.
1065
+ enter: (n) => {
1066
+ const node = /** @type {import("./syntax").HtmlElement} */ (n);
1067
+ const ignore = pendingWebpackIgnore === true;
1068
+ pendingWebpackIgnore = undefined;
1069
+
1070
+ if (ignore) {
1071
+ return;
1072
+ }
1271
1073
 
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) {
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
+ };
1092
+
1093
+ // Each matched attribute is a source; the element body (inline
1094
+ // `<style>`/`<script>`) is one more — content rather than an attribute.
1095
+ // All are dispatched by the same `switch (type)`.
1096
+ const sources =
1097
+ this.sourcesByTag[elementName] || this.sourcesByTag[ANY_TAG];
1098
+ const bodyItem = CONTENT_SOURCES[elementName];
1099
+ // 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;
1104
+ /** @type {SourceType} */
1105
+ let type;
1106
+ /** @type {string} */
1107
+ let value;
1108
+ /** @type {number} */
1109
+ let start;
1110
+ /** @type {number} */
1111
+ let end;
1112
+ /** @type {import("./syntax").HtmlAttribute | undefined} */
1113
+ let attr;
1114
+ if (content) {
1115
+ if (!bodyItem) continue;
1116
+ if (bodyItem.filter && !bodyItem.filter(getAttributesMap())) {
1117
+ continue;
1118
+ }
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;
1129
+ value = source.slice(bodyStart, bodyEnd);
1130
+ if (value.trim() === "") continue;
1131
+ start = bodyStart;
1132
+ end = bodyEnd;
1133
+ type =
1134
+ typeof bodyItem.type === "function"
1135
+ ? bodyItem.type(getAttributesMap(), css)
1136
+ : bodyItem.type;
1137
+ } else {
1138
+ attr = attrs[i];
1139
+ const item = sources[attr.name];
1140
+ if (!item) continue;
1141
+ if (
1142
+ item.namespace !== undefined &&
1143
+ node.namespace !== item.namespace
1144
+ ) {
1145
+ continue;
1146
+ }
1147
+ // Adoption-agency clones carry no offsets; skip blank values.
1148
+ if (attr.valueStart === undefined || attr.valueStart === -1) {
1149
+ continue;
1150
+ }
1151
+ value = attr.value;
1152
+ if (!value || !/\S/.test(value)) continue;
1153
+ const filter = item.filter;
1154
+ if (filter) {
1155
+ // The filter also sees the decoded value (the browser tokenizes it
1156
+ // decoded, e.g. `&#117;rl(`), so a value-only check needn't re-read it.
1157
+ const decoded = value.includes("&")
1158
+ ? decodeHtmlEntities(value, true)
1159
+ : value;
1160
+ if (!filter(getAttributesMap(), decoded)) continue;
1161
+ }
1162
+ start = attr.valueStart;
1163
+ end = attr.valueEnd;
1164
+ type =
1165
+ typeof item.type === "function"
1166
+ ? item.type(getAttributesMap(), css)
1167
+ : item.type;
1168
+ }
1169
+ // A resolved `type` selects the parse algorithm and the dependency
1170
+ // kind the only dispatch in the parser.
1171
+ switch (type) {
1172
+ // Inline CSS — a `<style>` body (raw, a full stylesheet) or an
1173
+ // attribute value (decoded, re-escaped on write-back; `*-attribute`
1174
+ // is a declaration list). Needs `experiments.css`.
1175
+ case "stylesheet-style":
1176
+ case "stylesheet-style-attribute": {
1177
+ if (!css) break;
1178
+ const cssText = content
1179
+ ? value
1180
+ : decodeHtmlEntities(value, true);
1181
+ if (cssText.trim() === "") break;
1182
+ const dep = new HtmlInlineStyleDependency(
1183
+ dataUri("text/css", cssText),
1184
+ [start, end],
1185
+ content ? false : type === "stylesheet-style-attribute",
1186
+ !content
1187
+ );
1188
+ setLoc(dep, start, end);
1189
+ module.addDependency(dep);
1190
+ module.addCodeGenerationDependency(dep);
1191
+ break;
1192
+ }
1193
+ // `<iframe srcdoc>`: an entity-encoded HTML document. Feed the
1194
+ // decoded markup back through the HTML pipeline as a nested
1195
+ // `data:text/html` module. Per spec the value is a full document
1196
+ // whose URL is `about:srcdoc`, so its base URL is inherited from
1197
+ // this document — asset URLs resolve against this file's context.
1198
+ case "srcdoc": {
1199
+ const htmlText = decodeHtmlEntities(value, true);
1200
+ // Only spin up a nested module when there is markup that can
1201
+ // reference an asset; text/formatting-only markup is identical
1202
+ // after rewriting (see `SRCDOC_ASSET_REGEXP`).
1371
1203
  if (
1372
- input.slice(a.nameStart, a.nameEnd).toLowerCase() === "type"
1204
+ !htmlText.includes("<") ||
1205
+ !SRCDOC_ASSET_REGEXP.test(htmlText)
1373
1206
  ) {
1374
- typeAttr = a;
1375
1207
  break;
1376
1208
  }
1209
+ const dep = new HtmlInlineHtmlDependency(
1210
+ dataUri("text/html", htmlText),
1211
+ [start, end]
1212
+ );
1213
+ setLoc(dep, start, end);
1214
+ module.addDependency(dep);
1215
+ module.addCodeGenerationDependency(dep);
1216
+ break;
1217
+ }
1218
+ // Inline `<script>` body — bundled as its own entry chunk.
1219
+ case "script":
1220
+ case "script-module":
1221
+ if (content) {
1222
+ const scriptType =
1223
+ /** @type {"script" | "script-module"} */ (type);
1224
+ const request = dataUri("text/javascript", value);
1225
+ const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1226
+ const dep = new HtmlInlineScriptDependency(
1227
+ request,
1228
+ node.nameEnd,
1229
+ [start, end],
1230
+ entryName,
1231
+ scriptType === "script-module" ? "esm" : "commonjs"
1232
+ );
1233
+ setLoc(dep, start, end);
1234
+ module.addPresentationalDependency(dep);
1235
+ reconcileScriptTypeAttr(
1236
+ findAttr(attrs, "type"),
1237
+ node.nameEnd,
1238
+ scriptType,
1239
+ source
1240
+ );
1241
+ (scriptType === "script"
1242
+ ? scriptEntries
1243
+ : scriptModuleEntries
1244
+ ).push({
1245
+ request,
1246
+ entryName,
1247
+ type: scriptType
1248
+ });
1249
+ break;
1250
+ }
1251
+ // falls through — external `<script src>` joins the URL-bearing types
1252
+ case "src":
1253
+ case "srcset":
1254
+ case "css-url":
1255
+ case "msapplication-task":
1256
+ case "modulepreload":
1257
+ case "stylesheet": {
1258
+ // Parse the value into one or more URLs (decoding character
1259
+ // references, mapping spans back to raw offsets), then emit a plain
1260
+ // asset reference or an entry chunk per URL.
1261
+ const a = /** @type {import("./syntax").HtmlAttribute} */ (
1262
+ attr
1263
+ );
1264
+ const parse =
1265
+ type === "srcset"
1266
+ ? parseSrcset
1267
+ : type === "css-url"
1268
+ ? parseCssUrls
1269
+ : type === "msapplication-task"
1270
+ ? parseMsapplicationTask
1271
+ : parseSrc;
1272
+ let text = value;
1273
+ /** @type {number[] | undefined} */
1274
+ let map;
1275
+ if (value.includes("&")) {
1276
+ ({ text, map } = decodeHtmlEntitiesWithMap(value, true));
1277
+ if (!/\S/.test(text)) break;
1278
+ }
1279
+ /** @type {ParsedSource[] | undefined} */
1280
+ let parsed;
1281
+ try {
1282
+ parsed = parse(text);
1283
+ } catch (err) {
1284
+ const ds = locConverter.get(start);
1285
+ const de = locConverter.get(end);
1286
+ module.addError(
1287
+ new ModuleDependencyError(
1288
+ module,
1289
+ new WebpackError(
1290
+ `Bad value for attribute "${
1291
+ a.name
1292
+ }" on element "${elementName}": ${
1293
+ /** @type {Error} */ (err).message
1294
+ }`
1295
+ ),
1296
+ {
1297
+ start: { line: ds.line, column: ds.column },
1298
+ end: { line: de.line, column: de.column }
1299
+ }
1300
+ )
1301
+ );
1302
+ break;
1303
+ }
1304
+ if (!parsed) break;
1305
+ for (const [url, us, ue] of parsed) {
1306
+ // Internal `url(#id)` / fragment-only refs aren't assets.
1307
+ if (!url || url.startsWith("#")) continue;
1308
+ const s = start + (map ? map[us] : us);
1309
+ const e = start + (map ? map[ue] : ue);
1310
+ // `src`/`srcset`/`css-url`/`msapplication-task` are plain assets;
1311
+ // the rest are narrowed to entry types here and share one
1312
+ // `HtmlEntryDependency`, recorded for HtmlModulesPlugin.
1313
+ if (
1314
+ type === "script" ||
1315
+ type === "script-module" ||
1316
+ type === "modulepreload" ||
1317
+ type === "stylesheet"
1318
+ ) {
1319
+ const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1320
+ const willBeModuleScript =
1321
+ type === "script-module" ||
1322
+ (outputModule && type === "script");
1323
+ /** @type {EntrySourceType} */
1324
+ const elementKind =
1325
+ type === "modulepreload"
1326
+ ? "modulepreload"
1327
+ : type === "stylesheet"
1328
+ ? "stylesheet"
1329
+ : willBeModuleScript
1330
+ ? "script-module"
1331
+ : "script";
1332
+ const entryCategory =
1333
+ type === "stylesheet"
1334
+ ? "css-import"
1335
+ : type === "script-module" || type === "modulepreload"
1336
+ ? "esm"
1337
+ : undefined;
1338
+ // Native = the loading element is the tag the template clones
1339
+ // verbatim (`<script>` / `<link>`); a custom element is not.
1340
+ const nativeTag =
1341
+ elementKind === "stylesheet" ||
1342
+ elementKind === "modulepreload"
1343
+ ? "link"
1344
+ : "script";
1345
+ // CSP/fetch attributes the template copies onto siblings.
1346
+ let copyableAttrsText = "";
1347
+ let hasOwnCrossOrigin = false;
1348
+ if (node.start >= 0) {
1349
+ for (const copyableName of COPYABLE_SIBLING_ATTRS) {
1350
+ const copyableAttr = findAttr(attrs, copyableName);
1351
+ if (copyableAttr) {
1352
+ copyableAttrsText += attrSourceSpan(
1353
+ source,
1354
+ copyableAttr
1355
+ );
1356
+ if (copyableName === "crossorigin") {
1357
+ hasOwnCrossOrigin = true;
1358
+ }
1359
+ }
1360
+ }
1361
+ }
1362
+ if (
1363
+ firstScriptStart === -1 &&
1364
+ (type === "script" || type === "script-module")
1365
+ ) {
1366
+ firstScriptStart = node.start;
1367
+ }
1368
+ // Sibling-clone attribute edits, captured from the parsed
1369
+ // attributes (offsets relative to the tag) so the template
1370
+ // needn't re-parse the tag text: `integrity` is dropped
1371
+ // (content-specific) and `script-module` clones are forced
1372
+ // to `type="module"` (its value range, else inserted).
1373
+ /** @type {Range | null} */
1374
+ let integrityRange = null;
1375
+ /** @type {Range | null} */
1376
+ 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
+ }
1409
+ }
1410
+ }
1411
+ const dep = new HtmlEntryDependency(
1412
+ url,
1413
+ [s, e],
1414
+ entryName,
1415
+ entryCategory,
1416
+ elementKind,
1417
+ node.start,
1418
+ node.tagEnd,
1419
+ elementName === nativeTag,
1420
+ copyableAttrsText,
1421
+ node.nameEnd,
1422
+ hasOwnCrossOrigin,
1423
+ firstScriptStart,
1424
+ integrityRange,
1425
+ typeValueRange
1426
+ );
1427
+ setLoc(dep, s, e);
1428
+ module.addPresentationalDependency(dep);
1429
+ if (
1430
+ elementName === "script" &&
1431
+ (type === "script" || type === "script-module")
1432
+ ) {
1433
+ reconcileScriptTypeAttr(
1434
+ findAttr(attrs, "type"),
1435
+ node.nameEnd,
1436
+ type,
1437
+ source
1438
+ );
1439
+ }
1440
+ (type === "script"
1441
+ ? scriptEntries
1442
+ : type === "script-module"
1443
+ ? scriptModuleEntries
1444
+ : type === "stylesheet"
1445
+ ? stylesheetEntries
1446
+ : modulePreloadEntries
1447
+ ).push({ request: url, entryName, type });
1448
+ } else {
1449
+ const dep = new HtmlSourceDependency(url, [s, e]);
1450
+ setLoc(dep, s, e);
1451
+ module.addDependency(dep);
1452
+ module.addCodeGenerationDependency(dep);
1453
+ }
1454
+ }
1455
+ break;
1377
1456
  }
1378
- reconcileScriptTypeAttr(typeAttr, nameEnd, kind, input);
1379
1457
  }
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
1458
  }
1459
+ },
1460
+ exit: () => {
1461
+ pendingWebpackIgnore = undefined;
1398
1462
  }
1399
1463
  }
1464
+ })
1465
+ .process(source, { ast: buildHtmlAst(source) });
1400
1466
 
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);
1467
+ const buildInfo = /** @type {HtmlModuleBuildInfo} */ (module.buildInfo);
1458
1468
  buildInfo.strict = true;
1459
1469
  // 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.
1470
+ // real compilation entries during the finishMake hook. The `script`
1471
+ // and `script-module` groups are chained via a leader-only dependOn
1472
+ // so they share a runtime; `modulepreload` and `stylesheet` entries
1473
+ // are emitted as independent entries since `<link rel=modulepreload>`
1474
+ // must preload without running and stylesheets have no runtime.
1465
1475
  if (
1466
- classicEntries.length > 0 ||
1467
- esmScriptEntries.length > 0 ||
1476
+ scriptEntries.length > 0 ||
1477
+ scriptModuleEntries.length > 0 ||
1468
1478
  modulePreloadEntries.length > 0 ||
1469
1479
  stylesheetEntries.length > 0
1470
1480
  ) {
1471
- /** @type {Record<string, EntryScriptInfo[]>} */
1472
- (buildInfo.htmlEntryScripts) = {
1473
- classic: classicEntries,
1474
- "esm-script": esmScriptEntries,
1481
+ buildInfo.htmlEntryScripts = {
1482
+ script: scriptEntries,
1483
+ "script-module": scriptModuleEntries,
1475
1484
  modulepreload: modulePreloadEntries,
1476
1485
  stylesheet: stylesheetEntries
1477
1486
  };