@depup/webpack 5.110.3-depup.0 → 5.111.1-depup.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 (283) hide show
  1. package/README.md +7 -8
  2. package/changes.json +7 -11
  3. package/lib/APIPlugin.js +26 -11
  4. package/lib/AutomaticPrefetchPlugin.js +1 -1
  5. package/lib/Cache.js +8 -3
  6. package/lib/CacheFacade.js +1 -1
  7. package/lib/CircularModulesPlugin.js +157 -55
  8. package/lib/CleanPlugin.js +2 -2
  9. package/lib/CompatibilityPlugin.js +3 -4
  10. package/lib/Compilation.js +336 -337
  11. package/lib/Compiler.js +213 -51
  12. package/lib/ConstPlugin.js +16 -132
  13. package/lib/CopyPlugin.js +1054 -0
  14. package/lib/DefinePlugin.js +24 -8
  15. package/lib/DependencyTemplate.js +2 -1
  16. package/lib/DotenvPlugin.js +24 -28
  17. package/lib/ErrorHelpers.js +113 -0
  18. package/lib/ExportsInfo.js +20 -16
  19. package/lib/ExternalModule.js +47 -22
  20. package/lib/FileSystemInfo.js +17 -14
  21. package/lib/FlagDependencyExportsPlugin.js +53 -34
  22. package/lib/FlagDependencyUsagePlugin.js +9 -13
  23. package/lib/Generator.js +24 -2
  24. package/lib/HotModuleReplacementPlugin.js +55 -7
  25. package/lib/IgnorePlugin.js +1 -1
  26. package/lib/JavascriptMetaInfoPlugin.js +5 -3
  27. package/lib/LoaderOptionsPlugin.js +1 -1
  28. package/lib/MainTemplate.js +5 -1
  29. package/lib/Module.js +19 -10
  30. package/lib/ModuleGraph.js +6 -9
  31. package/lib/MultiCompiler.js +7 -17
  32. package/lib/MultiWatching.js +1 -1
  33. package/lib/NormalModule.js +135 -50
  34. package/lib/NormalModuleFactory.js +1 -1
  35. package/lib/RuntimeGlobals.js +21 -7
  36. package/lib/RuntimeModule.js +28 -12
  37. package/lib/RuntimePlugin.js +30 -3
  38. package/lib/RuntimeTemplate.js +813 -518
  39. package/lib/SourceMapDevToolPlugin.js +54 -104
  40. package/lib/TemplatedPathPlugin.js +16 -59
  41. package/lib/Watching.js +9 -0
  42. package/lib/WebpackOptionsApply.js +84 -91
  43. package/lib/asset/AssetBytesGenerator.js +7 -1
  44. package/lib/asset/AssetGenerator.js +50 -24
  45. package/lib/asset/AssetModule.js +3 -0
  46. package/lib/asset/AssetSourceGenerator.js +7 -1
  47. package/lib/asset/WebManifestParser.js +7 -5
  48. package/lib/buildChunkGraph.js +11 -15
  49. package/lib/cache/AddBuildDependenciesPlugin.js +33 -4
  50. package/lib/cache/IdleFileCachePlugin.js +2 -2
  51. package/lib/cache/MemoryCachePlugin.js +4 -4
  52. package/lib/cache/MemoryWithGcCachePlugin.js +7 -8
  53. package/lib/cache/PackFileCacheStrategy.js +17 -19
  54. package/lib/cache/getLazyHashedEtag.js +3 -5
  55. package/lib/config/browserslistTargetHandler.js +17 -4
  56. package/lib/config/defaults.js +138 -128
  57. package/lib/config/getClaimedAssetTypes.js +10 -11
  58. package/lib/config/normalization.js +82 -12
  59. package/lib/config/target.js +13 -4
  60. package/lib/container/ContainerReferencePlugin.js +6 -0
  61. package/lib/container/RemoteRuntimeModule.js +9 -0
  62. package/lib/container/declaredRemotes.js +39 -0
  63. package/lib/{ContextExclusionPlugin.js → context/ContextExclusionPlugin.js} +1 -1
  64. package/lib/{ContextModule.js → context/ContextModule.js} +54 -115
  65. package/lib/{ContextModuleFactory.js → context/ContextModuleFactory.js} +19 -21
  66. package/lib/{ContextReplacementPlugin.js → context/ContextReplacementPlugin.js} +4 -4
  67. package/lib/css/CssGenerator.js +27 -11
  68. package/lib/css/CssInjectStyleRuntimeModule.js +9 -0
  69. package/lib/css/CssLoadingRuntimeModule.js +94 -36
  70. package/lib/css/CssModule.js +1 -1
  71. package/lib/css/CssModulesPlugin.js +7 -6
  72. package/lib/css/CssParser.js +162 -31
  73. package/lib/css/CssServerStylesRuntimeModule.js +9 -0
  74. package/lib/css/cssMinify.js +43 -32
  75. package/lib/css/data.js +3514 -869
  76. package/lib/css/syntax.js +6370 -1220
  77. package/lib/debug/ProfilingPlugin.js +7 -4
  78. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -3
  79. package/lib/dependencies/AMDRuntimeModules.js +18 -0
  80. package/lib/dependencies/CommonJsExportRequireDependency.js +13 -12
  81. package/lib/dependencies/CommonJsExportsParserPlugin.js +12 -13
  82. package/lib/dependencies/CommonJsImportsParserPlugin.js +5 -5
  83. package/lib/dependencies/CommonJsPlugin.js +19 -3
  84. package/lib/dependencies/CommonJsRequireDependency.js +14 -5
  85. package/lib/dependencies/CommonJsSelfReferenceDependency.js +2 -4
  86. package/lib/dependencies/ContextDependency.js +1 -1
  87. package/lib/dependencies/ContextDependencyHelpers.js +3 -9
  88. package/lib/dependencies/ContextElementDependency.js +1 -1
  89. package/lib/dependencies/CssIcssExportDependency.js +13 -9
  90. package/lib/dependencies/CssIcssImportDependency.js +3 -6
  91. package/lib/dependencies/CssUrlDependency.js +3 -5
  92. package/lib/dependencies/HarmonyCompatibilityDependency.js +67 -22
  93. package/lib/dependencies/HarmonyDetectionParserPlugin.js +3 -4
  94. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +10 -7
  95. package/lib/dependencies/HarmonyExportExpressionDependency.js +47 -33
  96. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +74 -17
  97. package/lib/dependencies/HarmonyExportInitFragment.js +110 -23
  98. package/lib/dependencies/HarmonyExportSpecifierDependency.js +25 -58
  99. package/lib/dependencies/HarmonyImportBareSideEffectDependency.js +59 -0
  100. package/lib/dependencies/HarmonyImportDependency.js +6 -1
  101. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +111 -17
  102. package/lib/dependencies/HarmonyImportSideEffectDependency.js +3 -4
  103. package/lib/dependencies/HarmonyImportSpecifierDependency.js +40 -3
  104. package/lib/dependencies/HarmonyModulesPlugin.js +20 -0
  105. package/lib/dependencies/HtmlEntryDependency.js +124 -102
  106. package/lib/dependencies/HtmlInlineScriptDependency.js +3 -4
  107. package/lib/dependencies/HtmlInlineStyleDependency.js +3 -4
  108. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +1 -1
  109. package/lib/dependencies/ImportMetaGlobHelpers.js +1 -1
  110. package/lib/dependencies/ImportMetaMainDependency.js +104 -0
  111. package/lib/dependencies/ImportMetaPlugin.js +67 -25
  112. package/lib/dependencies/ImportParserPlugin.js +1 -1
  113. package/lib/dependencies/LoaderPlugin.js +26 -1
  114. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  115. package/lib/dependencies/SystemRuntimeModule.js +9 -0
  116. package/lib/dependencies/URLContextDependency.js +1 -1
  117. package/lib/dependencies/URLDependency.js +6 -5
  118. package/lib/dependencies/WorkerAndWorkletPlugin.js +15 -21
  119. package/lib/dependencies/WorkerDependency.js +7 -7
  120. package/lib/dependencies/WorkletDependency.js +7 -11
  121. package/lib/dll/DelegatedModule.js +18 -1
  122. package/lib/dll/DllReferencePlugin.js +11 -8
  123. package/lib/dll/LibManifestPlugin.js +1 -1
  124. package/lib/errors/AnalyzableBailoutsWarning.js +41 -0
  125. package/lib/errors/DuplicateModulesWarning.js +12 -7
  126. package/lib/errors/EmptyCopyPatternWarning.js +26 -0
  127. package/lib/errors/ExternalStarReexportError.js +44 -0
  128. package/lib/errors/HookWebpackError.js +3 -2
  129. package/lib/errors/MissingSourceMapsWarning.js +36 -0
  130. package/lib/errors/ModuleParseError.js +2 -1
  131. package/lib/errors/UnusedAssetsWarning.js +36 -0
  132. package/lib/errors/UnusedFederationWarning.js +35 -0
  133. package/lib/errors/UnusedModulesWarning.js +38 -0
  134. package/lib/esm/ExportWebpackRequireRuntimeModule.js +9 -0
  135. package/lib/esm/ModuleChunkFormatPlugin.js +28 -3
  136. package/lib/esm/ModuleChunkLoadingPlugin.js +30 -11
  137. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +92 -60
  138. package/lib/hmr/HotModuleReplacement.runtime.js +8 -2
  139. package/lib/hmr/HotModuleReplacementRuntimeModule.js +9 -0
  140. package/lib/hmr/LazyCompilationPlugin.js +3 -5
  141. package/lib/html/HtmlGenerator.js +72 -87
  142. package/lib/html/HtmlModulesPlugin.js +418 -200
  143. package/lib/html/HtmlParser.js +110 -147
  144. package/lib/html/builtinEmbeddedRenderer.js +107 -0
  145. package/lib/html/data.js +142 -7
  146. package/lib/html/htmlMinify.js +92 -122
  147. package/lib/html/syntax.js +1618 -1391
  148. package/lib/ids/HashedModuleIdsPlugin.js +2 -1
  149. package/lib/index.js +49 -3
  150. package/lib/javascript/ChunkFormatHelpers.js +4 -3
  151. package/lib/javascript/JavascriptGenerator.js +8 -1
  152. package/lib/javascript/JavascriptModule.js +1 -0
  153. package/lib/javascript/JavascriptModulesPlugin.js +73 -72
  154. package/lib/javascript/JavascriptParser.js +1970 -900
  155. package/lib/javascript/StartupHelpers.js +27 -3
  156. package/lib/javascript/data.js +172 -0
  157. package/lib/javascript/grammar.js +5546 -0
  158. package/lib/javascript/parser.js +1500 -0
  159. package/lib/javascript/regexp.js +1762 -0
  160. package/lib/javascript/regexpData.js +51 -0
  161. package/lib/javascript/syntax.js +9061 -5262
  162. package/lib/json/JsonGenerator.js +7 -1
  163. package/lib/library/ModuleLibraryPlugin.js +171 -132
  164. package/lib/loaders/LoaderRunner.js +137 -9
  165. package/lib/node/NodeWatchFileSystem.js +3 -9
  166. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +22 -0
  167. package/lib/node/RequireChunkLoadingRuntimeModule.js +22 -0
  168. package/lib/optimize/AggressiveMergingPlugin.js +3 -5
  169. package/lib/optimize/ConcatenatedModule.js +178 -120
  170. package/lib/optimize/ConstExportsPlugin.js +50 -39
  171. package/lib/optimize/FlagIncludedChunksPlugin.js +3 -11
  172. package/lib/optimize/InlineExports.js +20 -123
  173. package/lib/optimize/InnerGraphPlugin.js +7 -13
  174. package/lib/optimize/LimitChunkCountPlugin.js +5 -8
  175. package/lib/optimize/MangleExportsPlugin.js +13 -4
  176. package/lib/optimize/ModuleConcatenationPlugin.js +27 -5
  177. package/lib/optimize/RealContentHashPlugin.js +68 -9
  178. package/lib/optimize/SideEffectsFlagPlugin.js +24 -28
  179. package/lib/optimize/SplitChunksPlugin.js +2 -4
  180. package/lib/performance/AnalyzableBailoutsPlugin.js +115 -0
  181. package/lib/performance/BroadContextsPlugin.js +1 -1
  182. package/lib/performance/CacheEffectivenessPlugin.js +7 -7
  183. package/lib/performance/DuplicateModulesPlugin.js +57 -13
  184. package/lib/performance/EvalUsagePlugin.js +5 -2
  185. package/lib/performance/MissingSourceMapsPlugin.js +115 -0
  186. package/lib/performance/PureAnnotationsPlugin.js +6 -3
  187. package/lib/performance/SizeLimitsPlugin.js +2 -4
  188. package/lib/performance/TopLevelThisPlugin.js +5 -4
  189. package/lib/performance/UnusedAssetsPlugin.js +109 -0
  190. package/lib/performance/UnusedFederationPlugin.js +147 -0
  191. package/lib/performance/UnusedModulesPlugin.js +141 -0
  192. package/lib/performance/UnusedReexportsPlugin.js +1 -55
  193. package/lib/performance/isOnlyReexported.js +65 -0
  194. package/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js +25 -5
  195. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +8 -8
  196. package/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js +9 -0
  197. package/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js +9 -0
  198. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +9 -0
  199. package/lib/prefetch/ResourceHintPlugin.js +18 -23
  200. package/lib/prefetch/ResourceHintRuntimeModule.js +9 -0
  201. package/lib/prefetch/StartupAssetHintRuntimeModule.js +16 -10
  202. package/lib/rules/RuleSetCompiler.js +211 -4
  203. package/lib/runtime/AsyncModuleRuntimeModule.js +98 -66
  204. package/lib/runtime/AutoPublicPathRuntimeModule.js +9 -0
  205. package/lib/runtime/BaseUriRuntimeModule.js +9 -0
  206. package/lib/runtime/ChunkNameRuntimeModule.js +9 -0
  207. package/lib/runtime/EnsureChunkRuntimeModule.js +35 -16
  208. package/lib/runtime/GetChunkFilenameRuntimeModule.js +12 -4
  209. package/lib/runtime/GetFullHashRuntimeModule.js +9 -0
  210. package/lib/runtime/GetMainFilenameRuntimeModule.js +9 -0
  211. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +9 -0
  212. package/lib/runtime/GlobalRuntimeModule.js +11 -4
  213. package/lib/runtime/HasOwnPropertyRuntimeModule.js +9 -0
  214. package/lib/runtime/HelperRuntimeModule.js +10 -0
  215. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +305 -224
  216. package/lib/runtime/NonceRuntimeModule.js +9 -0
  217. package/lib/runtime/OnChunksLoadedRuntimeModule.js +67 -20
  218. package/lib/runtime/PublicPathRuntimeModule.js +9 -0
  219. package/lib/runtime/RuntimeIdRuntimeModule.js +9 -0
  220. package/lib/runtime/SpecNamespaceObjectRuntimeModule.js +90 -0
  221. package/lib/runtime/StartupChunkDependenciesRuntimeModule.js +9 -0
  222. package/lib/runtime/StartupEntrypointRuntimeModule.js +9 -0
  223. package/lib/runtime/SystemContextRuntimeModule.js +9 -0
  224. package/lib/runtime/ToBinaryRuntimeModule.js +9 -0
  225. package/lib/schemes/DataUriPlugin.js +3 -6
  226. package/lib/schemes/VirtualUrlPlugin.js +1 -1
  227. package/lib/serialization/BinaryMiddleware.js +9 -9
  228. package/lib/serialization/FileMiddleware.js +16 -16
  229. package/lib/serialization/ObjectMiddleware.js +168 -139
  230. package/lib/sharing/ConsumeSharedPlugin.js +12 -0
  231. package/lib/sharing/ConsumeSharedRuntimeModule.js +11 -0
  232. package/lib/sharing/ShareRuntimeModule.js +9 -0
  233. package/lib/sharing/declaredShared.js +47 -0
  234. package/lib/stats/DefaultStatsFactoryPlugin.js +6 -5
  235. package/lib/stats/DefaultStatsPrinterPlugin.js +3 -0
  236. package/lib/typescript/TypeScriptPlugin.js +2 -3
  237. package/lib/url/URLParserPlugin.js +6 -8
  238. package/lib/util/LocConverter.js +4 -7
  239. package/lib/util/SourceProcessor.js +91 -91
  240. package/lib/util/async.js +292 -0
  241. package/lib/util/buildDiagnostics.js +69 -0
  242. package/lib/util/chainedImports.js +3 -4
  243. package/lib/util/concatenate.js +4 -4
  244. package/lib/util/createHooksRegistry.js +1 -2
  245. package/lib/util/dataURL.js +2 -3
  246. package/lib/util/deterministicGrouping.js +8 -13
  247. package/lib/util/findGraphRoots.js +6 -13
  248. package/lib/util/fs.js +27 -1
  249. package/lib/util/globUtils.js +1 -1
  250. package/lib/util/handlerKeys.js +40 -0
  251. package/lib/util/hash/wasm-hash.js +3 -4
  252. package/lib/util/identifier.js +20 -0
  253. package/lib/util/internalSerializables.js +5 -1
  254. package/lib/util/registerExternalSerializer.js +6 -45
  255. package/lib/util/semver.js +5 -3
  256. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +9 -0
  257. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +9 -0
  258. package/lib/wasm-async/AsyncWebAssemblyGenerator.js +6 -1
  259. package/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +5 -1
  260. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +12 -4
  261. package/lib/wasm-sync/WebAssemblyGenerator.js +6 -1
  262. package/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +5 -1
  263. package/lib/wasm-sync/WebAssemblyUtils.js +2 -4
  264. package/lib/web/JsonpChunkLoadingRuntimeModule.js +36 -0
  265. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +19 -0
  266. package/package.json +55 -46
  267. package/schemas/WebpackOptions.check.js +1 -1
  268. package/schemas/WebpackOptions.json +379 -66
  269. package/schemas/plugins/LoaderOptionsPlugin.check.js +1 -1
  270. package/schemas/plugins/ManifestPlugin.check.js +1 -1
  271. package/schemas/plugins/SourceMapDevToolPlugin.check.js +1 -1
  272. package/schemas/plugins/asset/AssetGeneratorOptions.check.js +1 -1
  273. package/schemas/plugins/asset/AssetResourceGeneratorOptions.check.js +1 -1
  274. package/schemas/plugins/container/ContainerPlugin.check.js +1 -1
  275. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  276. package/schemas/plugins/debug/ProfilingPlugin.check.js +1 -1
  277. package/schemas/plugins/dll/DllReferencePlugin.check.js +1 -1
  278. package/schemas/plugins/ids/HashedModuleIdsPlugin.check.js +1 -1
  279. package/schemas/plugins/schemes/HttpUriPlugin.check.js +1 -1
  280. package/types.d.ts +1723 -604
  281. package/lib/dependencies/ExportBindingInitFragment.js +0 -191
  282. package/lib/errors/EntrypointOverlapWarning.js +0 -45
  283. package/lib/performance/EntrypointOverlapPlugin.js +0 -122
@@ -4,6 +4,7 @@
4
4
 
5
5
  "use strict";
6
6
 
7
+ const { basename, extname } = require("path");
7
8
  const { pathToFileURL } = require("url");
8
9
  const { AsyncSeriesHook, AsyncSeriesWaterfallHook } = require("tapable");
9
10
  const { RawSource } = require("webpack-sources");
@@ -19,10 +20,18 @@ const ConstDependency = require("../dependencies/ConstDependency");
19
20
  const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
20
21
  const WebpackError = require("../errors/WebpackError");
21
22
  const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin");
22
- const { compareModulesByFullName } = require("../util/comparators");
23
+ const ConcatenatedModule = require("../optimize/ConcatenatedModule");
24
+ const {
25
+ compareModulesByFullName,
26
+ compareModulesByIdentifier
27
+ } = require("../util/comparators");
23
28
  const createHash = require("../util/createHash");
24
29
  const createHooksRegistry = require("../util/createHooksRegistry");
25
- const { getUndoPath, makePathsRelative } = require("../util/identifier");
30
+ const {
31
+ getUndoPath,
32
+ makePathsRelative,
33
+ parseResource
34
+ } = require("../util/identifier");
26
35
  const implicitTypeLoaderFallback = require("../util/implicitTypeLoaderFallback");
27
36
  const lazyModule = require("../util/lazyModule");
28
37
  const memoize = require("../util/memoize");
@@ -45,20 +54,41 @@ const getHtmlParser = lazyModule(() => require("./HtmlParser"));
45
54
  // html generation only happens once a build actually has an html module
46
55
  const getHtmlGenerator = memoize(() => require("./HtmlGenerator"));
47
56
 
57
+ // only reached once a page names a CSS chunk of its own
58
+ const getCssModulesPlugin = memoize(() => require("../css/CssModulesPlugin"));
59
+
48
60
  /**
49
61
  * @import {
50
62
  * EntryDescriptionNormalized,
63
+ * FilenameTemplate,
51
64
  * OutputHtmlOptions,
52
65
  * HtmlFaviconIcon
53
66
  * } from "../../declarations/WebpackOptions"
54
67
  */
68
+ /**
69
+ * @import {
70
+ * OutputNormalizedWithDefaults as OutputOptions
71
+ * } from "../config/defaults"
72
+ */
55
73
  /** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["favicon"]} FaviconOption */
56
74
  /** @typedef {import("../../declarations/WebpackOptions").OutputHtmlOptions["manifest"]} ManifestOption */
57
75
  /** @typedef {Exclude<HtmlFaviconIcon, string>} FaviconIcon */
76
+ /** @import Chunk, { ChunkFilenameTemplate } from "../Chunk" */
77
+ /** @import ChunkGraph from "../ChunkGraph" */
58
78
  /** @import Compiler from "../Compiler" */
79
+ /** @import Module from "../Module" */
80
+ /** @typedef {import("../Compilation").EntryData} EntryData */
59
81
  /** @import { HtmlModuleBuildInfo } from "./HtmlModule" */
60
82
  /** @import HtmlParser from "./HtmlParser" */
61
- /** @typedef {{ request: string, entryName: string, type: "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch", css?: boolean }} HtmlEntryInfo */
83
+ /** @typedef {{ request: string, entryName: string, index: number, type: "script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch", css?: boolean }} HtmlEntryInfo */
84
+ /**
85
+ * What one extracted entry needs to be named. Recorded in page order once
86
+ * every page is built, and resolved to filenames once chunks exist.
87
+ * @typedef {object} PendingName
88
+ * @property {string} base name the tag's url (or its page) asks for, before numbering
89
+ * @property {FilenameTemplate} jsTemplate `output.filename` for this entry, as configured
90
+ * @property {string=} freeName name of a page-only entry, which emits nothing and leaves it free
91
+ */
62
92
 
63
93
  /** @typedef {{ outputName: string }} HtmlTransformHtmlContext */
64
94
  /** @typedef {{ outputName: string }} HtmlEmittedContext */
@@ -118,10 +148,9 @@ const createCompilationHooks = () => ({
118
148
 
119
149
  const PLUGIN_NAME = "HtmlModulesPlugin";
120
150
 
121
- // Built-in default favicon (the webpack logo). Referenced as a `file:` request
122
- // so `<link rel="icon">` in the synthetic wrapper flows through the normal
123
- // asset pipeline — emitted hashed, with the correct publicPath, no direct IO.
124
- // Resolved from `__filename` (a sibling of this module) — no `path` needed.
151
+ // Built-in default favicon, referenced as a `file:` request so the wrapper's
152
+ // `<link rel="icon">` flows through the normal asset pipeline: emitted hashed,
153
+ // with the right publicPath and no direct IO.
125
154
  const DEFAULT_FAVICON = new URL("./favicon.svg", pathToFileURL(__filename))
126
155
  .href;
127
156
 
@@ -222,9 +251,130 @@ const isIntegrityEnabled = (integrity) =>
222
251
  // `\.html`/`\.css` request matchers for the synthetic `output.html` wrapper.
223
252
  const HTML_REQUEST_RE = /\.html(\?|$)/i;
224
253
  const CSS_REQUEST_RE = /\.css(\?|$)/i;
225
- // The wrapper's own resource — the media type must end here, so that a
226
- // `data:text/htmlx,` entry (JavaScript to `DataUriPlugin`) is not one.
227
- const HTML_DATA_URL_RE = /^data:text\/html[,;]/i;
254
+ /**
255
+ * The entry a page is the whole of: nothing imports the module and the entry
256
+ * asks for nothing else, so the entry's JavaScript would be the page itself.
257
+ * @param {Compilation} compilation compilation
258
+ * @param {Module} module html module
259
+ * @returns {{ name: string, data: EntryData } | undefined} the page's own entry, when it has one
260
+ */
261
+ const getPageEntry = (compilation, module) => {
262
+ const { moduleGraph } = compilation;
263
+ for (const connection of moduleGraph.getIncomingConnections(module)) {
264
+ if (connection.originModule) return undefined;
265
+ }
266
+ for (const [name, data] of compilation.entries) {
267
+ if (data.dependencies.length !== 1) continue;
268
+ if (data.includeDependencies.length !== 0) continue;
269
+ if (moduleGraph.getModule(data.dependencies[0]) === module) {
270
+ return { name, data };
271
+ }
272
+ }
273
+ return undefined;
274
+ };
275
+
276
+ /**
277
+ * The entry of a page that emits nothing but its markup, so its chunk carries
278
+ * no JavaScript. An `output.library` asks for the page's exports, and keeps it.
279
+ * @param {Compilation} compilation compilation
280
+ * @param {Module} module html module
281
+ * @returns {{ name: string, data: EntryData } | undefined} the entry, when the page is all it emits
282
+ */
283
+ const getPageOnlyEntry = (compilation, module) => {
284
+ const entry = getPageEntry(compilation, module);
285
+ if (entry === undefined) return undefined;
286
+ const { library } = entry.data.options;
287
+ const configured =
288
+ library === undefined ? compilation.outputOptions.library : library;
289
+ return configured === undefined ? entry : undefined;
290
+ };
291
+
292
+ /**
293
+ * The basename a tag's url lends to the entry it becomes, so
294
+ * `src="./my-script.js"` emits `my-script.js`. Undefined for an inline body.
295
+ * @param {string} request the tag's request
296
+ * @returns {string | undefined} the basename, when the url carries one
297
+ */
298
+ const getRequestBaseName = (request) => {
299
+ // An inline `<script>` body is a `data:` request webpack writes itself.
300
+ if (request.startsWith("data:")) return undefined;
301
+ const { path: file } = parseResource(request);
302
+ return basename(file, extname(file)) || undefined;
303
+ };
304
+
305
+ /**
306
+ * Claims `base`, numbering it while something else already emits under that
307
+ * name, so two tags sharing a basename stay distinct.
308
+ * @param {string} base preferred name
309
+ * @param {Set<string>} taken names this kind of output already emits under
310
+ * @returns {string} the claimed name
311
+ */
312
+ const claimName = (base, taken) => {
313
+ let name = base;
314
+ for (let index = 1; taken.has(name); index++) name = `${base}${index}`;
315
+ taken.add(name);
316
+ return name;
317
+ };
318
+
319
+ // Placeholders that tell chunks apart on their own, so a template carrying
320
+ // one needs no `[name]`. Not `[id]`: an extracted entry's id is synthetic.
321
+ const CONTENT_PLACEHOLDER_REGEXP =
322
+ /\[(?:chunkhash|contenthash|fullhash|hash)[\]:]/;
323
+
324
+ // Basename stem of a filename template — what `[name]` stands in for, keeping
325
+ // the directory, extension and query the template asked for.
326
+ const TEMPLATE_STEM_REGEXP = /(^|\/)[^/?]*?((?:\.[^./?]+)*)(\?.*)?$/;
327
+
328
+ const NAME_PLACEHOLDER_REGEXP = /\[name\]/g;
329
+
330
+ /**
331
+ * A filename template usable for a page's extracted entries: it must tell one
332
+ * entry from another, or every entry of the page would be emitted to the same
333
+ * file. A template naming neither the chunk nor its content lends its directory
334
+ * and extension to a `[name]` instead.
335
+ * @param {string} template configured template
336
+ * @param {string} extension extension to fall back to when the template names none
337
+ * @returns {string} a template that tells the page's entries apart
338
+ */
339
+ const getNamedTemplate = (template, extension) => {
340
+ if (
341
+ template.includes("[name]") ||
342
+ CONTENT_PLACEHOLDER_REGEXP.test(template)
343
+ ) {
344
+ return template;
345
+ }
346
+ return template.replace(
347
+ TEMPLATE_STEM_REGEXP,
348
+ (_, dir, ext, query) => `${dir}[name]${ext || extension}${query || ""}`
349
+ );
350
+ };
351
+
352
+ /**
353
+ * The filename one extracted entry emits under, with `name` filling the
354
+ * `[name]` its template carries or the stem it leaves behind.
355
+ * @param {string} template configured template
356
+ * @param {string} extension extension to fall back to when the template names none
357
+ * @param {string} name the name this entry claimed
358
+ * @returns {string} the entry's filename template
359
+ */
360
+ const fillNamedTemplate = (template, extension, name) =>
361
+ getNamedTemplate(template, extension).replace(NAME_PLACEHOLDER_REGEXP, name);
362
+
363
+ /**
364
+ * The same, for a template `output.filename` states as a function: it is called
365
+ * at emit, and the path it returns read for a stem the same way.
366
+ * @param {FilenameTemplate} template configured template
367
+ * @param {string} extension extension to fall back to when the template names none
368
+ * @param {string} name the name this entry claimed
369
+ * @returns {ChunkFilenameTemplate} the entry's filename template
370
+ */
371
+ const getEntryTemplate = (template, extension, name) => {
372
+ if (typeof template === "function") {
373
+ return (pathData, assetInfo) =>
374
+ fillNamedTemplate(template(pathData, assetInfo), extension, name);
375
+ }
376
+ return fillNamedTemplate(template, extension, name);
377
+ };
228
378
 
229
379
  /**
230
380
  * Requests an `output.html` page must load for an entry: its `dependOn`
@@ -294,7 +444,7 @@ class HtmlModulesPlugin {
294
444
  * digest of `content`, with `nonNumericOnlyHash` applied — webpack's
295
445
  * standard `[contenthash]` recipe.
296
446
  * @param {string | Buffer} content content to hash
297
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
447
+ * @param {OutputOptions} outputOptions output options
298
448
  * @returns {string} content hash
299
449
  */
300
450
  static computeContentHash(content, outputOptions) {
@@ -316,7 +466,7 @@ class HtmlModulesPlugin {
316
466
  * initial chunks, `output.htmlChunkFilename` otherwise — the HTML counterpart
317
467
  * of `CssModulesPlugin.getChunkFilenameTemplate`.
318
468
  * @param {import("../Chunk")} chunk chunk
319
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
469
+ * @param {OutputOptions} outputOptions output options
320
470
  * @returns {import("../Chunk").ChunkFilenameTemplate} used filename template
321
471
  */
322
472
  static getChunkFilenameTemplate(chunk, outputOptions) {
@@ -355,10 +505,9 @@ class HtmlModulesPlugin {
355
505
  // several entries, so those can only be applied per emitted asset.
356
506
  /** @type {Map<string, OutputHtmlOptions>} */
357
507
  const htmlOptionsByEntry = new Map();
358
- // SRI sentinels are emitted while the page's HTML is generated, before the
359
- // owning entry is known, so any page asking for integrity turns them on for
360
- // all of them; pages that don't want SRI drop them again on emit. Authored
361
- // `.html` pages never reach the entry hook, hence the global seed.
508
+ // SRI sentinels are emitted before the owning entry is known, so one page
509
+ // asking for integrity turns them on for all and the rest drop them on emit.
510
+ // Authored `.html` pages never reach the entry hook, hence the global seed.
362
511
  let anyIntegrity = isIntegrityEnabled(
363
512
  globalHtmlOptions && globalHtmlOptions.integrity
364
513
  );
@@ -391,13 +540,9 @@ class HtmlModulesPlugin {
391
540
  return options;
392
541
  };
393
542
 
394
- // `output.html` (or an entry's `html`) wraps a non-HTML entry in a
395
- // synthetic HTML module so the existing pipeline injects its JS/CSS
396
- // chunks and applies the `template` option. `dependOn` ancestors are
397
- // injected first so the page loads the shared/runtime chunks before
398
- // the entry's own (see `collectHtmlEntryRequests`). `crossOriginLoading`
399
- // and SRI are applied to the injected tags centrally in
400
- // HtmlEntryDependency.
543
+ // `output.html` wraps a non-HTML entry in a synthetic HTML module, so the
544
+ // existing pipeline injects its chunks. `dependOn` ancestors go first, so the
545
+ // page loads shared and runtime chunks before the entry's own.
401
546
  EntryOptionPlugin.getHooks(compiler).entry.tap(
402
547
  PLUGIN_NAME,
403
548
  (context, name, desc) => {
@@ -472,24 +617,20 @@ class HtmlModulesPlugin {
472
617
  /** @type {Map<string, { content: string, source: import("webpack-sources").RawSource }>} */
473
618
  const sentinelResolvedSourceCache = new Map();
474
619
 
475
- // `<script src>` and `<link rel="modulepreload">` references collected
476
- // by HtmlParser become real compilation entries here. The `script`
477
- // and `script-module` groups are chained via a leader-only dependOn so
478
- // they share a runtime — the first entry of the group owns it and
479
- // every subsequent entry sets `dependOn: [leader]`. Modulepreload
480
- // entries are emitted as independent entries (no dependOn) so they
481
- // can never be imported as a runtime leader by a later script —
482
- // that's what keeps the "preload but don't execute" contract of
483
- // `<link rel="modulepreload">` intact.
484
- // Per-compilation state: the HTML modules seen during make (so
485
- // `finishMake` creates their entries without scanning the whole module
486
- // graph), the stylesheet entry names (read in `afterChunks`) and the html
487
- // options of every emitted page, keyed by its asset name.
488
- /** @type {WeakMap<import("../Compilation"), { htmlModules: Set<import("../Module")>, stylesheetEntries: Set<string>, htmlAssetOptions: Map<string, OutputHtmlOptions> }>} */
620
+ // WHY: the `script` and `script-module` groups are chained through a
621
+ // leader-only `dependOn`, so they share a runtime: the group's first entry
622
+ // owns it and every later one sets `dependOn: [leader]`. A modulepreload
623
+ // entry is emitted independently, with no `dependOn`, so a later script can
624
+ // never import it as a runtime leader — which is what keeps the "preload but
625
+ // don't execute" contract of `<link rel="modulepreload">` intact.
626
+ // Per-compilation state: the HTML modules seen during make, so `finishMake`
627
+ // creates their entries without scanning the module graph, plus the
628
+ // stylesheet entry names and each emitted page's html options.
629
+ /** @type {WeakMap<import("../Compilation"), { htmlModules: Set<Module>, stylesheetEntries: Set<string>, pendingNames: Map<string, PendingName>, htmlAssetOptions: Map<string, OutputHtmlOptions> }>} */
489
630
  const compilationState = new WeakMap();
490
631
  /**
491
632
  * @param {import("../Compilation")} compilation compilation
492
- * @returns {{ htmlModules: Set<import("../Module")>, stylesheetEntries: Set<string>, htmlAssetOptions: Map<string, OutputHtmlOptions> }} per-compilation state
633
+ * @returns {{ htmlModules: Set<Module>, stylesheetEntries: Set<string>, pendingNames: Map<string, PendingName>, htmlAssetOptions: Map<string, OutputHtmlOptions> }} per-compilation state
493
634
  */
494
635
  const getState = (compilation) => {
495
636
  let state = compilationState.get(compilation);
@@ -497,6 +638,7 @@ class HtmlModulesPlugin {
497
638
  state = {
498
639
  htmlModules: new Set(),
499
640
  stylesheetEntries: new Set(),
641
+ pendingNames: new Map(),
500
642
  htmlAssetOptions: new Map()
501
643
  };
502
644
  compilationState.set(compilation, state);
@@ -504,14 +646,19 @@ class HtmlModulesPlugin {
504
646
  return state;
505
647
  };
506
648
  compiler.hooks.finishMake.tapAsync(PLUGIN_NAME, (compilation, callback) => {
507
- const { htmlModules, stylesheetEntries } = getState(compilation);
508
-
509
- // Collect the entries an HTML module asks for. Only the script chains
510
- // (`script`, `script-module`) share a runtime via a leader-only
511
- // `dependOn`; `modulepreload`, `stylesheet` and `html` links are all
512
- // independent entries (a CSS or page entry must not chain into a JS
513
- // leader, or the chunk would mix unrelated outputs).
514
- /** @type {(module: import("../Module")) => { context: string, request: string, name: string, dependOn: string[] | undefined }[]} */
649
+ const { htmlModules, stylesheetEntries, pendingNames } =
650
+ getState(compilation);
651
+ /** @type {Set<string>} */
652
+ const extractedNames = new Set();
653
+ // The page each `type: "html"` link built, so the naming walk below can
654
+ // follow the links in document order instead of in build order.
655
+ /** @type {Map<string, Module>} */
656
+ const linkedPages = new Map();
657
+
658
+ // Collect the entries an HTML module asks for. Only the script chains share
659
+ // a runtime, through a leader-only `dependOn`; chaining a CSS or page entry
660
+ // into a JS leader would mix unrelated outputs into one chunk.
661
+ /** @type {(module: Module) => { context: string, request: string, name: string, dependOn: string[] | undefined }[]} */
515
662
  const collectEntrySpecs = (module) => {
516
663
  const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
517
664
  module.buildInfo
@@ -533,10 +680,11 @@ class HtmlModulesPlugin {
533
680
  leaderName = entry.entryName;
534
681
  }
535
682
  // Stylesheet entries and `as="style"` preload/prefetch entries emit
536
- // a CSS chunk, so they need the CSS filename template below.
537
- if (groupKind === "stylesheet" || entry.css) {
683
+ // a CSS chunk, so they are named from `output.cssFilename` alone.
684
+ if (groupKind === "stylesheet" || Boolean(entry.css)) {
538
685
  stylesheetEntries.add(entry.entryName);
539
686
  }
687
+ extractedNames.add(entry.entryName);
540
688
  specs.push({
541
689
  context,
542
690
  request: entry.request,
@@ -548,9 +696,70 @@ class HtmlModulesPlugin {
548
696
  return specs;
549
697
  };
550
698
 
699
+ // Records what a page's entries ask to be named, then walks its links —
700
+ // once every page is built, so the order is the documents', not the builds'.
701
+ /** @type {(module: Module, seen: Set<Module>) => void} */
702
+ const recordNames = (module, seen) => {
703
+ if (seen.has(module)) return;
704
+ seen.add(module);
705
+ const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
706
+ module.buildInfo
707
+ );
708
+ if (!htmlEntries) return;
709
+ const pageEntry = getPageOnlyEntry(compilation, module);
710
+ const namedEntry =
711
+ pageEntry !== undefined && !extractedNames.has(pageEntry.name)
712
+ ? pageEntry
713
+ : undefined;
714
+ const { resource } = /** @type {NormalModule} */ (module);
715
+ // An `output.html` page is a wrapper webpack writes around an entry, so
716
+ // what it loads is that entry's own output and takes its name.
717
+ const wrapsEntry =
718
+ namedEntry !== undefined &&
719
+ typeof resource === "string" &&
720
+ resource.startsWith("data:text/html");
721
+ const { filename } = compilation.outputOptions;
722
+ // Kept as configured, a function included, and read for a name once the
723
+ // chunks exist.
724
+ const jsTemplate =
725
+ namedEntry && namedEntry.data.options.filename !== undefined
726
+ ? namedEntry.data.options.filename
727
+ : filename;
728
+ // In document order, so of two tags sharing a basename the first a
729
+ // reader sees keeps it. A linked page is named on emit instead.
730
+ /** @type {HtmlEntryInfo[]} */
731
+ const ordered = [];
732
+ /** @type {HtmlEntryInfo[]} */
733
+ const links = [];
734
+ for (const [groupKind, group] of Object.entries(htmlEntries)) {
735
+ for (const entry of group) {
736
+ (groupKind === "html" ? links : ordered).push(entry);
737
+ }
738
+ }
739
+ ordered.sort((a, b) => a.index - b.index);
740
+ for (const entry of ordered) {
741
+ // An inline `<script>` body has no url to be named after, so it
742
+ // keeps the name of its own entry, page hash and tag index.
743
+ const base =
744
+ wrapsEntry && namedEntry
745
+ ? namedEntry.name
746
+ : getRequestBaseName(entry.request) || entry.entryName;
747
+ pendingNames.set(entry.entryName, {
748
+ base,
749
+ jsTemplate,
750
+ freeName: namedEntry && namedEntry.name
751
+ });
752
+ }
753
+ links.sort((a, b) => a.index - b.index);
754
+ for (const link of links) {
755
+ const page = linkedPages.get(link.entryName);
756
+ if (page) recordNames(page, seen);
757
+ }
758
+ };
759
+
551
760
  // Push one collected reference as a compilation entry; resolves with
552
761
  // the built entry module.
553
- /** @type {(spec: { context: string, request: string, name: string, dependOn: string[] | undefined }) => Promise<import("../Module") | null>} */
762
+ /** @type {(spec: { context: string, request: string, name: string, dependOn: string[] | undefined }) => Promise<Module | null>} */
554
763
  const addEntry = (spec) =>
555
764
  new Promise((resolve, reject) => {
556
765
  compilation.addEntry(
@@ -558,10 +767,8 @@ class HtmlModulesPlugin {
558
767
  EntryPlugin.createDependency(spec.request, { name: spec.name }),
559
768
  {
560
769
  name: spec.name,
561
- // Each entry gets its own filename from the synthetic entry name so
562
- // it doesn't collide with `output.filename`. CSS entries set their
563
- // `.css` name via `cssFilenameTemplate` below; `html` page entries
564
- // emit their file via `renderManifest`.
770
+ // A placeholder `afterChunks` overwrites for every entry it
771
+ // names; only a linked page keeps it, and it emits no JavaScript.
565
772
  filename: compilation.outputOptions.chunkFilename || "[name].js",
566
773
  dependOn: spec.dependOn
567
774
  },
@@ -575,7 +782,7 @@ class HtmlModulesPlugin {
575
782
  // every HTML entry already gets. `processed` guards diamonds and cycles.
576
783
  /** @type {WeakSet<import("../Module")>} */
577
784
  const processed = new WeakSet();
578
- /** @type {(module: import("../Module")) => Promise<void>} */
785
+ /** @type {(module: Module) => Promise<void>} */
579
786
  const processEntry = async (module) => {
580
787
  if (processed.has(module)) return;
581
788
  processed.add(module);
@@ -583,27 +790,30 @@ class HtmlModulesPlugin {
583
790
  collectEntrySpecs(module).map(async (spec) => {
584
791
  const entryModule = await addEntry(spec);
585
792
  if (entryModule && entryModule.type === HTML_MODULE_TYPE) {
793
+ linkedPages.set(spec.name, entryModule);
586
794
  await processEntry(entryModule);
587
795
  }
588
796
  })
589
797
  );
590
798
  };
591
- // Seed with the HTML modules seen during make (config entries /
592
- // imports, fresh or cache-restored); linked pages are reached by
593
- // recursion above.
594
- Promise.all([...htmlModules].map(processEntry)).then(
595
- () => callback(),
596
- callback
597
- );
799
+ // Seed with the HTML modules seen during make; linked pages are reached by
800
+ // the recursion above. Sorted because they are tracked in build-completion
801
+ // order, which the generated names must not depend on.
802
+ const seeds = [...htmlModules].sort(compareModulesByIdentifier);
803
+ Promise.all(seeds.map(processEntry)).then(() => {
804
+ /** @type {Set<Module>} */
805
+ const named = new Set();
806
+ for (const seed of seeds) recordNames(seed, named);
807
+ callback();
808
+ }, callback);
598
809
  });
599
810
 
600
811
  // `csp` injects a `<meta http-equiv="Content-Security-Policy">` and
601
- // `integrity` resolves the SRI sentinels — both once the page's inline
602
- // content is final (in `processAssets` below), per page, so an entry's
603
- // `html` can override them.
604
- // `"auto"` marks implicit enablement (no user rule for HTML files) — see
605
- // `applyExperimentsDefaults`. Only then loaders win over the built-in type
606
- // (e.g. html-webpack-plugin's template loader in its child compiler).
812
+ // `integrity` resolves the SRI sentinels, both per page once its inline
813
+ // content is final, so an entry's `html` can override them.
814
+
815
+ // `"auto"` marks implicit enablement, the one case where loaders win over
816
+ // the built-in type — see `applyExperimentsDefaults`.
607
817
  // TODO webpack 6: html defaults to `true`, drop this implicit-only fallback.
608
818
  const implicitlyEnabled = compiler.options.experiments.html === "auto";
609
819
  compiler.hooks.compilation.tap(
@@ -636,13 +846,12 @@ class HtmlModulesPlugin {
636
846
  compilation.hooks.succeedModule.tap(PLUGIN_NAME, trackHtmlModule);
637
847
  compilation.hooks.stillValidModule.tap(PLUGIN_NAME, trackHtmlModule);
638
848
 
639
- // Resolve integrity and inline sentinels after `RealContentHashPlugin`
640
- // so final bytes are in place. Only HTML pages carry these sentinels —
641
- // never JS chunks that embed an HTML string.
642
- // SRI only takes effect on a cross-origin subresource fetched
643
- // with CORS; without `output.crossOriginLoading` the browser
644
- // silently ignores `integrity` on cross-origin loads. Warn once
645
- // so a CDN deployment doesn't ship no-op integrity attributes.
849
+ // Resolve integrity and inline sentinels after `RealContentHashPlugin`,
850
+ // so final bytes are in place. Only HTML pages carry them.
851
+
852
+ // SRI takes effect only on a cross-origin subresource fetched with CORS,
853
+ // so without `output.crossOriginLoading` the browser ignores `integrity`.
854
+ // Warn once, rather than let a CDN deployment ship no-op attributes.
646
855
  if (anyIntegrity && !compilation.outputOptions.crossOriginLoading) {
647
856
  compilation.warnings.push(
648
857
  new WebpackError(
@@ -688,12 +897,9 @@ class HtmlModulesPlugin {
688
897
  inlinedFiles
689
898
  );
690
899
  }
691
- // `injectTags` (add tags), `transformTags` (mutate/move/remove the
692
- // page's own and injected tags), and CSP all run over a single
693
- // parse: collect the tags/anchors once, let the hooks act, then
694
- // render every edit — placement, attribute rewrites, moves,
695
- // removals, CSP hashes/nonce/meta — in one pass. Injected inline
696
- // `<script>`/`<style>` are hashed like the page's own.
900
+ // `injectTags`, `transformTags` and CSP all run over a single parse:
901
+ // collect the tags and anchors once, let the hooks act, then render
902
+ // every edit in one pass.
697
903
  const injected = await hooks.injectTags.promise([], {
698
904
  outputName: name,
699
905
  html: resolved
@@ -725,26 +931,6 @@ class HtmlModulesPlugin {
725
931
  // a content-hashed filename makes a substring hit a real reference.
726
932
  /** @type {Set<string>} */
727
933
  const deletionCandidates = new Set(inlinedFiles);
728
- // A wrapper entry's chunk only rebuilds the page's own markup; the
729
- // page loads the `<script src>` entries the parser split out of it.
730
- for (const entrypoint of compilation.entrypoints.values()) {
731
- const chunk = entrypoint.getEntrypointChunk();
732
- if (!chunk) continue;
733
- let wrapperOnly = false;
734
- for (const entryModule of compilation.chunkGraph.getChunkEntryModulesIterable(
735
- chunk
736
- )) {
737
- const { resource } = /** @type {import("../NormalModule")} */ (
738
- entryModule
739
- );
740
- wrapperOnly =
741
- typeof resource === "string" &&
742
- HTML_DATA_URL_RE.test(resource);
743
- if (!wrapperOnly) break;
744
- }
745
- if (!wrapperOnly) continue;
746
- for (const file of chunk.files) deletionCandidates.add(file);
747
- }
748
934
  // Materialize each other asset's source once (it can be expensive to
749
935
  // render) and test all candidates against it, rather than
750
936
  // re-materializing every asset per candidate.
@@ -787,45 +973,70 @@ class HtmlModulesPlugin {
787
973
  }
788
974
  );
789
975
 
790
- // CSS entries created by `<link rel="stylesheet">` in HTML need
791
- // their `.css` filename set via `chunk.cssFilenameTemplate`
792
- // (the field `CssModulesPlugin.getChunkFilenameTemplate` reads).
793
- // Compilation only flows `options.filename` to `chunk.filenameTemplate`,
794
- // which controls JS emit — there's no entry-level `cssFilename`.
795
- // Set it ourselves after chunks are created so each stylesheet
796
- // entry emits to a distinct file derived from `output.cssFilename`
797
- // (or `output.cssChunkFilename` for non-initial CSS chunks).
976
+ // There is no entry-level `cssFilename`: Compilation flows
977
+ // `options.filename` only to `chunk.filenameTemplate`, which drives JS
978
+ // emit. So set `chunk.cssFilenameTemplate` here, once chunks exist.
798
979
  compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => {
799
- const { stylesheetEntries } = getState(compilation);
800
- if (stylesheetEntries.size === 0) return;
801
- for (const entryName of stylesheetEntries) {
980
+ const { stylesheetEntries, pendingNames } = getState(compilation);
981
+ if (pendingNames.size === 0) return;
982
+ const { chunkGraph, outputOptions } = compilation;
983
+ const jsExtension = outputOptions.module ? ".mjs" : ".js";
984
+ // Claimed in the order the pages recorded them, which is the
985
+ // documents' order rather than their builds'.
986
+ const takenJsNames = new Set(compilation.entries.keys());
987
+ const takenCssNames = new Set(takenJsNames);
988
+ // An extracted entry emits nothing under its own synthetic name, so
989
+ // that name never stands in the way of the one its tag asks for.
990
+ for (const entryName of pendingNames.keys()) {
991
+ takenJsNames.delete(entryName);
992
+ takenCssNames.delete(entryName);
993
+ }
994
+ for (const { freeName } of pendingNames.values()) {
995
+ if (freeName === undefined) continue;
996
+ // A page-only entry emits nothing of its own, so its name is free for
997
+ // the entries extracted from it — `app.html` keeps `app.js`.
998
+ takenJsNames.delete(freeName);
999
+ takenCssNames.delete(freeName);
1000
+ }
1001
+ for (const [entryName, { base, jsTemplate }] of pendingNames) {
802
1002
  const entrypoint = compilation.entrypoints.get(entryName);
803
1003
  if (!entrypoint) continue;
804
1004
  const chunk = entrypoint.getEntrypointChunk();
805
1005
  if (!chunk) continue;
806
- // Each html-derived stylesheet entry uses the
807
- // `cssChunkFilename` template — even though the entry
808
- // chunk technically `canBeInitial()`, we deliberately
809
- // avoid `cssFilename` here because that template often
810
- // has no per-entry placeholder (it's derived from
811
- // `output.filename`, which can be a literal like
812
- // `bundle0.js`), and multiple `<link rel="stylesheet">`
813
- // tags would then collide on the same emitted `.css`
814
- // file. `cssChunkFilename` is derived from
815
- // `output.chunkFilename` which webpack auto-extends
816
- // with `[id].` when needed, guaranteeing uniqueness.
817
- chunk.cssFilenameTemplate =
818
- compilation.outputOptions.cssChunkFilename;
1006
+ // A stylesheet entry emits no JavaScript, so it claims no JS name and
1007
+ // leaves that one to a `<script src>` of the same basename.
1008
+ const emitsCss = stylesheetEntries.has(entryName);
1009
+ if (!emitsCss) {
1010
+ // `output.filename` named after the tag's url, the way an entry
1011
+ // point is named. `Compilation` set it from the placeholder above.
1012
+ chunk.filenameTemplate = getEntryTemplate(
1013
+ jsTemplate,
1014
+ jsExtension,
1015
+ claimName(base, takenJsNames)
1016
+ );
1017
+ }
1018
+ // A `<script src>` whose chunk carries no CSS claims no CSS name, so
1019
+ // a `<link>` of the same basename still gets the plain one.
1020
+ if (
1021
+ !emitsCss &&
1022
+ !getCssModulesPlugin().chunkHasCss(chunk, chunkGraph)
1023
+ ) {
1024
+ continue;
1025
+ }
1026
+ chunk.cssFilenameTemplate = getEntryTemplate(
1027
+ outputOptions.cssFilename,
1028
+ ".css",
1029
+ claimName(base, takenCssNames)
1030
+ );
819
1031
  }
820
1032
  });
821
1033
  compilation.dependencyTemplates.set(
822
1034
  StaticExportsDependency,
823
1035
  new StaticExportsDependency.Template()
824
1036
  );
825
- // `ConstDependency` is used by HtmlParser to insert
826
- // ` type="module"` into the rewritten <script> tag when
827
- // `output.module` is on. Register its template so the HTML
828
- // generator runs the insertion.
1037
+ // HtmlParser uses `ConstDependency` to insert ` type="module"` into a
1038
+ // rewritten `<script>` under `output.module`; its template has to be
1039
+ // registered for the HTML generator to run that insertion.
829
1040
  compilation.dependencyTemplates.set(
830
1041
  ConstDependency,
831
1042
  new ConstDependency.Template()
@@ -857,10 +1068,9 @@ class HtmlModulesPlugin {
857
1068
  HtmlEntryDependency,
858
1069
  new HtmlEntryDependency.Template()
859
1070
  );
860
- // Inline `<script>` content is bundled as its own entry — the
861
- // same pipeline that handles `<script src>` — via a
862
- // `data:text/javascript,...` request. The dependency
863
- // template rewrites the original tag to `<script src=…>`.
1071
+ // Inline `<script>` content becomes its own entry through a
1072
+ // `data:text/javascript,...` request, the same pipeline as
1073
+ // `<script src>`; the template then rewrites the tag.
864
1074
  compilation.dependencyFactories.set(
865
1075
  HtmlInlineScriptDependency,
866
1076
  normalModuleFactory
@@ -869,11 +1079,9 @@ class HtmlModulesPlugin {
869
1079
  HtmlInlineScriptDependency,
870
1080
  new HtmlInlineScriptDependency.Template()
871
1081
  );
872
- // Inline `<style>` content is routed through the CSS pipeline
873
- // as a `data:text/css` module. The dependency template reads
874
- // the processed CSS text from the CSS module's code
875
- // generation data (`css-text` channel set by CssGenerator
876
- // when `exportType` is `"text"`).
1082
+ // Inline `<style>` content goes through the CSS pipeline as a
1083
+ // `data:text/css` module; the template reads the processed text
1084
+ // from the `css-text` code-generation channel.
877
1085
  compilation.dependencyFactories.set(
878
1086
  HtmlInlineStyleDependency,
879
1087
  normalModuleFactory
@@ -933,7 +1141,8 @@ class HtmlModulesPlugin {
933
1141
  );
934
1142
  return new (getHtmlGenerator())(
935
1143
  generatorOptions,
936
- compilation.moduleGraph
1144
+ compilation.moduleGraph,
1145
+ (module) => getPageOnlyEntry(compilation, module) !== undefined
937
1146
  );
938
1147
  });
939
1148
 
@@ -942,10 +1151,9 @@ class HtmlModulesPlugin {
942
1151
  (result, module) => {
943
1152
  if (module.type === HTML_MODULE_TYPE) {
944
1153
  const [source, ...rest] = removeBOMFromResult(result);
945
- // `applyTemplate` is a no-op unless `module.parser.html.template`
946
- // is set. Running it here (where the returned source becomes the
947
- // module's stored source) keeps the parser's dependency offsets
948
- // and the generator's render base in sync.
1154
+ // Run here, where the returned source becomes the module's stored
1155
+ // source, so the parser's dependency offsets and the generator's
1156
+ // render base stay in sync.
949
1157
  const parser = /** @type {HtmlParser} */ (module.parser);
950
1158
 
951
1159
  return [parser.applyTemplate(source, module), ...rest];
@@ -955,35 +1163,20 @@ class HtmlModulesPlugin {
955
1163
  }
956
1164
  );
957
1165
 
958
- // Emit extracted `.html` files for any HTML module that opted
959
- // into extraction. The opt-in is computed by
960
- // `HtmlGenerator#_shouldExtract`: `module.generator.html.extract:
961
- // true` always extracts, `false` never extracts, and when
962
- // `extract` is unset the generator extracts iff the HTML module
963
- // is a compilation entry — the iteration below picks up only
964
- // modules whose generator reported the `html` source type, so
965
- // that decision is honored implicitly. The HTML content is read
966
- // from the generator's secondary `"html"` source type (see
967
- // HtmlGenerator#generate). The filename template comes from
968
- // `output.htmlFilename` (initial chunks) or
969
- // `output.htmlChunkFilename` (non-initial chunks), mirroring
970
- // the CSS pipeline. Path data follows the asset-module pattern —
971
- // `module` + a relative source `filename`, with `chunk`
972
- // intentionally omitted so `[name]` resolves to the HTML
973
- // source's basename (e.g. `page` for `./page.html`) rather
974
- // than the importing chunk's name (e.g. `main`). A per-module
975
- // content hash is computed from the rewritten HTML so the
976
- // template's `[contenthash]` placeholder works; the
977
- // compilation hash is also forwarded so `[fullhash]` /
978
- // `[hash]` work in user-supplied templates.
979
-
980
- // Sentinel-resolved content and its content hash depend only on
981
- // the HTML-type module source — not on the chunk or output
982
- // filename — yet `renderManifest` runs per `(chunk, module)`.
983
- // Memoize so a module landing in multiple chunks resolves and
984
- // hashes its sentinels once. Scoped to this compilation because
985
- // sentinel resolution embeds chunk filenames, which change
986
- // across rebuilds; weakly keyed by source so entries release.
1166
+ // WHY: emit extracted `.html` files for any HTML module that opted in.
1167
+ // `HtmlGenerator#_shouldExtract` computes that opt-in —
1168
+ // `module.generator.html.extract: true` always extracts, `false` never,
1169
+ // and unset extracts only a compilation entry — and the iteration below
1170
+ // picks up only modules whose generator reported the `html` source type,
1171
+ // so that decision is honoured implicitly. The filename comes from
1172
+ // `output.htmlFilename` for initial chunks and `output.htmlChunkFilename`
1173
+ // otherwise, mirroring the CSS pipeline, and the path data follows the
1174
+ // asset-module pattern with `chunk` deliberately left out, so `[name]`
1175
+ // resolves to the HTML source's basename rather than the chunk's.
1176
+
1177
+ // `renderManifest` runs per `(chunk, module)`, but resolved content and
1178
+ // its hash depend only on the module source, so memoize. Scoped to this
1179
+ // compilation, since resolution embeds chunk filenames.
987
1180
  /** @type {WeakMap<import("webpack-sources").Source, { resolvedContent: string, contentHash: string }>} */
988
1181
  const resolvedSentinelHashCache = new WeakMap();
989
1182
 
@@ -1005,15 +1198,12 @@ class HtmlModulesPlugin {
1005
1198
  if (!placeholderSource) return undefined;
1006
1199
  let cached = resolvedSentinelHashCache.get(placeholderSource);
1007
1200
  if (cached === undefined) {
1008
- // Resolve chunk-URL sentinels *before* hashing so the HTML's
1009
- // `[contenthash]` invalidates with the referenced chunks'
1010
- // filenames. Inlined chunks have no URL, so tag their inline
1011
- // sentinels with the chunk content hash — that keeps the emitted
1012
- // bytes (and RealContentHashPlugin's later recompute)
1013
- // content-dependent, so the page hash tracks inlined content too.
1014
- // Asset URL sentinels (from `HtmlEntryDependency` resource-hint
1015
- // tags) resolve here too — deferred from template.apply so it
1016
- // doesn't race with asset-module codegen.
1201
+ // Resolve chunk-URL sentinels before hashing, so the page's
1202
+ // `[contenthash]` tracks the referenced chunks. An inlined chunk has
1203
+ // no URL, so its sentinel carries the chunk content hash instead.
1204
+
1205
+ // Asset URL sentinels resolve here rather than in `template.apply`,
1206
+ // which would race with asset-module codegen.
1017
1207
  const resolvedContent = getHtmlGenerator().resolveAssetUrlSentinels(
1018
1208
  getHtmlGenerator().embedInlineChunkHashes(
1019
1209
  getHtmlGenerator().resolveChunkUrlSentinels(
@@ -1109,9 +1299,8 @@ class HtmlModulesPlugin {
1109
1299
  compilation.hooks.renderManifest.tap(
1110
1300
  PLUGIN_NAME,
1111
1301
  (result, { chunk }) => {
1112
- // HMR's `HotUpdateChunk`s flow through the same hook
1113
- // but aren't real output chunks — extracting `.html`
1114
- // for them would create stray hot-update HTML files.
1302
+ // HMR's `HotUpdateChunk`s reach this hook but are not output chunks,
1303
+ // so extracting `.html` for them would emit stray hot-update files.
1115
1304
  // `CssModulesPlugin` early-returns for the same reason.
1116
1305
  if (chunk instanceof HotUpdateChunk) return result;
1117
1306
  const { chunkGraph } = compilation;
@@ -1125,12 +1314,13 @@ class HtmlModulesPlugin {
1125
1314
  const outputOptions = compilation.outputOptions;
1126
1315
  for (const module of modules) {
1127
1316
  const normalModule = /** @type {NormalModule} */ (module);
1128
- // `<iframe srcdoc>` modules expose `html` only so HtmlInlineHtmlDependency
1129
- // can write the processed markup back into the host attribute — they are
1130
- // never standalone pages. `generator.extract: "inline"` marks them so no
1131
- // `.html` file is emitted; without this they'd collide on the
1132
- // `data:text/html` → chunk-name path (every srcdoc module in a chunk would
1133
- // emit `<chunk>.html`). An O(1) flag check, not a scan.
1317
+ // WHY: an `<iframe srcdoc>` module exposes `html` only so that
1318
+ // `HtmlInlineHtmlDependency` can write the processed markup back
1319
+ // into the host attribute; it is never a standalone page.
1320
+ // `generator.extract: "inline"` marks it so no `.html` file is
1321
+ // emitted — without it they would collide on the `data:text/html`
1322
+ // to chunk-name path, every srcdoc module in a chunk emitting
1323
+ // `<chunk>.html`. An O(1) flag check rather than a scan.
1134
1324
  const generatorOptions = normalModule.generatorOptions;
1135
1325
  if (generatorOptions && generatorOptions.extract === "inline") {
1136
1326
  continue;
@@ -1140,11 +1330,9 @@ class HtmlModulesPlugin {
1140
1330
  if (!emit) continue;
1141
1331
  const { resolvedContent, contentHash, filename, info } = emit;
1142
1332
 
1143
- // Resolve any remaining `[webpack/auto]` placeholders to an undo
1144
- // path computed from the emitted HTML's location, so asset/chunk
1145
- // URLs stay relative to `output.path` even when the page emits into
1146
- // a subdirectory. A relative `<base href>` prepends `../`s so the
1147
- // base can't misdirect the rewritten URLs (see `HtmlParser`).
1333
+ // Resolve the remaining `[webpack/auto]` placeholders against the
1334
+ // emitted page's own location, so URLs stay relative to `output.path`
1335
+ // from a subdirectory. A relative `<base href>` prepends `../`s.
1148
1336
  const basePrefix =
1149
1337
  /** @type {HtmlModuleBuildInfo} */ (normalModule.buildInfo)
1150
1338
  .baseUrlPrefix || "";
@@ -1166,12 +1354,9 @@ class HtmlModulesPlugin {
1166
1354
  .split(autoPlaceholder)
1167
1355
  .join(undoPath);
1168
1356
  const finalSource = new RawSource(finalContent);
1169
- // The same HTML module can land in multiple chunks with different
1170
- // `htmlFilename`/`htmlChunkFilename` shapes → different `undoPath`s and
1171
- // final content for the same module id. Include the emitted filename in
1172
- // the asset cache key and the post-undo-path content in the hash so the
1173
- // asset cache can't reuse one variant's bytes at another variant's URL.
1174
- // Unchanged content reuses the memoized hash instead of re-digesting.
1357
+ // One module can land in several chunks whose filename shapes give
1358
+ // different undo paths, so the emitted filename joins the cache key
1359
+ // and the post-undo content the hash — else variants alias.
1175
1360
  const finalContentHash =
1176
1361
  finalContent === resolvedContent
1177
1362
  ? contentHash
@@ -1211,6 +1396,16 @@ class HtmlModulesPlugin {
1211
1396
  jsHooks.render.tap(PLUGIN_NAME, (source, renderContext) => {
1212
1397
  // No HTML modules ⇒ no sentinels; skip materializing the JS source.
1213
1398
  if (htmlModules.size === 0) return source;
1399
+ // Chunks without HTML modules can't carry HTML sentinels — avoid
1400
+ // ConcatSource.source() on the common pure-JS chunk path.
1401
+ if (
1402
+ !HtmlModulesPlugin.chunkHasHtml(
1403
+ renderContext.chunk,
1404
+ compilation.chunkGraph
1405
+ )
1406
+ ) {
1407
+ return source;
1408
+ }
1214
1409
  const raw = source.source();
1215
1410
  if (typeof raw !== "string") return source;
1216
1411
  // Every sentinel shares this prefix — one scan answers the common
@@ -1281,6 +1476,29 @@ class HtmlModulesPlugin {
1281
1476
  }
1282
1477
  );
1283
1478
  }
1479
+
1480
+ /**
1481
+ * Returns true, when the chunk has html.
1482
+ * @param {Chunk} chunk chunk
1483
+ * @param {ChunkGraph} chunkGraph chunk graph
1484
+ * @returns {boolean} true, when the chunk has html
1485
+ */
1486
+ static chunkHasHtml(chunk, chunkGraph) {
1487
+ if (chunkGraph.getChunkModulesIterableBySourceType(chunk, HTML_TYPE)) {
1488
+ return true;
1489
+ }
1490
+ // Scope-hoisted HTML loses HTML_TYPE on the ConcatenatedModule, but its
1491
+ // JS export can still carry unresolved sentinels that need this path.
1492
+ for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
1493
+ if (module instanceof HtmlModule) return true;
1494
+ if (module instanceof ConcatenatedModule) {
1495
+ for (const inner of module.modules) {
1496
+ if (inner instanceof HtmlModule) return true;
1497
+ }
1498
+ }
1499
+ }
1500
+ return false;
1501
+ }
1284
1502
  }
1285
1503
 
1286
1504
  /**