webpack 5.110.0 → 5.110.2

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 (52) hide show
  1. package/README.md +10 -17
  2. package/lib/ExportsInfo.js +10 -0
  3. package/lib/FileSystemInfo.js +17 -2
  4. package/lib/Generator.js +17 -0
  5. package/lib/NormalModule.js +3 -3
  6. package/lib/RuntimeTemplate.js +423 -430
  7. package/lib/asset/AssetBytesGenerator.js +1 -3
  8. package/lib/asset/AssetGenerator.js +1 -3
  9. package/lib/asset/AssetSourceGenerator.js +1 -3
  10. package/lib/config/defaults.js +6 -0
  11. package/lib/css/CssGenerator.js +1 -3
  12. package/lib/css/CssLoadingRuntimeModule.js +32 -13
  13. package/lib/css/CssModulesPlugin.js +9 -17
  14. package/lib/css/data.js +13 -3
  15. package/lib/css/syntax.js +78 -10
  16. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +10 -4
  17. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +2 -0
  18. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +48 -3
  19. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +83 -14
  20. package/lib/dependencies/HarmonyImportSideEffectDependency.js +114 -0
  21. package/lib/dependencies/WorkerAndWorkletPlugin.js +1 -0
  22. package/lib/dependencies/WorkerDependency.js +6 -12
  23. package/lib/dependencies/WorkletDependency.js +7 -11
  24. package/lib/esm/ModuleChunkLoadingPlugin.js +6 -9
  25. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +7 -1
  26. package/lib/html/HtmlGenerator.js +1 -1
  27. package/lib/html/HtmlModulesPlugin.js +55 -16
  28. package/lib/html/syntax.js +113 -26
  29. package/lib/javascript/JavascriptGenerator.js +1 -1
  30. package/lib/javascript/JavascriptModulesPlugin.js +3 -10
  31. package/lib/json/JsonGenerator.js +1 -1
  32. package/lib/loaders/LoaderRunner.js +33 -5
  33. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +8 -15
  34. package/lib/optimize/ConcatenatedModule.js +8 -0
  35. package/lib/optimize/ConstExportsPlugin.js +5 -0
  36. package/lib/optimize/InlineExports.js +121 -0
  37. package/lib/optimize/RealContentHashPlugin.js +111 -35
  38. package/lib/prefetch/ResourceHintPlugin.js +6 -12
  39. package/lib/serialization/ObjectMiddleware.js +128 -108
  40. package/lib/typescript/TypeScriptPlugin.js +8 -10
  41. package/lib/util/SourceProcessor.js +24 -8
  42. package/lib/util/createMappings.js +25 -3
  43. package/lib/util/removeBOM.js +82 -1
  44. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +5 -3
  45. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +5 -3
  46. package/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +3 -1
  47. package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +7 -12
  48. package/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +3 -1
  49. package/lib/web/FetchCompileAsyncWasmPlugin.js +10 -6
  50. package/lib/web/FetchCompileWasmPlugin.js +4 -2
  51. package/package.json +1 -1
  52. package/types.d.ts +77 -10
@@ -43,6 +43,7 @@ const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
43
43
  * } from "../../declarations/WebpackOptions"
44
44
  */
45
45
  /** @import Module from "../Module" */
46
+ /** @import { JavascriptModuleBuildInfo } from "../javascript/JavascriptModule" */
46
47
  /**
47
48
  * @import JavascriptParser, {
48
49
  * ImportAttributes,
@@ -71,10 +72,24 @@ const EMPTY_ID_RANGES = [];
71
72
  * @property {boolean} await
72
73
  * @property {ImportAttributes=} attributes
73
74
  * @property {ImportPhaseType} phase
75
+ * @property {boolean} used whether the binding is referenced anywhere
76
+ * @property {HarmonyImportSideEffectDependency | undefined} dependency the statement's own dependency
74
77
  */
75
78
 
76
79
  const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
77
80
 
81
+ /**
82
+ * Records that an import binding is referenced, so it is not reported as an
83
+ * unused specifier naming a missing export.
84
+ * @param {HarmonySettings | undefined} settings settings of the referenced tag
85
+ * @returns {boolean} true when the reference was a harmony import specifier
86
+ */
87
+ const markUsed = (settings) => {
88
+ if (!settings) return false;
89
+ settings.used = true;
90
+ return true;
91
+ };
92
+
78
93
  /**
79
94
  * Gets in operator harmony import info.
80
95
  * @param {JavascriptParser} parser the parser
@@ -107,6 +122,7 @@ const getInOperatorHarmonyImportInfo = (parser, left, right) => {
107
122
  if (!settings) {
108
123
  return;
109
124
  }
125
+ settings.used = true;
110
126
 
111
127
  return {
112
128
  leftPart,
@@ -125,7 +141,10 @@ const getInOperatorHarmonyImportInfo = (parser, left, right) => {
125
141
  const findImportSpecifier = (parser, node) => {
126
142
  switch (node.type) {
127
143
  case "Identifier":
128
- return Boolean(parser.getTagData(node.name, harmonySpecifierTag));
144
+ return markUsed(
145
+ /** @type {HarmonySettings | undefined} */
146
+ (parser.getTagData(node.name, harmonySpecifierTag))
147
+ );
129
148
  case "UnaryExpression":
130
149
  return findImportSpecifier(
131
150
  parser,
@@ -213,6 +232,35 @@ class HarmonyImportDependencyParserPlugin {
213
232
  this.options.sourceImport
214
233
  );
215
234
 
235
+ // Parsers are reused across modules, so both are reset per program.
236
+ /** @type {HarmonySettings[]} */
237
+ const declaredSpecifiers = [];
238
+ /** @type {HarmonyImportSideEffectDependency | undefined} */
239
+ let lastSideEffectDependency;
240
+
241
+ parser.hooks.program.tap(PLUGIN_NAME, () => {
242
+ declaredSpecifiers.length = 0;
243
+ lastSideEffectDependency = undefined;
244
+ });
245
+ parser.hooks.finish.tap(PLUGIN_NAME, () => {
246
+ for (const settings of declaredSpecifiers) {
247
+ const dependency = settings.dependency;
248
+ if (dependency === undefined || settings.used) continue;
249
+ if (dependency.unusedSpecifiers === undefined) {
250
+ dependency.unusedSpecifiers = {
251
+ exportPresenceMode: this.exportPresenceMode,
252
+ specifiers: []
253
+ };
254
+ }
255
+ dependency.unusedSpecifiers.specifiers.push([
256
+ settings.ids,
257
+ settings.name
258
+ ]);
259
+ }
260
+ declaredSpecifiers.length = 0;
261
+ lastSideEffectDependency = undefined;
262
+ });
263
+
216
264
  /**
217
265
  * Gets non optional member chain.
218
266
  * @param {MemberExpression} node member expression
@@ -228,7 +276,10 @@ class HarmonyImportDependencyParserPlugin {
228
276
  const expr = /** @type {Identifier} */ (expression);
229
277
  if (
230
278
  parser.isVariableDefined(expr.name) ||
231
- parser.getTagData(expr.name, harmonySpecifierTag)
279
+ markUsed(
280
+ /** @type {HarmonySettings | undefined} */
281
+ (parser.getTagData(expr.name, harmonySpecifierTag))
282
+ )
232
283
  ) {
233
284
  return true;
234
285
  }
@@ -267,6 +318,7 @@ class HarmonyImportDependencyParserPlugin {
267
318
  );
268
319
  sideEffectDep.loc = parser.getLocation(statement);
269
320
  parser.state.module.addDependency(sideEffectDep);
321
+ lastSideEffectDependency = sideEffectDep;
270
322
  return true;
271
323
  });
272
324
  parser.hooks.importSpecifier.tap(
@@ -274,18 +326,20 @@ class HarmonyImportDependencyParserPlugin {
274
326
  (statement, source, id, name) => {
275
327
  const ids = id === null ? [] : [id];
276
328
  const phase = getImportPhase(parser, statement);
277
- parser.tagVariable(
329
+ const settings = /** @type {HarmonySettings} */ ({
278
330
  name,
279
- harmonySpecifierTag,
280
- /** @type {HarmonySettings} */ ({
281
- name,
282
- source,
283
- ids,
284
- sourceOrder: parser.state.lastHarmonyImportOrder,
285
- attributes: getImportAttributes(statement),
286
- phase
287
- })
288
- );
331
+ source,
332
+ ids,
333
+ sourceOrder: parser.state.lastHarmonyImportOrder,
334
+ attributes: getImportAttributes(statement),
335
+ phase,
336
+ used: false,
337
+ dependency: lastSideEffectDependency
338
+ });
339
+ parser.tagVariable(name, harmonySpecifierTag, settings);
340
+ // The statement's dependency carries them, so a never-referenced
341
+ // one still resolves; a namespace import names no export.
342
+ if (ids.length !== 0) declaredSpecifiers.push(settings);
289
343
  return true;
290
344
  }
291
345
  );
@@ -328,16 +382,28 @@ class HarmonyImportDependencyParserPlugin {
328
382
  nameInfo &&
329
383
  nameInfo.rootInfo instanceof VariableInfo &&
330
384
  nameInfo.rootInfo.name &&
331
- parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag)
385
+ markUsed(
386
+ /** @type {HarmonySettings | undefined} */
387
+ (parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag))
388
+ )
332
389
  ) {
333
390
  return true;
334
391
  }
335
392
  }
336
393
  );
394
+ // Concatenation would turn the binding into a plain local, losing the
395
+ // getter-only property whose write throws.
396
+ parser.hooks.assign.for(harmonySpecifierTag).tap(PLUGIN_NAME, () => {
397
+ markUsed(/** @type {HarmonySettings} */ (parser.currentTagData));
398
+ /** @type {JavascriptModuleBuildInfo} */
399
+ (parser.state.module.buildInfo).moduleConcatenationBailout =
400
+ "assignment to an imported binding";
401
+ });
337
402
  parser.hooks.expression
338
403
  .for(harmonySpecifierTag)
339
404
  .tap(PLUGIN_NAME, (expr) => {
340
405
  const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
406
+ settings.used = true;
341
407
 
342
408
  const dep = new HarmonyImportSpecifierDependency(
343
409
  settings.source,
@@ -376,6 +442,7 @@ class HarmonyImportDependencyParserPlugin {
376
442
  const settings =
377
443
  /** @type {HarmonySettings} */
378
444
  (parser.currentTagData);
445
+ settings.used = true;
379
446
  const nonOptionalMembers = getNonOptionalPart(
380
447
  members,
381
448
  membersOptionals
@@ -431,6 +498,7 @@ class HarmonyImportDependencyParserPlugin {
431
498
  const settings = /** @type {HarmonySettings} */ (
432
499
  parser.currentTagData
433
500
  );
501
+ settings.used = true;
434
502
  const nonOptionalMembers = getNonOptionalPart(
435
503
  members,
436
504
  membersOptionals
@@ -491,6 +559,7 @@ class HarmonyImportDependencyParserPlugin {
491
559
  .for(harmonySpecifierTag)
492
560
  .tap(PLUGIN_NAME, (expression, members) => {
493
561
  const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
562
+ settings.used = true;
494
563
  if (!ImportPhaseUtils.isDefer(settings.phase)) return;
495
564
  if (expression.operator !== "=") return;
496
565
  if (members.length !== 1) return;
@@ -13,6 +13,8 @@ const makeSerializable = require("../util/makeSerializable");
13
13
  const HarmonyImportDependency = require("./HarmonyImportDependency");
14
14
  const { ImportPhaseUtils } = require("./ImportPhase");
15
15
 
16
+ const { ExportPresenceModes } = HarmonyImportDependency;
17
+
16
18
  /** @import { ReplaceSource } from "webpack-sources" */
17
19
  /** @import { GetConditionFn, LazyUntil } from "../Dependency" */
18
20
  /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
@@ -20,6 +22,19 @@ const { ImportPhaseUtils } = require("./ImportPhase");
20
22
  /** @import { ConnectionState } from "../ModuleGraphConnection" */
21
23
  /** @import { ImportAttributes } from "../javascript/JavascriptParser" */
22
24
  /** @import { ImportPhaseType } from "./ImportPhase" */
25
+ /** @import { Ids, ExportPresenceMode } from "./HarmonyImportDependency" */
26
+ /** @import { JavascriptModuleBuildMeta } from "../javascript/JavascriptModule" */
27
+ /** @import WebpackError from "../errors/WebpackError" */
28
+ /** @import { ObjectSerializerContext, ObjectDeserializerContext } from "../serialization/ObjectMiddleware" */
29
+
30
+ /**
31
+ * Specifiers the module declares but never reads, with the presence level to
32
+ * report them at. Allocated only for a module that has one, so the common
33
+ * import pays a single empty slot.
34
+ * @typedef {object} UnusedSpecifiers
35
+ * @property {ExportPresenceMode} exportPresenceMode
36
+ * @property {[Ids, string][]} specifiers
37
+ */
23
38
 
24
39
  class HarmonyImportSideEffectDependency extends HarmonyImportDependency {
25
40
  /**
@@ -31,12 +46,111 @@ class HarmonyImportSideEffectDependency extends HarmonyImportDependency {
31
46
  */
32
47
  constructor(request, sourceOrder, phase, attributes) {
33
48
  super(request, sourceOrder, phase, attributes);
49
+ /** @type {UnusedSpecifiers | undefined} */
50
+ this.unusedSpecifiers = undefined;
34
51
  }
35
52
 
36
53
  get type() {
37
54
  return "harmony side effect evaluation";
38
55
  }
39
56
 
57
+ /**
58
+ * Returns warnings.
59
+ * @param {ModuleGraph} moduleGraph module graph
60
+ * @returns {WebpackError[] | null | undefined} warnings
61
+ */
62
+ getWarnings(moduleGraph) {
63
+ if (
64
+ this._getEffectiveExportPresenceLevel(moduleGraph) !==
65
+ ExportPresenceModes.WARN
66
+ ) {
67
+ return null;
68
+ }
69
+ return this._getUnusedSpecifierErrors(moduleGraph);
70
+ }
71
+
72
+ /**
73
+ * Get effective export presence level.
74
+ * @param {ModuleGraph} moduleGraph module graph
75
+ * @returns {ExportPresenceMode} effective mode
76
+ */
77
+ _getEffectiveExportPresenceLevel(moduleGraph) {
78
+ if (this.unusedSpecifiers === undefined) return ExportPresenceModes.NONE;
79
+ const mode = this.unusedSpecifiers.exportPresenceMode;
80
+ if (mode !== ExportPresenceModes.AUTO) return mode;
81
+ const buildMeta =
82
+ /** @type {JavascriptModuleBuildMeta} */
83
+ (
84
+ /** @type {Module} */
85
+ (moduleGraph.getParentModule(this)).buildMeta
86
+ );
87
+ return buildMeta.strictHarmonyModule
88
+ ? ExportPresenceModes.ERROR
89
+ : ExportPresenceModes.WARN;
90
+ }
91
+
92
+ /**
93
+ * Returns errors.
94
+ * @param {ModuleGraph} moduleGraph module graph
95
+ * @returns {WebpackError[] | null | undefined} errors
96
+ */
97
+ getErrors(moduleGraph) {
98
+ if (
99
+ this._getEffectiveExportPresenceLevel(moduleGraph) !==
100
+ ExportPresenceModes.ERROR
101
+ ) {
102
+ return null;
103
+ }
104
+ return this._getUnusedSpecifierErrors(moduleGraph);
105
+ }
106
+
107
+ /**
108
+ * Linking errors for specifiers the module declares but never references.
109
+ * The spec resolves every import entry, so a name that does not exist is a
110
+ * link error whether or not the binding is read.
111
+ * @param {ModuleGraph} moduleGraph module graph
112
+ * @returns {WebpackError[] | undefined} errors
113
+ */
114
+ _getUnusedSpecifierErrors(moduleGraph) {
115
+ // Both callers resolve the presence level first, which is NONE without
116
+ // specifiers, so this only runs when there are some.
117
+ const { specifiers } = /** @type {UnusedSpecifiers} */ (
118
+ this.unusedSpecifiers
119
+ );
120
+ /** @type {WebpackError[] | undefined} */
121
+ let errors;
122
+ for (const [ids, name] of specifiers) {
123
+ const linkingErrors = this.getLinkingErrors(
124
+ moduleGraph,
125
+ ids,
126
+ `(imported as '${name}')`
127
+ );
128
+ if (linkingErrors) {
129
+ if (errors === undefined) errors = linkingErrors;
130
+ else errors.push(...linkingErrors);
131
+ }
132
+ }
133
+ return errors;
134
+ }
135
+
136
+ /**
137
+ * Serializes this instance into the provided serializer context.
138
+ * @param {ObjectSerializerContext} context context
139
+ */
140
+ serialize(context) {
141
+ context.write(this.unusedSpecifiers);
142
+ super.serialize(context);
143
+ }
144
+
145
+ /**
146
+ * Restores this instance from the provided deserializer context.
147
+ * @param {ObjectDeserializerContext} context context
148
+ */
149
+ deserialize(context) {
150
+ this.unusedSpecifiers = context.read();
151
+ super.deserialize(context);
152
+ }
153
+
40
154
  /**
41
155
  * Returns the export name this dependency requests from its target module (lazy barrel optimization).
42
156
  * @returns {string | true | null} export name, true for all exports, null for none
@@ -753,6 +753,7 @@ class WorkerAndWorkletPlugin {
753
753
  ) {
754
754
  return;
755
755
  }
756
+ settings.used = true;
756
757
  return handle(expr);
757
758
  });
758
759
  } else {
@@ -136,19 +136,13 @@ WorkerDependency.Template = class WorkerDependencyTemplate extends (
136
136
  // and webpack itself can statically follow the worker. A resource hint doesn't
137
137
  // block it: the `<link>` is emitted at chunk startup instead of wrapping the href,
138
138
  // which would turn the specifier into a non-analyzable expression.
139
- const analyzableSpecifier = runtimeTemplate.supportsAnalyzable(
140
- "url",
139
+ const analyzableSpecifier = runtimeTemplate.getAnalyzableWorkerUrl(
140
+ dep.options.publicPath,
141
+ chunk,
142
+ templateContext.module,
141
143
  chunkGraph,
142
- templateContext.module
143
- )
144
- ? runtimeTemplate._getAnalyzableChunkSpecifier(
145
- dep.options.publicPath,
146
- chunk,
147
- templateContext.module,
148
- chunkGraph,
149
- runtimeRequirements
150
- )
151
- : null;
144
+ runtimeRequirements
145
+ );
152
146
 
153
147
  if (analyzableSpecifier !== null) {
154
148
  // No `<link>` helper is asked for here: this form emits no call, and
@@ -140,17 +140,13 @@ WorkletDependency.Template = class WorkletDependencyTemplate extends (
140
140
  // import.meta.url)` form (literal specifier, no runtime helpers) so other bundlers
141
141
  // and webpack itself can statically follow the worklet. The worklet chunk links its
142
142
  // splits via native `import`, so only the entry chunk is referenced.
143
- const analyzableSpecifier =
144
- runtimeTemplate.supportsAnalyzable("url", chunkGraph, module) &&
145
- entryChunk.id !== null
146
- ? runtimeTemplate._getAnalyzableChunkSpecifier(
147
- dep.options.publicPath,
148
- entryChunk,
149
- module,
150
- chunkGraph,
151
- runtimeRequirements
152
- )
153
- : null;
143
+ const analyzableSpecifier = runtimeTemplate.getAnalyzableWorkerUrl(
144
+ dep.options.publicPath,
145
+ entryChunk,
146
+ module,
147
+ chunkGraph,
148
+ runtimeRequirements
149
+ );
154
150
  if (analyzableSpecifier !== null) {
155
151
  source.replace(
156
152
  dep.callRange[0],
@@ -120,15 +120,12 @@ class ModuleChunkLoadingPlugin {
120
120
  if (!isEnabledForChunk(chunk)) return;
121
121
  // A baked url carries where the chunk is, so neither is read for it.
122
122
  // Asked as the runtime module asks, so the two always agree.
123
- if (
124
- compilation.runtimeTemplate.analyzableChunkScriptUrls(
125
- chunk,
126
- chunkGraph,
127
- set
128
- ) !== null
129
- ) {
130
- return;
131
- }
123
+ const baked = compilation.runtimeTemplate.analyzableChunkScriptUrls(
124
+ chunk,
125
+ chunkGraph,
126
+ set
127
+ );
128
+ if (baked !== null && baked.complete) return;
132
129
  set.add(RuntimeGlobals.publicPath);
133
130
  set.add(RuntimeGlobals.getChunkScriptFilename);
134
131
  });
@@ -129,7 +129,7 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
129
129
  chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasJs);
130
130
  // Under module output each hinted chunk is a known file, so the urls are written
131
131
  // out and read by id rather than built from the chunk id at runtime.
132
- const chunkUrls =
132
+ const bakedChunkUrls =
133
133
  withPrefetch || withPreload
134
134
  ? runtimeTemplate.analyzableChunkScriptUrls(
135
135
  chunk,
@@ -138,6 +138,12 @@ class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
138
138
  this
139
139
  )
140
140
  : null;
141
+ // A hint is best-effort, so an id an incomplete map lacks is not worth a per-id
142
+ // fallback — only a complete map replaces the runtime name lookup.
143
+ const chunkUrls =
144
+ bakedChunkUrls !== null && bakedChunkUrls.complete
145
+ ? bakedChunkUrls.urls
146
+ : null;
141
147
  const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
142
148
  const hasJsMatcher = compileBooleanMatcher(conditionMap);
143
149
  const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs);
@@ -1648,7 +1648,7 @@ class HtmlGenerator extends Generator {
1648
1648
  .replace(/-{2,}/g, (m) => `${"-".repeat(m.length - 1)} `);
1649
1649
  return new RawSource(`<!-- webpack error: ${safe} -->`);
1650
1650
  }
1651
- return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
1651
+ return new RawSource(Generator.throwBuildErrorCode(error));
1652
1652
  }
1653
1653
 
1654
1654
  /**
@@ -31,7 +31,7 @@ const preloadModuleType = require("../util/preloadModuleType");
31
31
  const {
32
32
  PUBLIC_PATH_AUTO: autoPlaceholder
33
33
  } = require("../util/publicPathPlaceholder");
34
- const removeBOM = require("../util/removeBOM");
34
+ const { removeBOMFromResult } = require("../util/removeBOM");
35
35
  const HtmlModule = require("./HtmlModule");
36
36
 
37
37
  const getMimeTypes = memoize(() => require("../util/mimeTypes"));
@@ -222,6 +222,9 @@ const isIntegrityEnabled = (integrity) =>
222
222
  // `\.html`/`\.css` request matchers for the synthetic `output.html` wrapper.
223
223
  const HTML_REQUEST_RE = /\.html(\?|$)/i;
224
224
  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;
225
228
 
226
229
  /**
227
230
  * Requests an `output.html` page must load for an entry: its `dependOn`
@@ -714,28 +717,64 @@ class HtmlModulesPlugin {
714
717
  // An inlined chunk file is dead weight once nothing else references
715
718
  // it by URL (another page's tag, or the runtime's async chunk map);
716
719
  // a content-hashed filename makes a substring hit a real reference.
720
+ /** @type {Set<string>} */
721
+ const deletionCandidates = new Set(inlinedFiles);
722
+ // A wrapper entry's chunk only rebuilds the page's own markup; the
723
+ // page loads the `<script src>` entries the parser split out of it.
724
+ for (const entrypoint of compilation.entrypoints.values()) {
725
+ const chunk = entrypoint.getEntrypointChunk();
726
+ if (!chunk) continue;
727
+ let wrapperOnly = false;
728
+ for (const entryModule of compilation.chunkGraph.getChunkEntryModulesIterable(
729
+ chunk
730
+ )) {
731
+ const { resource } = /** @type {import("../NormalModule")} */ (
732
+ entryModule
733
+ );
734
+ wrapperOnly =
735
+ typeof resource === "string" &&
736
+ HTML_DATA_URL_RE.test(resource);
737
+ if (!wrapperOnly) break;
738
+ }
739
+ if (!wrapperOnly) continue;
740
+ for (const file of chunk.files) deletionCandidates.add(file);
741
+ }
717
742
  // 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) {
743
+ // render) and test all candidates against it, rather than
744
+ // re-materializing every asset per candidate.
745
+ if (deletionCandidates.size > 0) {
721
746
  /** @type {Set<string>} */
722
747
  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
- }
748
+ /** @type {string[]} */
749
+ const pending = [];
750
+ /**
751
+ * @param {string} name asset whose source may name a candidate
752
+ */
753
+ const markReferenced = (name) => {
730
754
  const source = compilation.assets[name].source();
731
- if (typeof source !== "string") continue;
732
- for (const file of inlinedFiles) {
755
+ if (typeof source !== "string") return;
756
+ for (const file of deletionCandidates) {
733
757
  if (!stillReferenced.has(file) && source.includes(file)) {
734
758
  stillReferenced.add(file);
759
+ pending.push(file);
735
760
  }
736
761
  }
762
+ };
763
+ for (const name of Object.keys(compilation.assets)) {
764
+ if (
765
+ deletionCandidates.has(name) ||
766
+ stillReferenced.size === deletionCandidates.size
767
+ ) {
768
+ continue;
769
+ }
770
+ markReferenced(name);
771
+ }
772
+ // A kept candidate can reference another through its async chunk
773
+ // map, so follow each until nothing new is kept.
774
+ while (pending.length > 0) {
775
+ markReferenced(/** @type {string} */ (pending.pop()));
737
776
  }
738
- for (const file of inlinedFiles) {
777
+ for (const file of deletionCandidates) {
739
778
  if (!stillReferenced.has(file)) compilation.deleteAsset(file);
740
779
  }
741
780
  }
@@ -896,14 +935,14 @@ class HtmlModulesPlugin {
896
935
  PLUGIN_NAME,
897
936
  (result, module) => {
898
937
  if (module.type === HTML_MODULE_TYPE) {
899
- const [source, ...rest] = result;
938
+ const [source, ...rest] = removeBOMFromResult(result);
900
939
  // `applyTemplate` is a no-op unless `module.parser.html.template`
901
940
  // is set. Running it here (where the returned source becomes the
902
941
  // module's stored source) keeps the parser's dependency offsets
903
942
  // and the generator's render base in sync.
904
943
  const parser = /** @type {HtmlParser} */ (module.parser);
905
944
 
906
- return [parser.applyTemplate(removeBOM(source), module), ...rest];
945
+ return [parser.applyTemplate(source, module), ...rest];
907
946
  }
908
947
 
909
948
  return result;