webpack 5.107.0 → 5.107.1

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 (47) hide show
  1. package/lib/BannerPlugin.js +3 -4
  2. package/lib/Chunk.js +21 -25
  3. package/lib/ChunkGroup.js +57 -15
  4. package/lib/Compilation.js +33 -11
  5. package/lib/EvalSourceMapDevToolPlugin.js +0 -1
  6. package/lib/ExportsInfo.js +30 -34
  7. package/lib/ExternalModule.js +15 -11
  8. package/lib/ExternalModuleFactoryPlugin.js +2 -1
  9. package/lib/Module.js +1 -1
  10. package/lib/ModuleNotFoundError.js +10 -0
  11. package/lib/ModuleSourceTypeConstants.js +24 -22
  12. package/lib/NormalModule.js +106 -46
  13. package/lib/NormalModuleFactory.js +38 -26
  14. package/lib/RuntimePlugin.js +1 -1
  15. package/lib/SourceMapDevToolPlugin.js +250 -49
  16. package/lib/Template.js +1 -1
  17. package/lib/TemplatedPathPlugin.js +22 -4
  18. package/lib/asset/AssetBytesGenerator.js +6 -6
  19. package/lib/asset/AssetGenerator.js +14 -14
  20. package/lib/asset/AssetModulesPlugin.js +3 -7
  21. package/lib/asset/AssetSourceGenerator.js +6 -6
  22. package/lib/css/CssModulesPlugin.js +2 -2
  23. package/lib/dependencies/CommonJsImportsParserPlugin.js +108 -1
  24. package/lib/dependencies/CssUrlDependency.js +3 -2
  25. package/lib/dependencies/HarmonyDetectionParserPlugin.js +21 -1
  26. package/lib/dependencies/HtmlScriptSrcDependency.js +264 -25
  27. package/lib/dependencies/HtmlSourceDependency.js +3 -2
  28. package/lib/html/HtmlModulesPlugin.js +1 -5
  29. package/lib/html/walkHtmlTokens.js +641 -125
  30. package/lib/index.js +2 -0
  31. package/lib/javascript/JavascriptModulesPlugin.js +2 -2
  32. package/lib/optimize/SideEffectsFlagPlugin.js +1 -2
  33. package/lib/optimize/SplitChunksPlugin.js +4 -4
  34. package/lib/runtime/AutoPublicPathRuntimeModule.js +3 -3
  35. package/lib/runtime/GetChunkFilenameRuntimeModule.js +5 -5
  36. package/lib/sharing/ConsumeSharedPlugin.js +2 -8
  37. package/lib/sharing/ProvideSharedPlugin.js +4 -4
  38. package/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js +1 -2
  39. package/package.json +3 -3
  40. package/schemas/WebpackOptions.check.js +1 -1
  41. package/schemas/WebpackOptions.json +11 -9
  42. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  43. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  44. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  45. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  46. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  47. package/types.d.ts +355 -144
@@ -9,11 +9,11 @@ const { RawSource } = require("webpack-sources");
9
9
  const ConcatenationScope = require("../ConcatenationScope");
10
10
  const Generator = require("../Generator");
11
11
  const {
12
+ ASSET_URL_TYPE,
13
+ ASSET_URL_TYPES,
12
14
  CSS_TYPE,
13
- CSS_URL_TYPE,
14
- CSS_URL_TYPES,
15
15
  HTML_TYPE,
16
- JAVASCRIPT_AND_CSS_URL_TYPES,
16
+ JAVASCRIPT_AND_ASSET_URL_TYPES,
17
17
  JAVASCRIPT_TYPE,
18
18
  JAVASCRIPT_TYPES,
19
19
  NO_TYPES
@@ -79,7 +79,7 @@ class AssetSourceGenerator extends Generator {
79
79
  }
80
80
  return new RawSource(sourceContent);
81
81
  }
82
- case CSS_URL_TYPE: {
82
+ case ASSET_URL_TYPE: {
83
83
  if (!originalSource) {
84
84
  return null;
85
85
  }
@@ -150,9 +150,9 @@ class AssetSourceGenerator extends Generator {
150
150
  sourceTypes.has(JAVASCRIPT_TYPE) &&
151
151
  (sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE))
152
152
  ) {
153
- return JAVASCRIPT_AND_CSS_URL_TYPES;
153
+ return JAVASCRIPT_AND_ASSET_URL_TYPES;
154
154
  } else if (sourceTypes.has(CSS_TYPE) || sourceTypes.has(HTML_TYPE)) {
155
- return CSS_URL_TYPES;
155
+ return ASSET_URL_TYPES;
156
156
  }
157
157
  return JAVASCRIPT_TYPES;
158
158
  }
@@ -58,7 +58,7 @@ const CssParser = require("./CssParser");
58
58
  /** @typedef {import("../Module").BuildInfo} BuildInfo */
59
59
  /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
60
60
  /** @typedef {import("../Template").RuntimeTemplate} RuntimeTemplate */
61
- /** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */
61
+ /** @typedef {import("../Chunk").ChunkFilenameTemplate} ChunkFilenameTemplate */
62
62
  /** @typedef {import("../util/Hash")} Hash */
63
63
  /** @typedef {import("../Module").BuildMeta} BuildMeta */
64
64
 
@@ -1152,7 +1152,7 @@ class CssModulesPlugin {
1152
1152
  * Gets chunk filename template.
1153
1153
  * @param {Chunk} chunk chunk
1154
1154
  * @param {OutputOptions} outputOptions output options
1155
- * @returns {TemplatePath} used filename template
1155
+ * @returns {ChunkFilenameTemplate} used filename template
1156
1156
  */
1157
1157
  static getChunkFilenameTemplate(chunk, outputOptions) {
1158
1158
  if (chunk.cssFilenameTemplate) {
@@ -48,6 +48,22 @@ const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency
48
48
  * @property {string} context
49
49
  */
50
50
 
51
+ /**
52
+ * Per-`const NAME = require(LITERAL)` binding state used to forward
53
+ * member-access references on `NAME` to the `CommonJsRequireDependency`
54
+ * created for the `require()` call.
55
+ * @typedef {object} RequireBindingData
56
+ * @property {RawReferencedExports} referencedExports mutable list shared with the dependency; pushed to as `NAME.x.y` accesses are walked
57
+ * @property {InstanceType<typeof import("./CommonJsRequireDependency")> | null} dep dependency for the `require()` call (assigned during walk)
58
+ */
59
+
60
+ /** @type {WeakMap<CallExpression, RequireBindingData>} */
61
+ const requireBindingData = new WeakMap();
62
+
63
+ const REQUIRE_BINDING_TAG = Symbol(
64
+ "CommonJsImportsParserPlugin require binding"
65
+ );
66
+
51
67
  const PLUGIN_NAME = "CommonJsImportsParserPlugin";
52
68
 
53
69
  /**
@@ -152,10 +168,18 @@ const createRequireCallHandler = (parser, options, getContext) => {
152
168
  */
153
169
  const processRequireItem = (expr, param) => {
154
170
  if (param.isString()) {
155
- const referencedExports = getRequireReferencedExportsFromDestructuring(
171
+ let referencedExports = getRequireReferencedExportsFromDestructuring(
156
172
  parser,
157
173
  expr
158
174
  );
175
+ const binding = requireBindingData.get(
176
+ /** @type {CallExpression} */ (expr)
177
+ );
178
+ if (binding && !referencedExports) {
179
+ // `const NAME = require(LITERAL)` — let later member-access walks
180
+ // on `NAME` populate the dependency's referenced exports.
181
+ referencedExports = binding.referencedExports;
182
+ }
159
183
  const dep = new CommonJsRequireDependency(
160
184
  /** @type {string} */ (param.string),
161
185
  /** @type {Range} */ (param.range),
@@ -163,6 +187,7 @@ const createRequireCallHandler = (parser, options, getContext) => {
163
187
  referencedExports,
164
188
  /** @type {Range} */ (expr.range)
165
189
  );
190
+ if (binding) binding.dep = dep;
166
191
  dep.loc = /** @type {DependencyLocation} */ (expr.loc);
167
192
  dep.optional = Boolean(parser.scope.inTry);
168
193
  parser.state.current.addDependency(dep);
@@ -662,6 +687,88 @@ class CommonJsImportsParserPlugin {
662
687
  .tap(PLUGIN_NAME, callChainHandler);
663
688
  // #endregion
664
689
 
690
+ // #region Require bound to a const variable
691
+ // Track `const NAME = require(LITERAL)` so that static member accesses on
692
+ // `NAME` (e.g. `NAME.foo`, `NAME.foo()`) are forwarded to the same
693
+ // `CommonJsRequireDependency` as referenced exports — enabling tree
694
+ // shaking of CommonJS modules that are imported into a named binding
695
+ // rather than destructured.
696
+ parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator, statement) => {
697
+ if (statement.kind !== "const") return;
698
+ if (declarator.id.type !== "Identifier") return;
699
+ if (!declarator.init || declarator.init.type !== "CallExpression") {
700
+ return;
701
+ }
702
+ const init = declarator.init;
703
+ if (
704
+ init.callee.type !== "Identifier" ||
705
+ init.callee.name !== "require" ||
706
+ init.arguments.length !== 1
707
+ ) {
708
+ return;
709
+ }
710
+ const arg = init.arguments[0];
711
+ if (arg.type !== "Literal" || typeof arg.value !== "string") return;
712
+ // Only attach binding state when `require` resolves to the free
713
+ // `require` (i.e. it isn't shadowed in the current scope).
714
+ const requireInfo = parser.getFreeInfoFromVariable("require");
715
+ if (!requireInfo || requireInfo.name !== "require") return;
716
+ /** @type {RequireBindingData} */
717
+ const binding = {
718
+ referencedExports: [],
719
+ dep: null
720
+ };
721
+ requireBindingData.set(init, binding);
722
+ parser.tagVariable(declarator.id.name, REQUIRE_BINDING_TAG, binding);
723
+ return true;
724
+ });
725
+
726
+ parser.hooks.expression.for(REQUIRE_BINDING_TAG).tap(PLUGIN_NAME, () => {
727
+ const binding =
728
+ /** @type {RequireBindingData} */
729
+ (parser.currentTagData);
730
+ if (binding && binding.dep) {
731
+ // `NAME` is read as a value (not as the object of a static member
732
+ // chain), so we have to assume the whole exports object is used.
733
+ binding.dep.referencedExports = null;
734
+ }
735
+ });
736
+
737
+ parser.hooks.expressionMemberChain
738
+ .for(REQUIRE_BINDING_TAG)
739
+ .tap(PLUGIN_NAME, (_expr, members) => {
740
+ const binding =
741
+ /** @type {RequireBindingData} */
742
+ (parser.currentTagData);
743
+ if (binding && binding.dep && binding.dep.referencedExports) {
744
+ binding.dep.referencedExports.push(members);
745
+ }
746
+ // Returning truthy suppresses the parser's fallback chain (which
747
+ // would otherwise walk `NAME` as a bare expression and trigger our
748
+ // `expression` hook above, marking the whole namespace as used).
749
+ return true;
750
+ });
751
+
752
+ parser.hooks.callMemberChain
753
+ .for(REQUIRE_BINDING_TAG)
754
+ .tap(PLUGIN_NAME, (expr, members) => {
755
+ const binding =
756
+ /** @type {RequireBindingData} */
757
+ (parser.currentTagData);
758
+ if (binding && binding.dep && binding.dep.referencedExports) {
759
+ if (members.length === 0) {
760
+ // `NAME(...)` — calling the require result directly; the
761
+ // whole exports object is observable.
762
+ binding.dep.referencedExports = null;
763
+ } else {
764
+ binding.dep.referencedExports.push(members);
765
+ }
766
+ }
767
+ if (expr.arguments) parser.walkExpressions(expr.arguments);
768
+ return true;
769
+ });
770
+ // #endregion
771
+
665
772
  // #region Require.resolve
666
773
  /**
667
774
  * Processes the provided expr.
@@ -5,6 +5,7 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const { ASSET_URL_TYPE } = require("../ModuleSourceTypeConstants");
8
9
  const RawDataUrlModule = require("../asset/RawDataUrlModule");
9
10
  const makeSerializable = require("../util/makeSerializable");
10
11
  const memoize = require("../util/memoize");
@@ -208,8 +209,8 @@ CssUrlDependency.Template = class CssUrlDependencyTemplate extends (
208
209
  const data = codeGen.data;
209
210
  if (!data) return "data:,";
210
211
  const url = data.get("url");
211
- if (!url || !url["css-url"]) return "data:,";
212
- return url["css-url"];
212
+ if (!url || !url[ASSET_URL_TYPE]) return "data:,";
213
+ return url[ASSET_URL_TYPE];
213
214
  }
214
215
  };
215
216
 
@@ -92,6 +92,19 @@ module.exports = class HarmonyDetectionParserPlugin {
92
92
  }
93
93
  };
94
94
 
95
+ /**
96
+ * Walks call arguments so import bindings used inside callbacks are
97
+ * still tracked, then skips default AMD/CommonJS handling.
98
+ * @param {import("estree").CallExpression} expr call expression
99
+ * @returns {boolean | undefined} true if in harmony
100
+ */
101
+ const walkArgumentsAndSkipInHarmony = (expr) => {
102
+ if (HarmonyExports.isEnabled(parser.state)) {
103
+ if (expr.arguments) parser.walkExpressions(expr.arguments);
104
+ return true;
105
+ }
106
+ };
107
+
95
108
  const nonHarmonyIdentifiers = ["define", "exports"];
96
109
  for (const identifier of nonHarmonyIdentifiers) {
97
110
  parser.hooks.evaluateTypeof
@@ -100,7 +113,14 @@ module.exports = class HarmonyDetectionParserPlugin {
100
113
  parser.hooks.typeof.for(identifier).tap(PLUGIN_NAME, skipInHarmony);
101
114
  parser.hooks.evaluate.for(identifier).tap(PLUGIN_NAME, nullInHarmony);
102
115
  parser.hooks.expression.for(identifier).tap(PLUGIN_NAME, skipInHarmony);
103
- parser.hooks.call.for(identifier).tap(PLUGIN_NAME, skipInHarmony);
116
+ parser.hooks.call
117
+ .for(identifier)
118
+ .tap(
119
+ PLUGIN_NAME,
120
+ identifier === "define"
121
+ ? walkArgumentsAndSkipInHarmony
122
+ : skipInHarmony
123
+ );
104
124
  }
105
125
  }
106
126
  };
@@ -4,12 +4,18 @@
4
4
 
5
5
  "use strict";
6
6
 
7
+ const {
8
+ CSS_IMPORT_TYPE,
9
+ CSS_TYPE,
10
+ JAVASCRIPT_TYPE
11
+ } = require("../ModuleSourceTypeConstants");
7
12
  const makeSerializable = require("../util/makeSerializable");
8
13
  const CssUrlDependency = require("./CssUrlDependency");
9
14
  const ModuleDependency = require("./ModuleDependency");
10
15
 
11
16
  /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
12
17
  /** @typedef {import("../Chunk")} Chunk */
18
+ /** @typedef {import("../ChunkGraph")} ChunkGraph */
13
19
  /** @typedef {import("../Dependency")} Dependency */
14
20
  /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
15
21
  /** @typedef {import("../Entrypoint")} Entrypoint */
@@ -184,6 +190,144 @@ const getEntrypointChunksInLoadOrder = (entrypoint) => {
184
190
  return ordered;
185
191
  };
186
192
 
193
+ /**
194
+ * Whether webpack will emit a `.js` file for this chunk that must be
195
+ * loaded with a `<script>` tag. Covers three independent reasons a
196
+ * chunk needs JS output: it owns one or more JS-source-type modules;
197
+ * it has entry modules whose source types include JavaScript (entry
198
+ * modules don't show up in `getChunkModulesIterableBySourceType` until
199
+ * they're connected as regular modules — this is why
200
+ * `JavascriptModulesPlugin#_chunkHasJs` checks them separately); or it
201
+ * is a runtime chunk — `chunk.hasRuntime()` — which produces a `.js`
202
+ * file holding the webpack runtime, but its `RuntimeModule`s live in
203
+ * a separate `runtimeModules` set and are *not* surfaced via
204
+ * `getChunkModulesIterableBySourceType`. Missing the runtime case
205
+ * would cause a `runtimeChunk`-split chunk to fall out of the
206
+ * `<script>` list and re-emerge after the chunks that depend on it,
207
+ * producing `__webpack_require__ is not defined` at load time.
208
+ * @param {Chunk} chunk chunk
209
+ * @param {ChunkGraph} chunkGraph chunk graph
210
+ * @returns {boolean} true if the chunk emits a `.js` file
211
+ */
212
+ const chunkHasJs = (chunk, chunkGraph) => {
213
+ if (chunk.hasRuntime()) return true;
214
+ if (chunkGraph.getNumberOfEntryModules(chunk) > 0) {
215
+ for (const module of chunkGraph.getChunkEntryModulesIterable(chunk)) {
216
+ if (chunkGraph.getModuleSourceTypes(module).has(JAVASCRIPT_TYPE)) {
217
+ return true;
218
+ }
219
+ }
220
+ }
221
+ return Boolean(
222
+ chunkGraph.getChunkModulesIterableBySourceType(chunk, JAVASCRIPT_TYPE)
223
+ );
224
+ };
225
+
226
+ /**
227
+ * Whether webpack will emit a `.css` file for this chunk that must be
228
+ * loaded with a `<link rel="stylesheet">` tag. Matches
229
+ * `CssModulesPlugin.chunkHasCss` exactly — both regular CSS modules
230
+ * and pure `@import` placeholder modules count, since the latter
231
+ * still contribute a `.css` asset to the chunk.
232
+ * @param {Chunk} chunk chunk
233
+ * @param {ChunkGraph} chunkGraph chunk graph
234
+ * @returns {boolean} true if the chunk emits a `.css` file
235
+ */
236
+ const chunkHasCss = (chunk, chunkGraph) =>
237
+ Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_TYPE)) ||
238
+ Boolean(
239
+ chunkGraph.getChunkModulesIterableBySourceType(chunk, CSS_IMPORT_TYPE)
240
+ );
241
+
242
+ /**
243
+ * Compare two chunks for a deterministic tie-break in CSS link ordering.
244
+ * `chunk.name` and `chunk.id` are both stable strings (when present);
245
+ * one of them is set for every chunk webpack emits. We can't rely on
246
+ * `Array.prototype.sort` being stable — webpack still supports Node
247
+ * 10.13 where V8's sort is not guaranteed stable for arrays larger
248
+ * than ten elements — so any time `firstCssModulePostOrderIndex`
249
+ * returns the same value for two chunks (most commonly when several
250
+ * chunks have no reachable CSS module in the entrypoint's dependency
251
+ * walk and all map to `Infinity`) this comparator picks the canonical
252
+ * order.
253
+ * @param {Chunk} a first chunk
254
+ * @param {Chunk} b second chunk
255
+ * @returns {-1 | 0 | 1} ordering
256
+ */
257
+ const compareChunksForCssTieBreak = (a, b) => {
258
+ const an = `${a.name || ""} ${a.id === null || a.id === undefined ? "" : a.id}`;
259
+ const bn = `${b.name || ""} ${b.id === null || b.id === undefined ? "" : b.id}`;
260
+ if (an < bn) return -1;
261
+ if (an > bn) return 1;
262
+ return 0;
263
+ };
264
+
265
+ /**
266
+ * Smallest post-order index among the CSS modules of a chunk, taken
267
+ * from the entrypoint's view of the dependency graph. Used to sort
268
+ * sibling CSS chunks so they appear in source import order in the
269
+ * extracted HTML — `entrypoint.chunks` itself does not give that
270
+ * ordering for arbitrary splitChunks layouts. Considers both
271
+ * `CSS_TYPE` and `CSS_IMPORT_TYPE` modules so a chunk made up
272
+ * exclusively of `@import` placeholder modules (e.g. when splitChunks
273
+ * separates them from their target CSS) still sorts by its true
274
+ * source position rather than collapsing to `Infinity` and relying on
275
+ * the chunk-name tie-breaker.
276
+ * @param {Chunk} chunk chunk
277
+ * @param {Entrypoint} entrypoint entrypoint the chunk belongs to
278
+ * @param {ChunkGraph} chunkGraph chunk graph
279
+ * @returns {number} the lowest post-order index of any CSS or
280
+ * CSS-import module in the chunk, or `Number.POSITIVE_INFINITY` when
281
+ * no such module has a defined index (e.g. for a module the
282
+ * entrypoint never reached on its own dependency walk — runtime-only
283
+ * modules, modules reached via `dependOn`, etc.) so such chunks sort
284
+ * last among CSS chunks
285
+ */
286
+ const firstCssModulePostOrderIndex = (chunk, entrypoint, chunkGraph) => {
287
+ let min = Number.POSITIVE_INFINITY;
288
+ for (const sourceType of [CSS_TYPE, CSS_IMPORT_TYPE]) {
289
+ const modules = chunkGraph.getChunkModulesIterableBySourceType(
290
+ chunk,
291
+ sourceType
292
+ );
293
+ if (!modules) continue;
294
+ for (const module of modules) {
295
+ const idx = entrypoint.getModulePostOrderIndex(module);
296
+ if (idx !== undefined && idx < min) min = idx;
297
+ }
298
+ }
299
+ return min;
300
+ };
301
+
302
+ const COPYABLE_LINK_ATTRS = ["nonce", "crossorigin", "referrerpolicy"];
303
+
304
+ /**
305
+ * Build a fresh `<link rel="stylesheet" href="…">` for a CSS chunk that
306
+ * was pulled in by a `<script src>` entry — the originating tag was a
307
+ * `<script>`, but the chunk is CSS so cloning the script tag verbatim
308
+ * would produce nonsense (`<script src="…\.css">`). Copy
309
+ * `nonce`/`crossorigin`/`referrerpolicy` from the original element so
310
+ * the same CSP and fetch policy applies; `defer`/`async`/`type` have no
311
+ * meaning on `<link>` and are dropped.
312
+ * @param {string} originalTag the originating `<script>`/`<link>` tag's source text
313
+ * @param {string} href URL for the stylesheet
314
+ * @returns {string} the sibling `<link>` tag's HTML
315
+ */
316
+ const buildStylesheetLink = (originalTag, href) => {
317
+ let extra = "";
318
+ for (const attr of COPYABLE_LINK_ATTRS) {
319
+ // Match ` <attr>`, ` <attr>=value`, ` <attr>="value"`, ` <attr>='value'`.
320
+ const re = new RegExp(
321
+ `\\s${attr}(?:\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>]+))?(?=[\\s/>])`,
322
+ "i"
323
+ );
324
+ const m = originalTag.match(re);
325
+ if (m) extra += m[0];
326
+ }
327
+ const safeHref = href.replace(/"/g, "&quot;");
328
+ return `<link rel="stylesheet" href="${safeHref}"${extra}>`;
329
+ };
330
+
187
331
  /**
188
332
  * Clone the original `<script>`/`<link>` opening tag with its `src`/`href`
189
333
  * value swapped for a different chunk URL. Reusing the source text verbatim
@@ -252,6 +396,7 @@ HtmlScriptSrcDependency.Template = class HtmlScriptSrcDependencyTemplate extends
252
396
  const { runtimeTemplate } = templateContext;
253
397
  const dep = /** @type {HtmlScriptSrcDependency} */ (dependency);
254
398
  const compilation = runtimeTemplate.compilation;
399
+ const { chunkGraph } = compilation;
255
400
  const entrypoint = /** @type {Entrypoint | undefined} */ (
256
401
  compilation.entrypoints.get(dep.entryName)
257
402
  );
@@ -263,50 +408,144 @@ HtmlScriptSrcDependency.Template = class HtmlScriptSrcDependencyTemplate extends
263
408
 
264
409
  const orderedChunks = getEntrypointChunksInLoadOrder(entrypoint);
265
410
  const entryChunk = orderedChunks[orderedChunks.length - 1];
266
- const contentHashType =
267
- dep.elementKind === "stylesheet" ? "css" : "javascript";
411
+ const isStylesheet = dep.elementKind === "stylesheet";
412
+
413
+ // Rewrite the originating tag's src/href to the entry chunk's
414
+ // primary asset for that element kind: `.css` for
415
+ // `<link rel="stylesheet">`, `.js` for everything else.
416
+ const entryContentHashType = isStylesheet ? "css" : "javascript";
268
417
  const entryUrl = `${CssUrlDependency.PUBLIC_PATH_AUTO}${getChunkFilename(
269
418
  entryChunk,
270
419
  compilation,
271
- contentHashType
420
+ entryContentHashType
272
421
  )}`;
273
422
  source.replace(dep.range[0], dep.range[1] - 1, entryUrl);
274
423
 
275
- if (
276
- orderedChunks.length <= 1 ||
277
- dep.tagStart < 0 ||
278
- dep.tagOpenEnd <= dep.tagStart
279
- ) {
424
+ if (dep.tagStart < 0 || dep.tagOpenEnd <= dep.tagStart) {
280
425
  return;
281
426
  }
282
427
 
283
- // The browser must load every chunk in dependency order, not just the
284
- // entry chunk. Clone the original tag for each non-entry chunk so the
285
- // preserved attributes (nonce, crossorigin, …) match the entry tag,
286
- // and insert the clones before the original tag's `<`.
428
+ // The browser must load every chunk the entry needs, not just the
429
+ // entry chunk. For `<script>` entries that's the JS for sibling
430
+ // chunks plus critically the CSS for any chunk that holds
431
+ // stylesheets imported transitively from the JS source. Previously
432
+ // every sibling was cloned as a `<script>` pointing at a `.js`
433
+ // filename, so CSS chunks ended up as `<script src="foo.css">`
434
+ // pointing at non-existent `.js` files (the bug in
435
+ // html-webpack-plugin#1838 / webpack/mini-css-extract-plugin#959,
436
+ // magnified here because the entry chunk's own CSS was emitted to
437
+ // disk but never linked from the HTML at all).
287
438
  const originalContent = /** @type {string} */ (source.original().source());
288
439
  const originalTag = originalContent.slice(dep.tagStart, dep.tagOpenEnd);
289
440
  const srcStartInTag = dep.range[0] - dep.tagStart;
290
441
  const srcEndInTag = dep.range[1] - dep.tagStart;
291
442
 
292
- const siblings = [];
293
- for (let i = 0; i < orderedChunks.length - 1; i++) {
443
+ /**
444
+ * @param {Chunk} chunk chunk to emit a sibling tag for
445
+ * @param {"javascript" | "css"} kind content type slice of the chunk to emit
446
+ * @returns {string} a single sibling tag's HTML
447
+ */
448
+ const buildSibling = (chunk, kind) => {
294
449
  const url = `${CssUrlDependency.PUBLIC_PATH_AUTO}${getChunkFilename(
295
- orderedChunks[i],
450
+ chunk,
296
451
  compilation,
297
- contentHashType
452
+ kind
298
453
  )}`;
299
- siblings.push(
300
- cloneTagWithUrl(
301
- originalTag,
302
- srcStartInTag,
303
- srcEndInTag,
304
- url,
305
- dep.elementKind
306
- )
454
+ if (kind === "css" && !isStylesheet) {
455
+ // Originating tag is `<script>` (or `<link rel=modulepreload>`)
456
+ // but this chunk is CSS — emit a fresh `<link>` rather than
457
+ // cloning the script.
458
+ return buildStylesheetLink(originalTag, url);
459
+ }
460
+ return cloneTagWithUrl(
461
+ originalTag,
462
+ srcStartInTag,
463
+ srcEndInTag,
464
+ url,
465
+ dep.elementKind
307
466
  );
467
+ };
468
+
469
+ const siblings = [];
470
+
471
+ if (isStylesheet) {
472
+ // `<link rel="stylesheet">` entries are CSS-only — every sibling
473
+ // chunk in the entrypoint is also CSS. Keep cloning the original
474
+ // `<link>` for them so attributes like `media` carry over.
475
+ for (let i = 0; i < orderedChunks.length - 1; i++) {
476
+ siblings.push(buildSibling(orderedChunks[i], "css"));
477
+ }
478
+ } else {
479
+ // CSS chunks are emitted before JS chunks so the cascade is set
480
+ // up before any script runs. Within CSS the order needs to match
481
+ // the source's import order — `entrypoint.chunks` alone doesn't
482
+ // give us that for arbitrary splitChunks layouts (splitChunks
483
+ // inserts each new chunk before the entry chunk via
484
+ // `insertChunk(_, before)`, so split CSS siblings end up in
485
+ // *reverse* of the order they were processed — exactly the
486
+ // html-webpack-plugin#1838 / mini-css-extract#959 symptom). We
487
+ // re-derive the order from the entrypoint's module post-order
488
+ // index, which mirrors the dependency walk and so reflects the
489
+ // import order.
490
+ /** @type {{ chunk: Chunk, index: number }[]} */
491
+ const cssChunkOrder = [];
492
+ /** @type {Chunk[]} */
493
+ const jsChunks = [];
494
+ for (let i = 0; i < orderedChunks.length - 1; i++) {
495
+ const chunk = orderedChunks[i];
496
+ const hasCss = chunkHasCss(chunk, chunkGraph);
497
+ const hasJs = chunkHasJs(chunk, chunkGraph);
498
+ if (hasCss) {
499
+ cssChunkOrder.push({
500
+ chunk,
501
+ index: firstCssModulePostOrderIndex(chunk, entrypoint, chunkGraph)
502
+ });
503
+ }
504
+ // Anything that isn't CSS-only stays on the JS lane, in the
505
+ // `orderedChunks` order — that preserves the runtime-first /
506
+ // vendor-before-entry invariant of `getEntrypointChunksInLoadOrder`.
507
+ // Chunks that produce no `.js` and no `.css` (e.g. wasm-only
508
+ // or asset-only) still get a `<script>` clone here so we
509
+ // keep prior behavior for users who relied on it.
510
+ if (hasJs || !hasCss) jsChunks.push(chunk);
511
+ }
512
+ // If the entry chunk itself contains CSS (entry JS imports CSS
513
+ // without splitChunks separating it), fold it into the same CSS
514
+ // ordering so the entry-chunk `<link>` lands in the correct
515
+ // cascade position relative to sibling CSS chunks.
516
+ if (chunkHasCss(entryChunk, chunkGraph)) {
517
+ cssChunkOrder.push({
518
+ chunk: entryChunk,
519
+ index: firstCssModulePostOrderIndex(
520
+ entryChunk,
521
+ entrypoint,
522
+ chunkGraph
523
+ )
524
+ });
525
+ }
526
+ cssChunkOrder.sort((a, b) => {
527
+ // Direct subtraction would yield `NaN` when both indices are
528
+ // `Infinity` (the documented fallback for chunks whose CSS
529
+ // modules the entrypoint's walk never reaches), and
530
+ // `Array#sort` doesn't promise stable ordering on the legacy
531
+ // Node 10 targets this repo still supports — so the
532
+ // tie-breaker must always run when the indices match,
533
+ // including the `Infinity === Infinity` case.
534
+ if (a.index < b.index) return -1;
535
+ if (a.index > b.index) return 1;
536
+ return compareChunksForCssTieBreak(a.chunk, b.chunk);
537
+ });
538
+ for (const { chunk } of cssChunkOrder) {
539
+ siblings.push(buildSibling(chunk, "css"));
540
+ }
541
+ for (const chunk of jsChunks) {
542
+ siblings.push(buildSibling(chunk, "javascript"));
543
+ }
544
+ }
545
+
546
+ if (siblings.length > 0) {
547
+ source.insert(dep.tagStart, siblings.join(""));
308
548
  }
309
- source.insert(dep.tagStart, siblings.join(""));
310
549
  }
311
550
  };
312
551
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const { ASSET_URL_TYPE } = require("../ModuleSourceTypeConstants");
8
9
  const RawDataUrlModule = require("../asset/RawDataUrlModule");
9
10
  const makeSerializable = require("../util/makeSerializable");
10
11
  const memoize = require("../util/memoize");
@@ -114,8 +115,8 @@ HtmlSourceDependency.Template = class HtmlSourceDependencyTemplate extends (
114
115
  const data = codeGen.data;
115
116
  if (!data) return "data:,";
116
117
  const url = data.get("url");
117
- if (!url || !url["css-url"]) return "data:,";
118
- return url["css-url"];
118
+ if (!url || !url[ASSET_URL_TYPE]) return "data:,";
119
+ return url[ASSET_URL_TYPE];
119
120
  }
120
121
  };
121
122
 
@@ -255,11 +255,7 @@ class HtmlModulesPlugin {
255
255
  options
256
256
  )
257
257
  );
258
- return new HtmlGenerator(
259
- /** @type {import("../../declarations/WebpackOptions").HtmlGeneratorOptions} */
260
- (generatorOptions),
261
- compilation.moduleGraph
262
- );
258
+ return new HtmlGenerator(generatorOptions, compilation.moduleGraph);
263
259
  });
264
260
 
265
261
  NormalModule.getCompilationHooks(compilation).processResult.tap(