@depup/webpack 5.110.1-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 (304) 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/ChunkGraph.js +20 -18
  8. package/lib/CircularModulesPlugin.js +157 -55
  9. package/lib/CleanPlugin.js +2 -2
  10. package/lib/CompatibilityPlugin.js +3 -4
  11. package/lib/Compilation.js +337 -338
  12. package/lib/Compiler.js +213 -51
  13. package/lib/ConcatenationScope.js +25 -35
  14. package/lib/ConstPlugin.js +16 -132
  15. package/lib/CopyPlugin.js +1054 -0
  16. package/lib/DefinePlugin.js +24 -8
  17. package/lib/DependencyTemplate.js +2 -1
  18. package/lib/DotenvPlugin.js +64 -40
  19. package/lib/ErrorHelpers.js +113 -0
  20. package/lib/ExportsInfo.js +30 -16
  21. package/lib/ExternalModule.js +47 -22
  22. package/lib/FileSystemInfo.js +17 -14
  23. package/lib/FlagDependencyExportsPlugin.js +53 -34
  24. package/lib/FlagDependencyUsagePlugin.js +17 -15
  25. package/lib/Generator.js +24 -2
  26. package/lib/HotModuleReplacementPlugin.js +55 -7
  27. package/lib/IgnorePlugin.js +1 -1
  28. package/lib/JavascriptMetaInfoPlugin.js +5 -3
  29. package/lib/LoaderOptionsPlugin.js +1 -1
  30. package/lib/MainTemplate.js +5 -1
  31. package/lib/Module.js +19 -10
  32. package/lib/ModuleGraph.js +6 -9
  33. package/lib/MultiCompiler.js +7 -17
  34. package/lib/MultiWatching.js +1 -1
  35. package/lib/NormalModule.js +148 -70
  36. package/lib/NormalModuleFactory.js +1 -1
  37. package/lib/RuntimeGlobals.js +21 -7
  38. package/lib/RuntimeModule.js +28 -12
  39. package/lib/RuntimePlugin.js +33 -3
  40. package/lib/RuntimeTemplate.js +1147 -847
  41. package/lib/SourceMapDevToolPlugin.js +54 -104
  42. package/lib/TemplatedPathPlugin.js +16 -59
  43. package/lib/Watching.js +9 -0
  44. package/lib/WebpackOptionsApply.js +84 -91
  45. package/lib/asset/AssetBytesGenerator.js +7 -1
  46. package/lib/asset/AssetGenerator.js +50 -24
  47. package/lib/asset/AssetModule.js +3 -0
  48. package/lib/asset/AssetSourceGenerator.js +7 -1
  49. package/lib/asset/WebManifestParser.js +7 -5
  50. package/lib/async-modules/AsyncModuleHelpers.js +10 -4
  51. package/lib/buildChunkGraph.js +11 -15
  52. package/lib/cache/AddBuildDependenciesPlugin.js +33 -4
  53. package/lib/cache/IdleFileCachePlugin.js +2 -2
  54. package/lib/cache/MemoryCachePlugin.js +4 -4
  55. package/lib/cache/MemoryWithGcCachePlugin.js +7 -8
  56. package/lib/cache/PackFileCacheStrategy.js +17 -19
  57. package/lib/cache/getLazyHashedEtag.js +3 -5
  58. package/lib/config/browserslistTargetHandler.js +17 -4
  59. package/lib/config/defaults.js +162 -142
  60. package/lib/config/getClaimedAssetTypes.js +10 -11
  61. package/lib/config/normalization.js +100 -20
  62. package/lib/config/target.js +13 -4
  63. package/lib/container/ContainerReferencePlugin.js +6 -0
  64. package/lib/container/RemoteRuntimeModule.js +9 -0
  65. package/lib/container/declaredRemotes.js +39 -0
  66. package/lib/{ContextExclusionPlugin.js → context/ContextExclusionPlugin.js} +1 -1
  67. package/lib/{ContextModule.js → context/ContextModule.js} +54 -115
  68. package/lib/{ContextModuleFactory.js → context/ContextModuleFactory.js} +19 -21
  69. package/lib/{ContextReplacementPlugin.js → context/ContextReplacementPlugin.js} +4 -4
  70. package/lib/css/CssGenerator.js +27 -11
  71. package/lib/css/CssInjectStyleRuntimeModule.js +9 -0
  72. package/lib/css/CssLoadingRuntimeModule.js +92 -15
  73. package/lib/css/CssModule.js +1 -1
  74. package/lib/css/CssModulesPlugin.js +3 -10
  75. package/lib/css/CssParser.js +162 -31
  76. package/lib/css/CssServerStylesRuntimeModule.js +9 -0
  77. package/lib/css/cssMinify.js +43 -32
  78. package/lib/css/data.js +3526 -871
  79. package/lib/css/syntax.js +6455 -1237
  80. package/lib/debug/ProfilingPlugin.js +7 -4
  81. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -3
  82. package/lib/dependencies/AMDRuntimeModules.js +18 -0
  83. package/lib/dependencies/CommonJsExportRequireDependency.js +13 -12
  84. package/lib/dependencies/CommonJsExportsParserPlugin.js +12 -13
  85. package/lib/dependencies/CommonJsImportsParserPlugin.js +5 -5
  86. package/lib/dependencies/CommonJsPlugin.js +19 -3
  87. package/lib/dependencies/CommonJsRequireDependency.js +32 -9
  88. package/lib/dependencies/CommonJsSelfReferenceDependency.js +2 -4
  89. package/lib/dependencies/ContextDependency.js +1 -1
  90. package/lib/dependencies/ContextDependencyHelpers.js +3 -9
  91. package/lib/dependencies/ContextDependencyTemplateAsRequireCall.js +20 -7
  92. package/lib/dependencies/ContextElementDependency.js +1 -1
  93. package/lib/dependencies/CssIcssExportDependency.js +13 -9
  94. package/lib/dependencies/CssIcssImportDependency.js +3 -6
  95. package/lib/dependencies/CssUrlDependency.js +3 -5
  96. package/lib/dependencies/HarmonyCompatibilityDependency.js +67 -22
  97. package/lib/dependencies/HarmonyDetectionParserPlugin.js +3 -4
  98. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +10 -4
  99. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +12 -7
  100. package/lib/dependencies/HarmonyExportExpressionDependency.js +47 -33
  101. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +217 -34
  102. package/lib/dependencies/HarmonyExportInitFragment.js +110 -23
  103. package/lib/dependencies/HarmonyExportSpecifierDependency.js +25 -58
  104. package/lib/dependencies/HarmonyImportBareSideEffectDependency.js +59 -0
  105. package/lib/dependencies/HarmonyImportDependency.js +99 -41
  106. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +198 -25
  107. package/lib/dependencies/HarmonyImportSideEffectDependency.js +117 -4
  108. package/lib/dependencies/HarmonyImportSpecifierDependency.js +40 -3
  109. package/lib/dependencies/HarmonyModulesPlugin.js +20 -0
  110. package/lib/dependencies/HtmlEntryDependency.js +136 -104
  111. package/lib/dependencies/HtmlInlineScriptDependency.js +3 -4
  112. package/lib/dependencies/HtmlInlineStyleDependency.js +3 -4
  113. package/lib/dependencies/ImportContextDependency.js +43 -3
  114. package/lib/dependencies/ImportDependency.js +8 -29
  115. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +1 -1
  116. package/lib/dependencies/ImportMetaGlobHelpers.js +1 -1
  117. package/lib/dependencies/ImportMetaMainDependency.js +104 -0
  118. package/lib/dependencies/ImportMetaPlugin.js +67 -25
  119. package/lib/dependencies/ImportParserPlugin.js +25 -15
  120. package/lib/dependencies/LoaderPlugin.js +26 -1
  121. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  122. package/lib/dependencies/RequireHeaderDependency.js +20 -21
  123. package/lib/dependencies/SystemRuntimeModule.js +9 -0
  124. package/lib/dependencies/URLContextDependency.js +1 -1
  125. package/lib/dependencies/URLDependency.js +6 -5
  126. package/lib/dependencies/WorkerAndWorkletPlugin.js +16 -21
  127. package/lib/dependencies/WorkerDependency.js +12 -18
  128. package/lib/dependencies/WorkletDependency.js +12 -20
  129. package/lib/dependencies/importOptionsCheck.js +47 -0
  130. package/lib/dll/DelegatedModule.js +18 -1
  131. package/lib/dll/DllReferencePlugin.js +11 -8
  132. package/lib/dll/LibManifestPlugin.js +1 -1
  133. package/lib/errors/AnalyzableBailoutsWarning.js +41 -0
  134. package/lib/errors/DotenvFileError.js +29 -0
  135. package/lib/errors/DuplicateModulesWarning.js +12 -7
  136. package/lib/errors/EmptyCopyPatternWarning.js +26 -0
  137. package/lib/errors/ExternalStarReexportError.js +44 -0
  138. package/lib/errors/HookWebpackError.js +3 -2
  139. package/lib/errors/ImportedBindingAssignmentError.js +30 -0
  140. package/lib/errors/MissingSourceMapsWarning.js +36 -0
  141. package/lib/errors/ModuleParseError.js +2 -1
  142. package/lib/errors/UnusedAssetsWarning.js +36 -0
  143. package/lib/errors/UnusedFederationWarning.js +35 -0
  144. package/lib/errors/UnusedModulesWarning.js +38 -0
  145. package/lib/esm/ExportWebpackRequireRuntimeModule.js +9 -0
  146. package/lib/esm/ModuleChunkFormatPlugin.js +28 -3
  147. package/lib/esm/ModuleChunkLoadingPlugin.js +21 -5
  148. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +91 -53
  149. package/lib/hmr/HotModuleReplacement.runtime.js +8 -2
  150. package/lib/hmr/HotModuleReplacementRuntimeModule.js +9 -0
  151. package/lib/hmr/LazyCompilationPlugin.js +3 -5
  152. package/lib/html/HtmlGenerator.js +72 -87
  153. package/lib/html/HtmlModulesPlugin.js +479 -215
  154. package/lib/html/HtmlParser.js +110 -147
  155. package/lib/html/builtinEmbeddedRenderer.js +107 -0
  156. package/lib/html/data.js +142 -7
  157. package/lib/html/htmlMinify.js +92 -122
  158. package/lib/html/syntax.js +2031 -1556
  159. package/lib/ids/HashedModuleIdsPlugin.js +2 -1
  160. package/lib/index.js +49 -3
  161. package/lib/javascript/ChunkFormatHelpers.js +4 -3
  162. package/lib/javascript/JavascriptGenerator.js +8 -1
  163. package/lib/javascript/JavascriptModule.js +1 -0
  164. package/lib/javascript/JavascriptModulesPlugin.js +76 -80
  165. package/lib/javascript/JavascriptParser.js +1970 -900
  166. package/lib/javascript/StartupHelpers.js +27 -3
  167. package/lib/javascript/data.js +172 -0
  168. package/lib/javascript/grammar.js +5546 -0
  169. package/lib/javascript/parser.js +1500 -0
  170. package/lib/javascript/regexp.js +1762 -0
  171. package/lib/javascript/regexpData.js +51 -0
  172. package/lib/javascript/syntax.js +9061 -5262
  173. package/lib/json/JsonGenerator.js +7 -1
  174. package/lib/library/ModuleLibraryPlugin.js +171 -132
  175. package/lib/loaders/LoaderRunner.js +170 -14
  176. package/lib/node/NodeWatchFileSystem.js +3 -9
  177. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +22 -0
  178. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +8 -15
  179. package/lib/node/RequireChunkLoadingRuntimeModule.js +22 -0
  180. package/lib/optimize/AggressiveMergingPlugin.js +3 -5
  181. package/lib/optimize/ConcatenatedModule.js +222 -125
  182. package/lib/optimize/ConstExportsPlugin.js +50 -34
  183. package/lib/optimize/FlagIncludedChunksPlugin.js +3 -11
  184. package/lib/optimize/InlineExports.js +20 -2
  185. package/lib/optimize/InnerGraphPlugin.js +7 -13
  186. package/lib/optimize/LimitChunkCountPlugin.js +5 -8
  187. package/lib/optimize/MangleExportsPlugin.js +13 -4
  188. package/lib/optimize/ModuleConcatenationPlugin.js +27 -5
  189. package/lib/optimize/RealContentHashPlugin.js +179 -44
  190. package/lib/optimize/SideEffectsFlagPlugin.js +24 -28
  191. package/lib/optimize/SplitChunksPlugin.js +2 -4
  192. package/lib/performance/AnalyzableBailoutsPlugin.js +115 -0
  193. package/lib/performance/BroadContextsPlugin.js +1 -1
  194. package/lib/performance/CacheEffectivenessPlugin.js +7 -7
  195. package/lib/performance/DuplicateModulesPlugin.js +57 -13
  196. package/lib/performance/EvalUsagePlugin.js +5 -2
  197. package/lib/performance/MissingSourceMapsPlugin.js +115 -0
  198. package/lib/performance/PureAnnotationsPlugin.js +6 -3
  199. package/lib/performance/SizeLimitsPlugin.js +2 -4
  200. package/lib/performance/TopLevelThisPlugin.js +5 -4
  201. package/lib/performance/UnusedAssetsPlugin.js +109 -0
  202. package/lib/performance/UnusedFederationPlugin.js +147 -0
  203. package/lib/performance/UnusedModulesPlugin.js +141 -0
  204. package/lib/performance/UnusedReexportsPlugin.js +1 -55
  205. package/lib/performance/isOnlyReexported.js +65 -0
  206. package/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js +25 -5
  207. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +8 -8
  208. package/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js +9 -0
  209. package/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js +9 -0
  210. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +9 -0
  211. package/lib/prefetch/ResourceHintPlugin.js +29 -35
  212. package/lib/prefetch/ResourceHintRuntimeModule.js +9 -0
  213. package/lib/prefetch/StartupAssetHintRuntimeModule.js +16 -10
  214. package/lib/rules/RuleSetCompiler.js +211 -4
  215. package/lib/runtime/AsyncModuleRuntimeModule.js +103 -67
  216. package/lib/runtime/AutoPublicPathRuntimeModule.js +9 -0
  217. package/lib/runtime/BaseUriRuntimeModule.js +9 -0
  218. package/lib/runtime/ChunkNameRuntimeModule.js +9 -0
  219. package/lib/runtime/EnsureChunkRuntimeModule.js +35 -16
  220. package/lib/runtime/GetChunkFilenameRuntimeModule.js +12 -4
  221. package/lib/runtime/GetFullHashRuntimeModule.js +9 -0
  222. package/lib/runtime/GetMainFilenameRuntimeModule.js +9 -0
  223. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +9 -0
  224. package/lib/runtime/GlobalRuntimeModule.js +11 -4
  225. package/lib/runtime/HasOwnPropertyRuntimeModule.js +9 -0
  226. package/lib/runtime/HelperRuntimeModule.js +10 -0
  227. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +313 -222
  228. package/lib/runtime/NonceRuntimeModule.js +9 -0
  229. package/lib/runtime/OnChunksLoadedRuntimeModule.js +67 -20
  230. package/lib/runtime/PublicPathRuntimeModule.js +9 -0
  231. package/lib/runtime/RuntimeIdRuntimeModule.js +9 -0
  232. package/lib/runtime/SpecNamespaceObjectRuntimeModule.js +90 -0
  233. package/lib/runtime/StartupChunkDependenciesRuntimeModule.js +9 -0
  234. package/lib/runtime/StartupEntrypointRuntimeModule.js +9 -0
  235. package/lib/runtime/SystemContextRuntimeModule.js +9 -0
  236. package/lib/runtime/ToBinaryRuntimeModule.js +9 -0
  237. package/lib/schemes/DataUriPlugin.js +3 -6
  238. package/lib/schemes/HttpUriPlugin.js +22 -5
  239. package/lib/schemes/VirtualUrlPlugin.js +1 -1
  240. package/lib/serialization/BinaryMiddleware.js +9 -9
  241. package/lib/serialization/FileMiddleware.js +31 -18
  242. package/lib/serialization/ObjectMiddleware.js +168 -139
  243. package/lib/sharing/ConsumeSharedPlugin.js +12 -0
  244. package/lib/sharing/ConsumeSharedRuntimeModule.js +11 -0
  245. package/lib/sharing/ShareRuntimeModule.js +9 -0
  246. package/lib/sharing/declaredShared.js +47 -0
  247. package/lib/stats/DefaultStatsFactoryPlugin.js +6 -5
  248. package/lib/stats/DefaultStatsPrinterPlugin.js +3 -0
  249. package/lib/typescript/TypeScriptPlugin.js +10 -13
  250. package/lib/url/URLParserPlugin.js +6 -8
  251. package/lib/util/ArrayQueue.js +3 -3
  252. package/lib/util/LocConverter.js +4 -7
  253. package/lib/util/SourceProcessor.js +115 -99
  254. package/lib/util/URLAbsoluteSpecifier.js +3 -1
  255. package/lib/util/async.js +292 -0
  256. package/lib/util/buildDiagnostics.js +69 -0
  257. package/lib/util/chainedImports.js +3 -4
  258. package/lib/util/concatenate.js +4 -4
  259. package/lib/util/conventions.js +5 -1
  260. package/lib/util/createHooksRegistry.js +1 -2
  261. package/lib/util/createMappings.js +25 -3
  262. package/lib/util/dataURL.js +2 -3
  263. package/lib/util/deterministicGrouping.js +8 -13
  264. package/lib/util/findGraphRoots.js +6 -13
  265. package/lib/util/fs.js +27 -1
  266. package/lib/util/globUtils.js +1 -1
  267. package/lib/util/handlerKeys.js +40 -0
  268. package/lib/util/hash/wasm-hash.js +3 -4
  269. package/lib/util/identifier.js +20 -0
  270. package/lib/util/internalSerializables.js +7 -1
  271. package/lib/util/registerExternalSerializer.js +6 -45
  272. package/lib/util/removeBOM.js +82 -1
  273. package/lib/util/semver.js +5 -3
  274. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +14 -3
  275. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +14 -3
  276. package/lib/wasm-async/AsyncWebAssemblyGenerator.js +6 -1
  277. package/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +5 -1
  278. package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +7 -12
  279. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +12 -4
  280. package/lib/wasm-sync/WebAssemblyGenerator.js +6 -1
  281. package/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +5 -1
  282. package/lib/wasm-sync/WebAssemblyUtils.js +2 -4
  283. package/lib/web/FetchCompileAsyncWasmPlugin.js +10 -6
  284. package/lib/web/FetchCompileWasmPlugin.js +4 -2
  285. package/lib/web/JsonpChunkLoadingRuntimeModule.js +36 -0
  286. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +19 -0
  287. package/package.json +55 -46
  288. package/schemas/WebpackOptions.check.js +1 -1
  289. package/schemas/WebpackOptions.json +388 -76
  290. package/schemas/plugins/LoaderOptionsPlugin.check.js +1 -1
  291. package/schemas/plugins/ManifestPlugin.check.js +1 -1
  292. package/schemas/plugins/SourceMapDevToolPlugin.check.js +1 -1
  293. package/schemas/plugins/asset/AssetGeneratorOptions.check.js +1 -1
  294. package/schemas/plugins/asset/AssetResourceGeneratorOptions.check.js +1 -1
  295. package/schemas/plugins/container/ContainerPlugin.check.js +1 -1
  296. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  297. package/schemas/plugins/debug/ProfilingPlugin.check.js +1 -1
  298. package/schemas/plugins/dll/DllReferencePlugin.check.js +1 -1
  299. package/schemas/plugins/ids/HashedModuleIdsPlugin.check.js +1 -1
  300. package/schemas/plugins/schemes/HttpUriPlugin.check.js +1 -1
  301. package/types.d.ts +1795 -608
  302. package/lib/dependencies/ExportBindingInitFragment.js +0 -191
  303. package/lib/errors/EntrypointOverlapWarning.js +0 -45
  304. 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");
@@ -31,7 +40,7 @@ const preloadModuleType = require("../util/preloadModuleType");
31
40
  const {
32
41
  PUBLIC_PATH_AUTO: autoPlaceholder
33
42
  } = require("../util/publicPathPlaceholder");
34
- const removeBOM = require("../util/removeBOM");
43
+ const { removeBOMFromResult } = require("../util/removeBOM");
35
44
  const HtmlModule = require("./HtmlModule");
36
45
 
37
46
  const getMimeTypes = memoize(() => require("../util/mimeTypes"));
@@ -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,6 +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;
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
+ };
225
378
 
226
379
  /**
227
380
  * Requests an `output.html` page must load for an entry: its `dependOn`
@@ -238,7 +391,12 @@ const collectHtmlEntryRequests = (name, entries) => {
238
391
  const seenRequests = new Set();
239
392
  /** @type {Set<string>} */
240
393
  const visited = new Set();
241
- const walk = (/** @type {string} */ entryName) => {
394
+ /**
395
+ * Collects an entry's non-html requests, `dependOn` entries included.
396
+ * @param {string} entryName entry to walk
397
+ * @returns {void}
398
+ */
399
+ const walk = (entryName) => {
242
400
  if (visited.has(entryName)) return;
243
401
  visited.add(entryName);
244
402
  const desc = entries[entryName];
@@ -286,7 +444,7 @@ class HtmlModulesPlugin {
286
444
  * digest of `content`, with `nonNumericOnlyHash` applied — webpack's
287
445
  * standard `[contenthash]` recipe.
288
446
  * @param {string | Buffer} content content to hash
289
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
447
+ * @param {OutputOptions} outputOptions output options
290
448
  * @returns {string} content hash
291
449
  */
292
450
  static computeContentHash(content, outputOptions) {
@@ -308,7 +466,7 @@ class HtmlModulesPlugin {
308
466
  * initial chunks, `output.htmlChunkFilename` otherwise — the HTML counterpart
309
467
  * of `CssModulesPlugin.getChunkFilenameTemplate`.
310
468
  * @param {import("../Chunk")} chunk chunk
311
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
469
+ * @param {OutputOptions} outputOptions output options
312
470
  * @returns {import("../Chunk").ChunkFilenameTemplate} used filename template
313
471
  */
314
472
  static getChunkFilenameTemplate(chunk, outputOptions) {
@@ -347,10 +505,9 @@ class HtmlModulesPlugin {
347
505
  // several entries, so those can only be applied per emitted asset.
348
506
  /** @type {Map<string, OutputHtmlOptions>} */
349
507
  const htmlOptionsByEntry = new Map();
350
- // SRI sentinels are emitted while the page's HTML is generated, before the
351
- // owning entry is known, so any page asking for integrity turns them on for
352
- // all of them; pages that don't want SRI drop them again on emit. Authored
353
- // `.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.
354
511
  let anyIntegrity = isIntegrityEnabled(
355
512
  globalHtmlOptions && globalHtmlOptions.integrity
356
513
  );
@@ -383,13 +540,9 @@ class HtmlModulesPlugin {
383
540
  return options;
384
541
  };
385
542
 
386
- // `output.html` (or an entry's `html`) wraps a non-HTML entry in a
387
- // synthetic HTML module so the existing pipeline injects its JS/CSS
388
- // chunks and applies the `template` option. `dependOn` ancestors are
389
- // injected first so the page loads the shared/runtime chunks before
390
- // the entry's own (see `collectHtmlEntryRequests`). `crossOriginLoading`
391
- // and SRI are applied to the injected tags centrally in
392
- // 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.
393
546
  EntryOptionPlugin.getHooks(compiler).entry.tap(
394
547
  PLUGIN_NAME,
395
548
  (context, name, desc) => {
@@ -464,24 +617,20 @@ class HtmlModulesPlugin {
464
617
  /** @type {Map<string, { content: string, source: import("webpack-sources").RawSource }>} */
465
618
  const sentinelResolvedSourceCache = new Map();
466
619
 
467
- // `<script src>` and `<link rel="modulepreload">` references collected
468
- // by HtmlParser become real compilation entries here. The `script`
469
- // and `script-module` groups are chained via a leader-only dependOn so
470
- // they share a runtime — the first entry of the group owns it and
471
- // every subsequent entry sets `dependOn: [leader]`. Modulepreload
472
- // entries are emitted as independent entries (no dependOn) so they
473
- // can never be imported as a runtime leader by a later script —
474
- // that's what keeps the "preload but don't execute" contract of
475
- // `<link rel="modulepreload">` intact.
476
- // Per-compilation state: the HTML modules seen during make (so
477
- // `finishMake` creates their entries without scanning the whole module
478
- // graph), the stylesheet entry names (read in `afterChunks`) and the html
479
- // options of every emitted page, keyed by its asset name.
480
- /** @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> }>} */
481
630
  const compilationState = new WeakMap();
482
631
  /**
483
632
  * @param {import("../Compilation")} compilation compilation
484
- * @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
485
634
  */
486
635
  const getState = (compilation) => {
487
636
  let state = compilationState.get(compilation);
@@ -489,6 +638,7 @@ class HtmlModulesPlugin {
489
638
  state = {
490
639
  htmlModules: new Set(),
491
640
  stylesheetEntries: new Set(),
641
+ pendingNames: new Map(),
492
642
  htmlAssetOptions: new Map()
493
643
  };
494
644
  compilationState.set(compilation, state);
@@ -496,14 +646,19 @@ class HtmlModulesPlugin {
496
646
  return state;
497
647
  };
498
648
  compiler.hooks.finishMake.tapAsync(PLUGIN_NAME, (compilation, callback) => {
499
- const { htmlModules, stylesheetEntries } = getState(compilation);
500
-
501
- // Collect the entries an HTML module asks for. Only the script chains
502
- // (`script`, `script-module`) share a runtime via a leader-only
503
- // `dependOn`; `modulepreload`, `stylesheet` and `html` links are all
504
- // independent entries (a CSS or page entry must not chain into a JS
505
- // leader, or the chunk would mix unrelated outputs).
506
- /** @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 }[]} */
507
662
  const collectEntrySpecs = (module) => {
508
663
  const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
509
664
  module.buildInfo
@@ -525,10 +680,11 @@ class HtmlModulesPlugin {
525
680
  leaderName = entry.entryName;
526
681
  }
527
682
  // Stylesheet entries and `as="style"` preload/prefetch entries emit
528
- // a CSS chunk, so they need the CSS filename template below.
529
- if (groupKind === "stylesheet" || entry.css) {
683
+ // a CSS chunk, so they are named from `output.cssFilename` alone.
684
+ if (groupKind === "stylesheet" || Boolean(entry.css)) {
530
685
  stylesheetEntries.add(entry.entryName);
531
686
  }
687
+ extractedNames.add(entry.entryName);
532
688
  specs.push({
533
689
  context,
534
690
  request: entry.request,
@@ -540,9 +696,70 @@ class HtmlModulesPlugin {
540
696
  return specs;
541
697
  };
542
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
+
543
760
  // Push one collected reference as a compilation entry; resolves with
544
761
  // the built entry module.
545
- /** @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>} */
546
763
  const addEntry = (spec) =>
547
764
  new Promise((resolve, reject) => {
548
765
  compilation.addEntry(
@@ -550,10 +767,8 @@ class HtmlModulesPlugin {
550
767
  EntryPlugin.createDependency(spec.request, { name: spec.name }),
551
768
  {
552
769
  name: spec.name,
553
- // Each entry gets its own filename from the synthetic entry name so
554
- // it doesn't collide with `output.filename`. CSS entries set their
555
- // `.css` name via `cssFilenameTemplate` below; `html` page entries
556
- // 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.
557
772
  filename: compilation.outputOptions.chunkFilename || "[name].js",
558
773
  dependOn: spec.dependOn
559
774
  },
@@ -567,7 +782,7 @@ class HtmlModulesPlugin {
567
782
  // every HTML entry already gets. `processed` guards diamonds and cycles.
568
783
  /** @type {WeakSet<import("../Module")>} */
569
784
  const processed = new WeakSet();
570
- /** @type {(module: import("../Module")) => Promise<void>} */
785
+ /** @type {(module: Module) => Promise<void>} */
571
786
  const processEntry = async (module) => {
572
787
  if (processed.has(module)) return;
573
788
  processed.add(module);
@@ -575,27 +790,30 @@ class HtmlModulesPlugin {
575
790
  collectEntrySpecs(module).map(async (spec) => {
576
791
  const entryModule = await addEntry(spec);
577
792
  if (entryModule && entryModule.type === HTML_MODULE_TYPE) {
793
+ linkedPages.set(spec.name, entryModule);
578
794
  await processEntry(entryModule);
579
795
  }
580
796
  })
581
797
  );
582
798
  };
583
- // Seed with the HTML modules seen during make (config entries /
584
- // imports, fresh or cache-restored); linked pages are reached by
585
- // recursion above.
586
- Promise.all([...htmlModules].map(processEntry)).then(
587
- () => callback(),
588
- callback
589
- );
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);
590
809
  });
591
810
 
592
811
  // `csp` injects a `<meta http-equiv="Content-Security-Policy">` and
593
- // `integrity` resolves the SRI sentinels — both once the page's inline
594
- // content is final (in `processAssets` below), per page, so an entry's
595
- // `html` can override them.
596
- // `"auto"` marks implicit enablement (no user rule for HTML files) — see
597
- // `applyExperimentsDefaults`. Only then loaders win over the built-in type
598
- // (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`.
599
817
  // TODO webpack 6: html defaults to `true`, drop this implicit-only fallback.
600
818
  const implicitlyEnabled = compiler.options.experiments.html === "auto";
601
819
  compiler.hooks.compilation.tap(
@@ -610,12 +828,13 @@ class HtmlModulesPlugin {
610
828
  );
611
829
  }
612
830
  const { htmlModules } = getState(compilation);
613
- // Record HTML modules as they appear — freshly built
614
- // (`succeedModule`) or restored from cache (`stillValidModule`) —
615
- // so `finishMake` creates their entries without scanning every
616
- // module in the graph. Tracking only records; entries are still
617
- // added (and awaited) in `finishMake`.
618
- const trackHtmlModule = (/** @type {import("../Module")} */ module) => {
831
+ /**
832
+ * Records HTML modules as they are built or restored, so `finishMake`
833
+ * creates their entries without scanning the whole module graph.
834
+ * @param {import("../Module")} module built or restored module
835
+ * @returns {void}
836
+ */
837
+ const trackHtmlModule = (module) => {
619
838
  if (module.type !== HTML_MODULE_TYPE) return;
620
839
  // enabled here, not on the compilation hook: an integrity build
621
840
  // with no html module must not load the generator
@@ -627,13 +846,12 @@ class HtmlModulesPlugin {
627
846
  compilation.hooks.succeedModule.tap(PLUGIN_NAME, trackHtmlModule);
628
847
  compilation.hooks.stillValidModule.tap(PLUGIN_NAME, trackHtmlModule);
629
848
 
630
- // Resolve integrity and inline sentinels after `RealContentHashPlugin`
631
- // so final bytes are in place. Only HTML pages carry these sentinels —
632
- // never JS chunks that embed an HTML string.
633
- // SRI only takes effect on a cross-origin subresource fetched
634
- // with CORS; without `output.crossOriginLoading` the browser
635
- // silently ignores `integrity` on cross-origin loads. Warn once
636
- // 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.
637
855
  if (anyIntegrity && !compilation.outputOptions.crossOriginLoading) {
638
856
  compilation.warnings.push(
639
857
  new WebpackError(
@@ -679,12 +897,9 @@ class HtmlModulesPlugin {
679
897
  inlinedFiles
680
898
  );
681
899
  }
682
- // `injectTags` (add tags), `transformTags` (mutate/move/remove the
683
- // page's own and injected tags), and CSP all run over a single
684
- // parse: collect the tags/anchors once, let the hooks act, then
685
- // render every edit — placement, attribute rewrites, moves,
686
- // removals, CSP hashes/nonce/meta — in one pass. Injected inline
687
- // `<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.
688
903
  const injected = await hooks.injectTags.promise([], {
689
904
  outputName: name,
690
905
  html: resolved
@@ -714,73 +929,114 @@ class HtmlModulesPlugin {
714
929
  // An inlined chunk file is dead weight once nothing else references
715
930
  // it by URL (another page's tag, or the runtime's async chunk map);
716
931
  // a content-hashed filename makes a substring hit a real reference.
932
+ /** @type {Set<string>} */
933
+ const deletionCandidates = new Set(inlinedFiles);
717
934
  // Materialize each other asset's source once (it can be expensive to
718
- // render) and test all inlined files against it, rather than
719
- // re-materializing every asset per inlined file.
720
- if (inlinedFiles.size > 0) {
935
+ // render) and test all candidates against it, rather than
936
+ // re-materializing every asset per candidate.
937
+ if (deletionCandidates.size > 0) {
721
938
  /** @type {Set<string>} */
722
939
  const stillReferenced = new Set();
723
- for (const name of Object.keys(compilation.assets)) {
724
- if (
725
- inlinedFiles.has(name) ||
726
- stillReferenced.size === inlinedFiles.size
727
- ) {
728
- continue;
729
- }
940
+ /** @type {string[]} */
941
+ const pending = [];
942
+ /**
943
+ * @param {string} name asset whose source may name a candidate
944
+ */
945
+ const markReferenced = (name) => {
730
946
  const source = compilation.assets[name].source();
731
- if (typeof source !== "string") continue;
732
- for (const file of inlinedFiles) {
947
+ if (typeof source !== "string") return;
948
+ for (const file of deletionCandidates) {
733
949
  if (!stillReferenced.has(file) && source.includes(file)) {
734
950
  stillReferenced.add(file);
951
+ pending.push(file);
735
952
  }
736
953
  }
954
+ };
955
+ for (const name of Object.keys(compilation.assets)) {
956
+ if (
957
+ deletionCandidates.has(name) ||
958
+ stillReferenced.size === deletionCandidates.size
959
+ ) {
960
+ continue;
961
+ }
962
+ markReferenced(name);
737
963
  }
738
- for (const file of inlinedFiles) {
964
+ // A kept candidate can reference another through its async chunk
965
+ // map, so follow each until nothing new is kept.
966
+ while (pending.length > 0) {
967
+ markReferenced(/** @type {string} */ (pending.pop()));
968
+ }
969
+ for (const file of deletionCandidates) {
739
970
  if (!stillReferenced.has(file)) compilation.deleteAsset(file);
740
971
  }
741
972
  }
742
973
  }
743
974
  );
744
975
 
745
- // CSS entries created by `<link rel="stylesheet">` in HTML need
746
- // their `.css` filename set via `chunk.cssFilenameTemplate`
747
- // (the field `CssModulesPlugin.getChunkFilenameTemplate` reads).
748
- // Compilation only flows `options.filename` to `chunk.filenameTemplate`,
749
- // which controls JS emit — there's no entry-level `cssFilename`.
750
- // Set it ourselves after chunks are created so each stylesheet
751
- // entry emits to a distinct file derived from `output.cssFilename`
752
- // (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.
753
979
  compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => {
754
- const { stylesheetEntries } = getState(compilation);
755
- if (stylesheetEntries.size === 0) return;
756
- 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) {
757
1002
  const entrypoint = compilation.entrypoints.get(entryName);
758
1003
  if (!entrypoint) continue;
759
1004
  const chunk = entrypoint.getEntrypointChunk();
760
1005
  if (!chunk) continue;
761
- // Each html-derived stylesheet entry uses the
762
- // `cssChunkFilename` template — even though the entry
763
- // chunk technically `canBeInitial()`, we deliberately
764
- // avoid `cssFilename` here because that template often
765
- // has no per-entry placeholder (it's derived from
766
- // `output.filename`, which can be a literal like
767
- // `bundle0.js`), and multiple `<link rel="stylesheet">`
768
- // tags would then collide on the same emitted `.css`
769
- // file. `cssChunkFilename` is derived from
770
- // `output.chunkFilename` which webpack auto-extends
771
- // with `[id].` when needed, guaranteeing uniqueness.
772
- chunk.cssFilenameTemplate =
773
- 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
+ );
774
1031
  }
775
1032
  });
776
1033
  compilation.dependencyTemplates.set(
777
1034
  StaticExportsDependency,
778
1035
  new StaticExportsDependency.Template()
779
1036
  );
780
- // `ConstDependency` is used by HtmlParser to insert
781
- // ` type="module"` into the rewritten <script> tag when
782
- // `output.module` is on. Register its template so the HTML
783
- // 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.
784
1040
  compilation.dependencyTemplates.set(
785
1041
  ConstDependency,
786
1042
  new ConstDependency.Template()
@@ -812,10 +1068,9 @@ class HtmlModulesPlugin {
812
1068
  HtmlEntryDependency,
813
1069
  new HtmlEntryDependency.Template()
814
1070
  );
815
- // Inline `<script>` content is bundled as its own entry — the
816
- // same pipeline that handles `<script src>` — via a
817
- // `data:text/javascript,...` request. The dependency
818
- // 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.
819
1074
  compilation.dependencyFactories.set(
820
1075
  HtmlInlineScriptDependency,
821
1076
  normalModuleFactory
@@ -824,11 +1079,9 @@ class HtmlModulesPlugin {
824
1079
  HtmlInlineScriptDependency,
825
1080
  new HtmlInlineScriptDependency.Template()
826
1081
  );
827
- // Inline `<style>` content is routed through the CSS pipeline
828
- // as a `data:text/css` module. The dependency template reads
829
- // the processed CSS text from the CSS module's code
830
- // generation data (`css-text` channel set by CssGenerator
831
- // 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.
832
1085
  compilation.dependencyFactories.set(
833
1086
  HtmlInlineStyleDependency,
834
1087
  normalModuleFactory
@@ -888,7 +1141,8 @@ class HtmlModulesPlugin {
888
1141
  );
889
1142
  return new (getHtmlGenerator())(
890
1143
  generatorOptions,
891
- compilation.moduleGraph
1144
+ compilation.moduleGraph,
1145
+ (module) => getPageOnlyEntry(compilation, module) !== undefined
892
1146
  );
893
1147
  });
894
1148
 
@@ -896,61 +1150,45 @@ class HtmlModulesPlugin {
896
1150
  PLUGIN_NAME,
897
1151
  (result, module) => {
898
1152
  if (module.type === HTML_MODULE_TYPE) {
899
- const [source, ...rest] = result;
900
- // `applyTemplate` is a no-op unless `module.parser.html.template`
901
- // is set. Running it here (where the returned source becomes the
902
- // module's stored source) keeps the parser's dependency offsets
903
- // and the generator's render base in sync.
1153
+ const [source, ...rest] = removeBOMFromResult(result);
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.
904
1157
  const parser = /** @type {HtmlParser} */ (module.parser);
905
1158
 
906
- return [parser.applyTemplate(removeBOM(source), module), ...rest];
1159
+ return [parser.applyTemplate(source, module), ...rest];
907
1160
  }
908
1161
 
909
1162
  return result;
910
1163
  }
911
1164
  );
912
1165
 
913
- // Emit extracted `.html` files for any HTML module that opted
914
- // into extraction. The opt-in is computed by
915
- // `HtmlGenerator#_shouldExtract`: `module.generator.html.extract:
916
- // true` always extracts, `false` never extracts, and when
917
- // `extract` is unset the generator extracts iff the HTML module
918
- // is a compilation entry — the iteration below picks up only
919
- // modules whose generator reported the `html` source type, so
920
- // that decision is honored implicitly. The HTML content is read
921
- // from the generator's secondary `"html"` source type (see
922
- // HtmlGenerator#generate). The filename template comes from
923
- // `output.htmlFilename` (initial chunks) or
924
- // `output.htmlChunkFilename` (non-initial chunks), mirroring
925
- // the CSS pipeline. Path data follows the asset-module pattern —
926
- // `module` + a relative source `filename`, with `chunk`
927
- // intentionally omitted so `[name]` resolves to the HTML
928
- // source's basename (e.g. `page` for `./page.html`) rather
929
- // than the importing chunk's name (e.g. `main`). A per-module
930
- // content hash is computed from the rewritten HTML so the
931
- // template's `[contenthash]` placeholder works; the
932
- // compilation hash is also forwarded so `[fullhash]` /
933
- // `[hash]` work in user-supplied templates.
934
-
935
- // Sentinel-resolved content and its content hash depend only on
936
- // the HTML-type module source — not on the chunk or output
937
- // filename — yet `renderManifest` runs per `(chunk, module)`.
938
- // Memoize so a module landing in multiple chunks resolves and
939
- // hashes its sentinels once. Scoped to this compilation because
940
- // sentinel resolution embeds chunk filenames, which change
941
- // 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.
942
1180
  /** @type {WeakMap<import("webpack-sources").Source, { resolvedContent: string, contentHash: string }>} */
943
1181
  const resolvedSentinelHashCache = new WeakMap();
944
1182
 
945
- // Compute a linked/entry HTML page's emitted filename + chunk-URL-resolved
946
- // content + content hash for one (module, chunk). Shared by the emit loop
947
- // below and the page-link resolver. The content hash covers only the
948
- // chunk-URL-resolved source — links to *other* pages stay sentinels here,
949
- // so a page's filename doesn't depend on the filenames of pages it links to.
950
- const computePageEmit = (
951
- /** @type {NormalModule} */ module,
952
- /** @type {import("../Chunk")} */ chunk
953
- ) => {
1183
+ /**
1184
+ * Computes an HTML page's emitted filename, chunk-URL-resolved content and
1185
+ * content hash. Links to other pages stay sentinels, so a page's filename
1186
+ * never depends on the filenames of the pages it links to.
1187
+ * @param {NormalModule} module the page's html module
1188
+ * @param {import("../Chunk")} chunk chunk the page is emitted from
1189
+ * @returns {{ resolvedContent: string, contentHash: string, filename: string, info: import("../Compilation").AssetInfo } | undefined} the page's emit, or `undefined` when it generates no html
1190
+ */
1191
+ const computePageEmit = (module, chunk) => {
954
1192
  const { chunkGraph, outputOptions } = compilation;
955
1193
  const codeGenResult =
956
1194
  /** @type {import("../CodeGenerationResults")} */ (
@@ -960,15 +1198,12 @@ class HtmlModulesPlugin {
960
1198
  if (!placeholderSource) return undefined;
961
1199
  let cached = resolvedSentinelHashCache.get(placeholderSource);
962
1200
  if (cached === undefined) {
963
- // Resolve chunk-URL sentinels *before* hashing so the HTML's
964
- // `[contenthash]` invalidates with the referenced chunks'
965
- // filenames. Inlined chunks have no URL, so tag their inline
966
- // sentinels with the chunk content hash — that keeps the emitted
967
- // bytes (and RealContentHashPlugin's later recompute)
968
- // content-dependent, so the page hash tracks inlined content too.
969
- // Asset URL sentinels (from `HtmlEntryDependency` resource-hint
970
- // tags) resolve here too — deferred from template.apply so it
971
- // 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.
972
1207
  const resolvedContent = getHtmlGenerator().resolveAssetUrlSentinels(
973
1208
  getHtmlGenerator().embedInlineChunkHashes(
974
1209
  getHtmlGenerator().resolveChunkUrlSentinels(
@@ -1027,14 +1262,15 @@ class HtmlModulesPlugin {
1027
1262
  };
1028
1263
  };
1029
1264
 
1030
- // Emitted filename of a linked `type: "html"` page, keyed by its entry
1031
- // name. Looked up from the page's own entry chunk so it's independent of
1032
- // chunk render order; cached for the compilation.
1033
1265
  /** @type {Map<string, string>} */
1034
1266
  const htmlPageFilenameCache = new Map();
1035
- const computePageFilenameByEntry = (
1036
- /** @type {string} */ entryName
1037
- ) => {
1267
+ /**
1268
+ * Emitted filename of a linked `type: "html"` page, read from the page's
1269
+ * own entry chunk so chunk render order cannot affect it.
1270
+ * @param {string} entryName entry name of the linked page
1271
+ * @returns {string} the page's emitted filename, `"data:,"` when it has none
1272
+ */
1273
+ const computePageFilenameByEntry = (entryName) => {
1038
1274
  const cached = htmlPageFilenameCache.get(entryName);
1039
1275
  if (cached !== undefined) return cached;
1040
1276
  let filename = "data:,";
@@ -1063,9 +1299,8 @@ class HtmlModulesPlugin {
1063
1299
  compilation.hooks.renderManifest.tap(
1064
1300
  PLUGIN_NAME,
1065
1301
  (result, { chunk }) => {
1066
- // HMR's `HotUpdateChunk`s flow through the same hook
1067
- // but aren't real output chunks — extracting `.html`
1068
- // 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.
1069
1304
  // `CssModulesPlugin` early-returns for the same reason.
1070
1305
  if (chunk instanceof HotUpdateChunk) return result;
1071
1306
  const { chunkGraph } = compilation;
@@ -1079,12 +1314,13 @@ class HtmlModulesPlugin {
1079
1314
  const outputOptions = compilation.outputOptions;
1080
1315
  for (const module of modules) {
1081
1316
  const normalModule = /** @type {NormalModule} */ (module);
1082
- // `<iframe srcdoc>` modules expose `html` only so HtmlInlineHtmlDependency
1083
- // can write the processed markup back into the host attribute — they are
1084
- // never standalone pages. `generator.extract: "inline"` marks them so no
1085
- // `.html` file is emitted; without this they'd collide on the
1086
- // `data:text/html` → chunk-name path (every srcdoc module in a chunk would
1087
- // 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.
1088
1324
  const generatorOptions = normalModule.generatorOptions;
1089
1325
  if (generatorOptions && generatorOptions.extract === "inline") {
1090
1326
  continue;
@@ -1094,11 +1330,9 @@ class HtmlModulesPlugin {
1094
1330
  if (!emit) continue;
1095
1331
  const { resolvedContent, contentHash, filename, info } = emit;
1096
1332
 
1097
- // Resolve any remaining `[webpack/auto]` placeholders to an undo
1098
- // path computed from the emitted HTML's location, so asset/chunk
1099
- // URLs stay relative to `output.path` even when the page emits into
1100
- // a subdirectory. A relative `<base href>` prepends `../`s so the
1101
- // 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.
1102
1336
  const basePrefix =
1103
1337
  /** @type {HtmlModuleBuildInfo} */ (normalModule.buildInfo)
1104
1338
  .baseUrlPrefix || "";
@@ -1120,12 +1354,9 @@ class HtmlModulesPlugin {
1120
1354
  .split(autoPlaceholder)
1121
1355
  .join(undoPath);
1122
1356
  const finalSource = new RawSource(finalContent);
1123
- // The same HTML module can land in multiple chunks with different
1124
- // `htmlFilename`/`htmlChunkFilename` shapes → different `undoPath`s and
1125
- // final content for the same module id. Include the emitted filename in
1126
- // the asset cache key and the post-undo-path content in the hash so the
1127
- // asset cache can't reuse one variant's bytes at another variant's URL.
1128
- // 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.
1129
1360
  const finalContentHash =
1130
1361
  finalContent === resolvedContent
1131
1362
  ? contentHash
@@ -1165,6 +1396,16 @@ class HtmlModulesPlugin {
1165
1396
  jsHooks.render.tap(PLUGIN_NAME, (source, renderContext) => {
1166
1397
  // No HTML modules ⇒ no sentinels; skip materializing the JS source.
1167
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
+ }
1168
1409
  const raw = source.source();
1169
1410
  if (typeof raw !== "string") return source;
1170
1411
  // Every sentinel shares this prefix — one scan answers the common
@@ -1235,6 +1476,29 @@ class HtmlModulesPlugin {
1235
1476
  }
1236
1477
  );
1237
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
+ }
1238
1502
  }
1239
1503
 
1240
1504
  /**