webpack 5.110.3 → 5.111.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 (208) hide show
  1. package/README.md +5 -0
  2. package/lib/APIPlugin.js +23 -7
  3. package/lib/AutomaticPrefetchPlugin.js +1 -1
  4. package/lib/Cache.js +8 -3
  5. package/lib/CacheFacade.js +1 -1
  6. package/lib/CircularModulesPlugin.js +157 -55
  7. package/lib/CleanPlugin.js +2 -2
  8. package/lib/Compilation.js +320 -302
  9. package/lib/Compiler.js +209 -46
  10. package/lib/ContextModule.js +17 -81
  11. package/lib/ContextModuleFactory.js +1 -1
  12. package/lib/CopyPlugin.js +1054 -0
  13. package/lib/DefinePlugin.js +24 -8
  14. package/lib/ExternalModule.js +41 -14
  15. package/lib/FileSystemInfo.js +17 -14
  16. package/lib/FlagDependencyExportsPlugin.js +45 -21
  17. package/lib/HotModuleReplacementPlugin.js +53 -7
  18. package/lib/JavascriptMetaInfoPlugin.js +5 -3
  19. package/lib/MainTemplate.js +5 -1
  20. package/lib/Module.js +16 -7
  21. package/lib/MultiCompiler.js +1 -1
  22. package/lib/MultiWatching.js +1 -1
  23. package/lib/NormalModule.js +45 -12
  24. package/lib/NormalModuleFactory.js +1 -1
  25. package/lib/RuntimeGlobals.js +21 -7
  26. package/lib/RuntimeModule.js +28 -12
  27. package/lib/RuntimePlugin.js +30 -3
  28. package/lib/RuntimeTemplate.js +780 -475
  29. package/lib/SourceMapDevToolPlugin.js +1 -1
  30. package/lib/Watching.js +9 -0
  31. package/lib/WebpackOptionsApply.js +84 -91
  32. package/lib/asset/AssetGenerator.js +25 -8
  33. package/lib/asset/AssetModule.js +3 -0
  34. package/lib/asset/WebManifestParser.js +7 -5
  35. package/lib/cache/AddBuildDependenciesPlugin.js +33 -4
  36. package/lib/cache/IdleFileCachePlugin.js +2 -2
  37. package/lib/cache/PackFileCacheStrategy.js +14 -1
  38. package/lib/config/browserslistTargetHandler.js +19 -0
  39. package/lib/config/defaults.js +86 -50
  40. package/lib/config/normalization.js +82 -12
  41. package/lib/config/target.js +10 -0
  42. package/lib/container/ContainerReferencePlugin.js +6 -0
  43. package/lib/container/RemoteRuntimeModule.js +9 -0
  44. package/lib/container/declaredRemotes.js +39 -0
  45. package/lib/css/CssGenerator.js +8 -0
  46. package/lib/css/CssInjectStyleRuntimeModule.js +9 -0
  47. package/lib/css/CssLoadingRuntimeModule.js +94 -36
  48. package/lib/css/CssModule.js +1 -1
  49. package/lib/css/CssModulesPlugin.js +7 -6
  50. package/lib/css/CssParser.js +144 -11
  51. package/lib/css/CssServerStylesRuntimeModule.js +9 -0
  52. package/lib/css/cssMinify.js +11 -3
  53. package/lib/css/data.js +2814 -678
  54. package/lib/css/syntax.js +4082 -248
  55. package/lib/debug/ProfilingPlugin.js +4 -0
  56. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -3
  57. package/lib/dependencies/AMDRuntimeModules.js +18 -0
  58. package/lib/dependencies/CommonJsExportRequireDependency.js +3 -1
  59. package/lib/dependencies/CommonJsExportsParserPlugin.js +12 -13
  60. package/lib/dependencies/CommonJsImportsParserPlugin.js +1 -0
  61. package/lib/dependencies/CommonJsPlugin.js +19 -3
  62. package/lib/dependencies/CommonJsRequireDependency.js +14 -5
  63. package/lib/dependencies/CssIcssExportDependency.js +7 -1
  64. package/lib/dependencies/HarmonyCompatibilityDependency.js +58 -4
  65. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +8 -3
  66. package/lib/dependencies/HarmonyExportExpressionDependency.js +44 -29
  67. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +71 -13
  68. package/lib/dependencies/HarmonyExportInitFragment.js +99 -19
  69. package/lib/dependencies/HarmonyExportSpecifierDependency.js +18 -30
  70. package/lib/dependencies/HarmonyImportBareSideEffectDependency.js +59 -0
  71. package/lib/dependencies/HarmonyImportDependency.js +6 -1
  72. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +8 -1
  73. package/lib/dependencies/HarmonyImportSpecifierDependency.js +24 -1
  74. package/lib/dependencies/HarmonyModulesPlugin.js +20 -0
  75. package/lib/dependencies/HtmlEntryDependency.js +50 -0
  76. package/lib/dependencies/ImportMetaMainDependency.js +104 -0
  77. package/lib/dependencies/ImportMetaPlugin.js +59 -15
  78. package/lib/dependencies/SystemRuntimeModule.js +9 -0
  79. package/lib/dependencies/WorkerDependency.js +1 -2
  80. package/lib/dependencies/WorkletDependency.js +1 -2
  81. package/lib/dll/DelegatedModule.js +18 -1
  82. package/lib/dll/DllReferencePlugin.js +11 -8
  83. package/lib/dll/LibManifestPlugin.js +1 -1
  84. package/lib/errors/AnalyzableBailoutsWarning.js +41 -0
  85. package/lib/errors/DuplicateModulesWarning.js +12 -7
  86. package/lib/errors/EmptyCopyPatternWarning.js +26 -0
  87. package/lib/errors/ExternalStarReexportError.js +44 -0
  88. package/lib/errors/MissingSourceMapsWarning.js +36 -0
  89. package/lib/errors/ModuleParseError.js +2 -1
  90. package/lib/errors/UnusedAssetsWarning.js +36 -0
  91. package/lib/errors/UnusedFederationWarning.js +35 -0
  92. package/lib/errors/UnusedModulesWarning.js +38 -0
  93. package/lib/esm/ExportWebpackRequireRuntimeModule.js +9 -0
  94. package/lib/esm/ModuleChunkFormatPlugin.js +28 -3
  95. package/lib/esm/ModuleChunkLoadingPlugin.js +30 -11
  96. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +92 -60
  97. package/lib/hmr/HotModuleReplacement.runtime.js +8 -2
  98. package/lib/hmr/HotModuleReplacementRuntimeModule.js +9 -0
  99. package/lib/html/HtmlGenerator.js +11 -5
  100. package/lib/html/HtmlModulesPlugin.js +331 -62
  101. package/lib/html/HtmlParser.js +13 -6
  102. package/lib/html/builtinEmbeddedRenderer.js +107 -0
  103. package/lib/html/data.js +121 -7
  104. package/lib/html/htmlMinify.js +84 -113
  105. package/lib/html/syntax.js +763 -426
  106. package/lib/ids/HashedModuleIdsPlugin.js +2 -1
  107. package/lib/index.js +3 -0
  108. package/lib/javascript/JavascriptModule.js +1 -0
  109. package/lib/javascript/JavascriptModulesPlugin.js +52 -24
  110. package/lib/javascript/JavascriptParser.js +1255 -728
  111. package/lib/javascript/StartupHelpers.js +27 -3
  112. package/lib/javascript/data.js +172 -0
  113. package/lib/javascript/grammar.js +5546 -0
  114. package/lib/javascript/parser.js +1494 -0
  115. package/lib/javascript/regexp.js +1762 -0
  116. package/lib/javascript/regexpData.js +51 -0
  117. package/lib/javascript/syntax.js +8696 -5225
  118. package/lib/library/ModuleLibraryPlugin.js +116 -5
  119. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +22 -0
  120. package/lib/node/RequireChunkLoadingRuntimeModule.js +22 -0
  121. package/lib/optimize/ConcatenatedModule.js +97 -20
  122. package/lib/optimize/ConstExportsPlugin.js +50 -39
  123. package/lib/optimize/InlineExports.js +20 -123
  124. package/lib/optimize/MangleExportsPlugin.js +10 -0
  125. package/lib/optimize/ModuleConcatenationPlugin.js +24 -1
  126. package/lib/optimize/RealContentHashPlugin.js +68 -9
  127. package/lib/optimize/SideEffectsFlagPlugin.js +17 -9
  128. package/lib/performance/AnalyzableBailoutsPlugin.js +115 -0
  129. package/lib/performance/CacheEffectivenessPlugin.js +4 -3
  130. package/lib/performance/DuplicateModulesPlugin.js +57 -13
  131. package/lib/performance/EvalUsagePlugin.js +5 -2
  132. package/lib/performance/MissingSourceMapsPlugin.js +115 -0
  133. package/lib/performance/PureAnnotationsPlugin.js +6 -3
  134. package/lib/performance/TopLevelThisPlugin.js +5 -4
  135. package/lib/performance/UnusedAssetsPlugin.js +109 -0
  136. package/lib/performance/UnusedFederationPlugin.js +147 -0
  137. package/lib/performance/UnusedModulesPlugin.js +141 -0
  138. package/lib/performance/UnusedReexportsPlugin.js +1 -55
  139. package/lib/performance/isOnlyReexported.js +65 -0
  140. package/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js +25 -5
  141. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +2 -0
  142. package/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js +9 -0
  143. package/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js +9 -0
  144. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +9 -0
  145. package/lib/prefetch/ResourceHintPlugin.js +1 -2
  146. package/lib/prefetch/ResourceHintRuntimeModule.js +9 -0
  147. package/lib/prefetch/StartupAssetHintRuntimeModule.js +9 -0
  148. package/lib/rules/RuleSetCompiler.js +211 -4
  149. package/lib/runtime/AsyncModuleRuntimeModule.js +100 -66
  150. package/lib/runtime/AutoPublicPathRuntimeModule.js +9 -0
  151. package/lib/runtime/BaseUriRuntimeModule.js +9 -0
  152. package/lib/runtime/ChunkNameRuntimeModule.js +9 -0
  153. package/lib/runtime/EnsureChunkRuntimeModule.js +35 -16
  154. package/lib/runtime/GetChunkFilenameRuntimeModule.js +9 -0
  155. package/lib/runtime/GetFullHashRuntimeModule.js +9 -0
  156. package/lib/runtime/GetMainFilenameRuntimeModule.js +9 -0
  157. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +9 -0
  158. package/lib/runtime/GlobalRuntimeModule.js +9 -0
  159. package/lib/runtime/HasOwnPropertyRuntimeModule.js +9 -0
  160. package/lib/runtime/HelperRuntimeModule.js +10 -0
  161. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +330 -219
  162. package/lib/runtime/NonceRuntimeModule.js +9 -0
  163. package/lib/runtime/OnChunksLoadedRuntimeModule.js +67 -20
  164. package/lib/runtime/PublicPathRuntimeModule.js +9 -0
  165. package/lib/runtime/RuntimeIdRuntimeModule.js +9 -0
  166. package/lib/runtime/SpecNamespaceObjectRuntimeModule.js +90 -0
  167. package/lib/runtime/StartupChunkDependenciesRuntimeModule.js +9 -0
  168. package/lib/runtime/StartupEntrypointRuntimeModule.js +9 -0
  169. package/lib/runtime/SystemContextRuntimeModule.js +9 -0
  170. package/lib/runtime/ToBinaryRuntimeModule.js +9 -0
  171. package/lib/serialization/ObjectMiddleware.js +138 -106
  172. package/lib/sharing/ConsumeSharedPlugin.js +12 -0
  173. package/lib/sharing/ConsumeSharedRuntimeModule.js +11 -0
  174. package/lib/sharing/ShareRuntimeModule.js +9 -0
  175. package/lib/sharing/declaredShared.js +47 -0
  176. package/lib/stats/DefaultStatsFactoryPlugin.js +3 -1
  177. package/lib/stats/DefaultStatsPrinterPlugin.js +3 -0
  178. package/lib/util/SourceProcessor.js +76 -69
  179. package/lib/util/async.js +292 -0
  180. package/lib/util/buildDiagnostics.js +69 -0
  181. package/lib/util/fs.js +27 -1
  182. package/lib/util/handlerKeys.js +40 -0
  183. package/lib/util/identifier.js +20 -0
  184. package/lib/util/internalSerializables.js +4 -0
  185. package/lib/util/registerExternalSerializer.js +6 -45
  186. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +9 -0
  187. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +9 -0
  188. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +9 -0
  189. package/lib/web/JsonpChunkLoadingRuntimeModule.js +36 -0
  190. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +19 -0
  191. package/package.json +22 -16
  192. package/schemas/WebpackOptions.check.js +1 -1
  193. package/schemas/WebpackOptions.json +379 -66
  194. package/schemas/plugins/LoaderOptionsPlugin.check.js +1 -1
  195. package/schemas/plugins/ManifestPlugin.check.js +1 -1
  196. package/schemas/plugins/SourceMapDevToolPlugin.check.js +1 -1
  197. package/schemas/plugins/asset/AssetGeneratorOptions.check.js +1 -1
  198. package/schemas/plugins/asset/AssetResourceGeneratorOptions.check.js +1 -1
  199. package/schemas/plugins/container/ContainerPlugin.check.js +1 -1
  200. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  201. package/schemas/plugins/debug/ProfilingPlugin.check.js +1 -1
  202. package/schemas/plugins/dll/DllReferencePlugin.check.js +1 -1
  203. package/schemas/plugins/ids/HashedModuleIdsPlugin.check.js +1 -1
  204. package/schemas/plugins/schemes/HttpUriPlugin.check.js +1 -1
  205. package/types.d.ts +1414 -314
  206. package/lib/dependencies/ExportBindingInitFragment.js +0 -191
  207. package/lib/errors/EntrypointOverlapWarning.js +0 -45
  208. 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 */
@@ -222,9 +252,130 @@ const isIntegrityEnabled = (integrity) =>
222
252
  // `\.html`/`\.css` request matchers for the synthetic `output.html` wrapper.
223
253
  const HTML_REQUEST_RE = /\.html(\?|$)/i;
224
254
  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;
255
+ /**
256
+ * The entry a page is the whole of: nothing imports the module and the entry
257
+ * asks for nothing else, so the entry's JavaScript would be the page itself.
258
+ * @param {Compilation} compilation compilation
259
+ * @param {Module} module html module
260
+ * @returns {{ name: string, data: EntryData } | undefined} the page's own entry, when it has one
261
+ */
262
+ const getPageEntry = (compilation, module) => {
263
+ const { moduleGraph } = compilation;
264
+ for (const connection of moduleGraph.getIncomingConnections(module)) {
265
+ if (connection.originModule) return undefined;
266
+ }
267
+ for (const [name, data] of compilation.entries) {
268
+ if (data.dependencies.length !== 1) continue;
269
+ if (data.includeDependencies.length !== 0) continue;
270
+ if (moduleGraph.getModule(data.dependencies[0]) === module) {
271
+ return { name, data };
272
+ }
273
+ }
274
+ return undefined;
275
+ };
276
+
277
+ /**
278
+ * The entry of a page that emits nothing but its markup, so its chunk carries
279
+ * no JavaScript. An `output.library` asks for the page's exports, and keeps it.
280
+ * @param {Compilation} compilation compilation
281
+ * @param {Module} module html module
282
+ * @returns {{ name: string, data: EntryData } | undefined} the entry, when the page is all it emits
283
+ */
284
+ const getPageOnlyEntry = (compilation, module) => {
285
+ const entry = getPageEntry(compilation, module);
286
+ if (entry === undefined) return undefined;
287
+ const { library } = entry.data.options;
288
+ const configured =
289
+ library === undefined ? compilation.outputOptions.library : library;
290
+ return configured === undefined ? entry : undefined;
291
+ };
292
+
293
+ /**
294
+ * The basename a tag's url lends to the entry it becomes, so
295
+ * `src="./my-script.js"` emits `my-script.js`. Undefined for an inline body.
296
+ * @param {string} request the tag's request
297
+ * @returns {string | undefined} the basename, when the url carries one
298
+ */
299
+ const getRequestBaseName = (request) => {
300
+ // An inline `<script>` body is a `data:` request webpack writes itself.
301
+ if (request.startsWith("data:")) return undefined;
302
+ const { path: file } = parseResource(request);
303
+ return basename(file, extname(file)) || undefined;
304
+ };
305
+
306
+ /**
307
+ * Claims `base`, numbering it while something else already emits under that
308
+ * name, so two tags sharing a basename stay distinct.
309
+ * @param {string} base preferred name
310
+ * @param {Set<string>} taken names this kind of output already emits under
311
+ * @returns {string} the claimed name
312
+ */
313
+ const claimName = (base, taken) => {
314
+ let name = base;
315
+ for (let index = 1; taken.has(name); index++) name = `${base}${index}`;
316
+ taken.add(name);
317
+ return name;
318
+ };
319
+
320
+ // Placeholders that tell chunks apart on their own, so a template carrying
321
+ // one needs no `[name]`. Not `[id]`: an extracted entry's id is synthetic.
322
+ const CONTENT_PLACEHOLDER_REGEXP =
323
+ /\[(?:chunkhash|contenthash|fullhash|hash)[\]:]/;
324
+
325
+ // Basename stem of a filename template — what `[name]` stands in for, keeping
326
+ // the directory, extension and query the template asked for.
327
+ const TEMPLATE_STEM_REGEXP = /(^|\/)[^/?]*?((?:\.[^./?]+)*)(\?.*)?$/;
328
+
329
+ const NAME_PLACEHOLDER_REGEXP = /\[name\]/g;
330
+
331
+ /**
332
+ * A filename template usable for a page's extracted entries: it must tell one
333
+ * entry from another, or every entry of the page would be emitted to the same
334
+ * file. A template naming neither the chunk nor its content lends its directory
335
+ * and extension to a `[name]` instead.
336
+ * @param {string} template configured template
337
+ * @param {string} extension extension to fall back to when the template names none
338
+ * @returns {string} a template that tells the page's entries apart
339
+ */
340
+ const getNamedTemplate = (template, extension) => {
341
+ if (
342
+ template.includes("[name]") ||
343
+ CONTENT_PLACEHOLDER_REGEXP.test(template)
344
+ ) {
345
+ return template;
346
+ }
347
+ return template.replace(
348
+ TEMPLATE_STEM_REGEXP,
349
+ (_, dir, ext, query) => `${dir}[name]${ext || extension}${query || ""}`
350
+ );
351
+ };
352
+
353
+ /**
354
+ * The filename one extracted entry emits under, with `name` filling the
355
+ * `[name]` its template carries or the stem it leaves behind.
356
+ * @param {string} template configured template
357
+ * @param {string} extension extension to fall back to when the template names none
358
+ * @param {string} name the name this entry claimed
359
+ * @returns {string} the entry's filename template
360
+ */
361
+ const fillNamedTemplate = (template, extension, name) =>
362
+ getNamedTemplate(template, extension).replace(NAME_PLACEHOLDER_REGEXP, name);
363
+
364
+ /**
365
+ * The same, for a template `output.filename` states as a function: it is called
366
+ * at emit, and the path it returns read for a stem the same way.
367
+ * @param {FilenameTemplate} template configured template
368
+ * @param {string} extension extension to fall back to when the template names none
369
+ * @param {string} name the name this entry claimed
370
+ * @returns {ChunkFilenameTemplate} the entry's filename template
371
+ */
372
+ const getEntryTemplate = (template, extension, name) => {
373
+ if (typeof template === "function") {
374
+ return (pathData, assetInfo) =>
375
+ fillNamedTemplate(template(pathData, assetInfo), extension, name);
376
+ }
377
+ return fillNamedTemplate(template, extension, name);
378
+ };
228
379
 
229
380
  /**
230
381
  * Requests an `output.html` page must load for an entry: its `dependOn`
@@ -294,7 +445,7 @@ class HtmlModulesPlugin {
294
445
  * digest of `content`, with `nonNumericOnlyHash` applied — webpack's
295
446
  * standard `[contenthash]` recipe.
296
447
  * @param {string | Buffer} content content to hash
297
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
448
+ * @param {OutputOptions} outputOptions output options
298
449
  * @returns {string} content hash
299
450
  */
300
451
  static computeContentHash(content, outputOptions) {
@@ -316,7 +467,7 @@ class HtmlModulesPlugin {
316
467
  * initial chunks, `output.htmlChunkFilename` otherwise — the HTML counterpart
317
468
  * of `CssModulesPlugin.getChunkFilenameTemplate`.
318
469
  * @param {import("../Chunk")} chunk chunk
319
- * @param {import("../../declarations/WebpackOptions").Output} outputOptions output options
470
+ * @param {OutputOptions} outputOptions output options
320
471
  * @returns {import("../Chunk").ChunkFilenameTemplate} used filename template
321
472
  */
322
473
  static getChunkFilenameTemplate(chunk, outputOptions) {
@@ -485,11 +636,11 @@ class HtmlModulesPlugin {
485
636
  // `finishMake` creates their entries without scanning the whole module
486
637
  // graph), the stylesheet entry names (read in `afterChunks`) and the html
487
638
  // 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> }>} */
639
+ /** @type {WeakMap<import("../Compilation"), { htmlModules: Set<Module>, stylesheetEntries: Set<string>, pendingNames: Map<string, PendingName>, htmlAssetOptions: Map<string, OutputHtmlOptions> }>} */
489
640
  const compilationState = new WeakMap();
490
641
  /**
491
642
  * @param {import("../Compilation")} compilation compilation
492
- * @returns {{ htmlModules: Set<import("../Module")>, stylesheetEntries: Set<string>, htmlAssetOptions: Map<string, OutputHtmlOptions> }} per-compilation state
643
+ * @returns {{ htmlModules: Set<Module>, stylesheetEntries: Set<string>, pendingNames: Map<string, PendingName>, htmlAssetOptions: Map<string, OutputHtmlOptions> }} per-compilation state
493
644
  */
494
645
  const getState = (compilation) => {
495
646
  let state = compilationState.get(compilation);
@@ -497,6 +648,7 @@ class HtmlModulesPlugin {
497
648
  state = {
498
649
  htmlModules: new Set(),
499
650
  stylesheetEntries: new Set(),
651
+ pendingNames: new Map(),
500
652
  htmlAssetOptions: new Map()
501
653
  };
502
654
  compilationState.set(compilation, state);
@@ -504,14 +656,21 @@ class HtmlModulesPlugin {
504
656
  return state;
505
657
  };
506
658
  compiler.hooks.finishMake.tapAsync(PLUGIN_NAME, (compilation, callback) => {
507
- const { htmlModules, stylesheetEntries } = getState(compilation);
659
+ const { htmlModules, stylesheetEntries, pendingNames } =
660
+ getState(compilation);
661
+ /** @type {Set<string>} */
662
+ const extractedNames = new Set();
663
+ // The page each `type: "html"` link built, so the naming walk below can
664
+ // follow the links in document order instead of in build order.
665
+ /** @type {Map<string, Module>} */
666
+ const linkedPages = new Map();
508
667
 
509
668
  // Collect the entries an HTML module asks for. Only the script chains
510
669
  // (`script`, `script-module`) share a runtime via a leader-only
511
670
  // `dependOn`; `modulepreload`, `stylesheet` and `html` links are all
512
671
  // independent entries (a CSS or page entry must not chain into a JS
513
672
  // leader, or the chunk would mix unrelated outputs).
514
- /** @type {(module: import("../Module")) => { context: string, request: string, name: string, dependOn: string[] | undefined }[]} */
673
+ /** @type {(module: Module) => { context: string, request: string, name: string, dependOn: string[] | undefined }[]} */
515
674
  const collectEntrySpecs = (module) => {
516
675
  const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
517
676
  module.buildInfo
@@ -533,10 +692,11 @@ class HtmlModulesPlugin {
533
692
  leaderName = entry.entryName;
534
693
  }
535
694
  // 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) {
695
+ // a CSS chunk, so they are named from `output.cssFilename` alone.
696
+ if (groupKind === "stylesheet" || Boolean(entry.css)) {
538
697
  stylesheetEntries.add(entry.entryName);
539
698
  }
699
+ extractedNames.add(entry.entryName);
540
700
  specs.push({
541
701
  context,
542
702
  request: entry.request,
@@ -548,9 +708,70 @@ class HtmlModulesPlugin {
548
708
  return specs;
549
709
  };
550
710
 
711
+ // Records what a page's entries ask to be named, then walks its links —
712
+ // once every page is built, so the order is the documents', not the builds'.
713
+ /** @type {(module: Module, seen: Set<Module>) => void} */
714
+ const recordNames = (module, seen) => {
715
+ if (seen.has(module)) return;
716
+ seen.add(module);
717
+ const { htmlEntries } = /** @type {HtmlModuleBuildInfo} */ (
718
+ module.buildInfo
719
+ );
720
+ if (!htmlEntries) return;
721
+ const pageEntry = getPageOnlyEntry(compilation, module);
722
+ const namedEntry =
723
+ pageEntry !== undefined && !extractedNames.has(pageEntry.name)
724
+ ? pageEntry
725
+ : undefined;
726
+ const { resource } = /** @type {NormalModule} */ (module);
727
+ // An `output.html` page is a wrapper webpack writes around an entry, so
728
+ // what it loads is that entry's own output and takes its name.
729
+ const wrapsEntry =
730
+ namedEntry !== undefined &&
731
+ typeof resource === "string" &&
732
+ resource.startsWith("data:text/html");
733
+ const { filename } = compilation.outputOptions;
734
+ // Kept as configured, a function included, and read for a name once the
735
+ // chunks exist.
736
+ const jsTemplate =
737
+ namedEntry && namedEntry.data.options.filename !== undefined
738
+ ? namedEntry.data.options.filename
739
+ : filename;
740
+ // In document order, so of two tags sharing a basename the first a
741
+ // reader sees keeps it. A linked page is named on emit instead.
742
+ /** @type {HtmlEntryInfo[]} */
743
+ const ordered = [];
744
+ /** @type {HtmlEntryInfo[]} */
745
+ const links = [];
746
+ for (const [groupKind, group] of Object.entries(htmlEntries)) {
747
+ for (const entry of group) {
748
+ (groupKind === "html" ? links : ordered).push(entry);
749
+ }
750
+ }
751
+ ordered.sort((a, b) => a.index - b.index);
752
+ for (const entry of ordered) {
753
+ // An inline `<script>` body has no url to be named after, so it
754
+ // keeps the name of its own entry, page hash and tag index.
755
+ const base =
756
+ wrapsEntry && namedEntry
757
+ ? namedEntry.name
758
+ : getRequestBaseName(entry.request) || entry.entryName;
759
+ pendingNames.set(entry.entryName, {
760
+ base,
761
+ jsTemplate,
762
+ freeName: namedEntry && namedEntry.name
763
+ });
764
+ }
765
+ links.sort((a, b) => a.index - b.index);
766
+ for (const link of links) {
767
+ const page = linkedPages.get(link.entryName);
768
+ if (page) recordNames(page, seen);
769
+ }
770
+ };
771
+
551
772
  // Push one collected reference as a compilation entry; resolves with
552
773
  // the built entry module.
553
- /** @type {(spec: { context: string, request: string, name: string, dependOn: string[] | undefined }) => Promise<import("../Module") | null>} */
774
+ /** @type {(spec: { context: string, request: string, name: string, dependOn: string[] | undefined }) => Promise<Module | null>} */
554
775
  const addEntry = (spec) =>
555
776
  new Promise((resolve, reject) => {
556
777
  compilation.addEntry(
@@ -558,10 +779,8 @@ class HtmlModulesPlugin {
558
779
  EntryPlugin.createDependency(spec.request, { name: spec.name }),
559
780
  {
560
781
  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`.
782
+ // A placeholder `afterChunks` overwrites for every entry it
783
+ // names; only a linked page keeps it, and it emits no JavaScript.
565
784
  filename: compilation.outputOptions.chunkFilename || "[name].js",
566
785
  dependOn: spec.dependOn
567
786
  },
@@ -575,7 +794,7 @@ class HtmlModulesPlugin {
575
794
  // every HTML entry already gets. `processed` guards diamonds and cycles.
576
795
  /** @type {WeakSet<import("../Module")>} */
577
796
  const processed = new WeakSet();
578
- /** @type {(module: import("../Module")) => Promise<void>} */
797
+ /** @type {(module: Module) => Promise<void>} */
579
798
  const processEntry = async (module) => {
580
799
  if (processed.has(module)) return;
581
800
  processed.add(module);
@@ -583,6 +802,7 @@ class HtmlModulesPlugin {
583
802
  collectEntrySpecs(module).map(async (spec) => {
584
803
  const entryModule = await addEntry(spec);
585
804
  if (entryModule && entryModule.type === HTML_MODULE_TYPE) {
805
+ linkedPages.set(spec.name, entryModule);
586
806
  await processEntry(entryModule);
587
807
  }
588
808
  })
@@ -590,11 +810,15 @@ class HtmlModulesPlugin {
590
810
  };
591
811
  // Seed with the HTML modules seen during make (config entries /
592
812
  // 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
- );
813
+ // recursion above. Sorted because they are tracked in build-completion
814
+ // order, which the names must not depend on.
815
+ const seeds = [...htmlModules].sort(compareModulesByIdentifier);
816
+ Promise.all(seeds.map(processEntry)).then(() => {
817
+ /** @type {Set<Module>} */
818
+ const named = new Set();
819
+ for (const seed of seeds) recordNames(seed, named);
820
+ callback();
821
+ }, callback);
598
822
  });
599
823
 
600
824
  // `csp` injects a `<meta http-equiv="Content-Security-Policy">` and
@@ -725,26 +949,6 @@ class HtmlModulesPlugin {
725
949
  // a content-hashed filename makes a substring hit a real reference.
726
950
  /** @type {Set<string>} */
727
951
  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
952
  // Materialize each other asset's source once (it can be expensive to
749
953
  // render) and test all candidates against it, rather than
750
954
  // re-materializing every asset per candidate.
@@ -796,26 +1000,57 @@ class HtmlModulesPlugin {
796
1000
  // entry emits to a distinct file derived from `output.cssFilename`
797
1001
  // (or `output.cssChunkFilename` for non-initial CSS chunks).
798
1002
  compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => {
799
- const { stylesheetEntries } = getState(compilation);
800
- if (stylesheetEntries.size === 0) return;
801
- for (const entryName of stylesheetEntries) {
1003
+ const { stylesheetEntries, pendingNames } = getState(compilation);
1004
+ if (pendingNames.size === 0) return;
1005
+ const { chunkGraph, outputOptions } = compilation;
1006
+ const jsExtension = outputOptions.module ? ".mjs" : ".js";
1007
+ // Claimed in the order the pages recorded them, which is the
1008
+ // documents' order rather than their builds'.
1009
+ const takenJsNames = new Set(compilation.entries.keys());
1010
+ const takenCssNames = new Set(takenJsNames);
1011
+ // An extracted entry emits nothing under its own synthetic name, so
1012
+ // that name never stands in the way of the one its tag asks for.
1013
+ for (const entryName of pendingNames.keys()) {
1014
+ takenJsNames.delete(entryName);
1015
+ takenCssNames.delete(entryName);
1016
+ }
1017
+ for (const { freeName } of pendingNames.values()) {
1018
+ if (freeName === undefined) continue;
1019
+ // A page-only entry emits nothing of its own, so its name is free for
1020
+ // the entries extracted from it — `app.html` keeps `app.js`.
1021
+ takenJsNames.delete(freeName);
1022
+ takenCssNames.delete(freeName);
1023
+ }
1024
+ for (const [entryName, { base, jsTemplate }] of pendingNames) {
802
1025
  const entrypoint = compilation.entrypoints.get(entryName);
803
1026
  if (!entrypoint) continue;
804
1027
  const chunk = entrypoint.getEntrypointChunk();
805
1028
  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;
1029
+ // A stylesheet entry emits no JavaScript, so it claims no JS name and
1030
+ // leaves that one to a `<script src>` of the same basename.
1031
+ const emitsCss = stylesheetEntries.has(entryName);
1032
+ if (!emitsCss) {
1033
+ // `output.filename` named after the tag's url, the way an entry
1034
+ // point is named. `Compilation` set it from the placeholder above.
1035
+ chunk.filenameTemplate = getEntryTemplate(
1036
+ jsTemplate,
1037
+ jsExtension,
1038
+ claimName(base, takenJsNames)
1039
+ );
1040
+ }
1041
+ // A `<script src>` whose chunk carries no CSS claims no CSS name, so
1042
+ // a `<link>` of the same basename still gets the plain one.
1043
+ if (
1044
+ !emitsCss &&
1045
+ !getCssModulesPlugin().chunkHasCss(chunk, chunkGraph)
1046
+ ) {
1047
+ continue;
1048
+ }
1049
+ chunk.cssFilenameTemplate = getEntryTemplate(
1050
+ outputOptions.cssFilename,
1051
+ ".css",
1052
+ claimName(base, takenCssNames)
1053
+ );
819
1054
  }
820
1055
  });
821
1056
  compilation.dependencyTemplates.set(
@@ -933,7 +1168,8 @@ class HtmlModulesPlugin {
933
1168
  );
934
1169
  return new (getHtmlGenerator())(
935
1170
  generatorOptions,
936
- compilation.moduleGraph
1171
+ compilation.moduleGraph,
1172
+ (module) => getPageOnlyEntry(compilation, module) !== undefined
937
1173
  );
938
1174
  });
939
1175
 
@@ -1211,6 +1447,16 @@ class HtmlModulesPlugin {
1211
1447
  jsHooks.render.tap(PLUGIN_NAME, (source, renderContext) => {
1212
1448
  // No HTML modules ⇒ no sentinels; skip materializing the JS source.
1213
1449
  if (htmlModules.size === 0) return source;
1450
+ // Chunks without HTML modules can't carry HTML sentinels — avoid
1451
+ // ConcatSource.source() on the common pure-JS chunk path.
1452
+ if (
1453
+ !HtmlModulesPlugin.chunkHasHtml(
1454
+ renderContext.chunk,
1455
+ compilation.chunkGraph
1456
+ )
1457
+ ) {
1458
+ return source;
1459
+ }
1214
1460
  const raw = source.source();
1215
1461
  if (typeof raw !== "string") return source;
1216
1462
  // Every sentinel shares this prefix — one scan answers the common
@@ -1281,6 +1527,29 @@ class HtmlModulesPlugin {
1281
1527
  }
1282
1528
  );
1283
1529
  }
1530
+
1531
+ /**
1532
+ * Returns true, when the chunk has html.
1533
+ * @param {Chunk} chunk chunk
1534
+ * @param {ChunkGraph} chunkGraph chunk graph
1535
+ * @returns {boolean} true, when the chunk has html
1536
+ */
1537
+ static chunkHasHtml(chunk, chunkGraph) {
1538
+ if (chunkGraph.getChunkModulesIterableBySourceType(chunk, HTML_TYPE)) {
1539
+ return true;
1540
+ }
1541
+ // Scope-hoisted HTML loses HTML_TYPE on the ConcatenatedModule, but its
1542
+ // JS export can still carry unresolved sentinels that need this path.
1543
+ for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
1544
+ if (module instanceof HtmlModule) return true;
1545
+ if (module instanceof ConcatenatedModule) {
1546
+ for (const inner of module.modules) {
1547
+ if (inner instanceof HtmlModule) return true;
1548
+ }
1549
+ }
1550
+ }
1551
+ return false;
1552
+ }
1284
1553
  }
1285
1554
 
1286
1555
  /**
@@ -868,6 +868,7 @@ class HtmlParser extends Parser {
868
868
  * @typedef {object} HtmlEntryInfo
869
869
  * @property {string} request
870
870
  * @property {string} entryName
871
+ * @property {number} index position in the document, across all groups — the order the emitted files are named in
871
872
  * @property {"script" | "script-module" | "modulepreload" | "stylesheet" | "html" | "preload" | "prefetch"} type
872
873
  * @property {boolean=} css entry emits a CSS chunk (`<link rel="preload" as="style">`), so it needs the CSS filename template
873
874
  */
@@ -1478,7 +1479,8 @@ class HtmlParser extends Parser {
1478
1479
  const scriptType =
1479
1480
  /** @type {"script" | "script-module"} */ (type);
1480
1481
  const request = dataUri("text/javascript", value);
1481
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1482
+ const index = nextEntryIndex++;
1483
+ const entryName = `__html_${moduleHash}_${index}`;
1482
1484
  const dep = new HtmlInlineScriptDependency(
1483
1485
  request,
1484
1486
  nameEnd,
@@ -1501,6 +1503,7 @@ class HtmlParser extends Parser {
1501
1503
  ).push({
1502
1504
  request,
1503
1505
  entryName,
1506
+ index,
1504
1507
  type: scriptType
1505
1508
  });
1506
1509
  break;
@@ -1581,7 +1584,8 @@ class HtmlParser extends Parser {
1581
1584
  // kind rewrites to the page's filename and emits no sibling tags.
1582
1585
  // Recorded as an independent entry (no `dependOn`), like an entry point.
1583
1586
  if (type === "html") {
1584
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1587
+ const index = nextEntryIndex++;
1588
+ const entryName = `__html_${moduleHash}_${index}`;
1585
1589
  const dep = new HtmlEntryDependency(
1586
1590
  request,
1587
1591
  [s, e],
@@ -1591,7 +1595,7 @@ class HtmlParser extends Parser {
1591
1595
  );
1592
1596
  setLoc(dep, s, e);
1593
1597
  module.addPresentationalDependency(dep);
1594
- htmlLinkEntries.push({ request, entryName, type });
1598
+ htmlLinkEntries.push({ request, entryName, index, type });
1595
1599
  continue;
1596
1600
  }
1597
1601
  // `rel="preload"/"prefetch"` of a bundled resource: an
@@ -1603,7 +1607,8 @@ class HtmlParser extends Parser {
1603
1607
  const asStyle = as
1604
1608
  ? as.trim().toLowerCase() === "style"
1605
1609
  : false;
1606
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1610
+ const index = nextEntryIndex++;
1611
+ const entryName = `__html_${moduleHash}_${index}`;
1607
1612
  // Tag metadata so the template can mirror `crossorigin`/
1608
1613
  // `integrity` onto a native `preload` `<link>` (integrity-
1609
1614
  // eligible per the SRI spec). `prefetch` gets none — the spec
@@ -1635,6 +1640,7 @@ class HtmlParser extends Parser {
1635
1640
  ).push({
1636
1641
  request,
1637
1642
  entryName,
1643
+ index,
1638
1644
  type,
1639
1645
  css: asStyle
1640
1646
  });
@@ -1649,7 +1655,8 @@ class HtmlParser extends Parser {
1649
1655
  type === "modulepreload" ||
1650
1656
  type === "stylesheet"
1651
1657
  ) {
1652
- const entryName = `__html_${moduleHash}_${nextEntryIndex++}`;
1658
+ const index = nextEntryIndex++;
1659
+ const entryName = `__html_${moduleHash}_${index}`;
1653
1660
  const willBeModuleScript =
1654
1661
  type === "script-module" ||
1655
1662
  (outputModule && type === "script");
@@ -1777,7 +1784,7 @@ class HtmlParser extends Parser {
1777
1784
  : type === "stylesheet"
1778
1785
  ? stylesheetEntries
1779
1786
  : modulePreloadEntries
1780
- ).push({ request, entryName, type });
1787
+ ).push({ request, entryName, index, type });
1781
1788
  } else {
1782
1789
  const dep = new HtmlSourceDependency(request, [s, e]);
1783
1790
  ResourceHintPlugin.applyDefaults(