webpack 5.108.2 → 5.108.4

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.
@@ -1394,10 +1394,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
1394
1394
  this._unsafeCachePredicate =
1395
1395
  typeof unsafeCache === "function" ? unsafeCache : () => true;
1396
1396
 
1397
- /**
1398
- * @private
1399
- * @type {LazyBarrelController}
1400
- */
1397
+ /** @type {LazyBarrelController} */
1401
1398
  this._lazyBarrelController = new LazyBarrelController(this);
1402
1399
  }
1403
1400
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  const asyncLib = require("neo-async");
9
9
  const Queue = require("./util/Queue");
10
+ const createHash = require("./util/createHash");
10
11
 
11
12
  /** @typedef {import("./Compiler")} Compiler */
12
13
  /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
@@ -16,12 +17,32 @@ const Queue = require("./util/Queue");
16
17
  /** @typedef {import("./ExportsInfo")} ExportsInfo */
17
18
  /** @typedef {import("./ExportsInfo").ExportInfoName} ExportInfoName */
18
19
  /** @typedef {import("./ExportsInfo").RestoreProvidedData} RestoreProvidedData */
20
+ /** @typedef {import("../declarations/WebpackOptions").HashFunction} HashFunction */
21
+ /** @typedef {import("./LazyBarrel")} LazyBarrelController */
19
22
  /** @typedef {import("./Module")} Module */
20
23
  /** @typedef {import("./Module").BuildInfo} BuildInfo */
21
24
 
22
25
  const PLUGIN_NAME = "FlagDependencyExportsPlugin";
23
26
  const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`;
24
27
 
28
+ /** @typedef {{ etag: string, data: RestoreProvidedData }} MemCacheEntry */
29
+
30
+ /**
31
+ * @param {Module} module the module
32
+ * @param {LazyBarrelController} lazyBarrelController the compilation's lazy barrel controller
33
+ * @param {HashFunction} hashFunction hash function for long keys
34
+ * @returns {string} the hash
35
+ */
36
+ const getLazyRequestsHash = (module, lazyBarrelController, hashFunction) => {
37
+ const lazyKeys = lazyBarrelController.getLazyRequests(module);
38
+ if (!lazyKeys) return "";
39
+ const joined = [...lazyKeys].join("|");
40
+ if (joined.length < 100) return joined;
41
+ const hash = createHash(hashFunction);
42
+ hash.update(joined);
43
+ return /** @type {string} */ (hash.digest("hex"));
44
+ };
45
+
25
46
  class FlagDependencyExportsPlugin {
26
47
  /**
27
48
  * Applies the plugin by registering its hooks on the compiler.
@@ -32,6 +53,8 @@ class FlagDependencyExportsPlugin {
32
53
  compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
33
54
  const moduleGraph = compilation.moduleGraph;
34
55
  const cache = compilation.getCache(PLUGIN_NAME);
56
+ const lazyBarrelController = compilation._lazyBarrelController;
57
+ const hashFunction = compilation.outputOptions.hashFunction;
35
58
  compilation.hooks.finishModules.tapAsync(
36
59
  PLUGIN_NAME,
37
60
  (modules, callback) => {
@@ -80,16 +103,28 @@ class FlagDependencyExportsPlugin {
80
103
  return callback();
81
104
  }
82
105
  const memCache = moduleMemCaches && moduleMemCaches.get(module);
83
- const memCacheValue = memCache && memCache.get(this);
84
- if (memCacheValue !== undefined) {
106
+ /** @type {MemCacheEntry | undefined} */
107
+ const memCacheEntry = memCache && memCache.get(this);
108
+ const hash = getLazyRequestsHash(
109
+ module,
110
+ lazyBarrelController,
111
+ hashFunction
112
+ );
113
+ // validated by the still-lazy request keys: reference-change
114
+ // detection cannot invalidate the mem cache when the un-lazied
115
+ // connections are unsafe-cached (they are skipped when comparing
116
+ // references)
117
+ if (memCacheEntry !== undefined && memCacheEntry.etag === hash) {
85
118
  statRestoredFromMemCache++;
86
- exportsInfo.restoreProvided(memCacheValue);
119
+ exportsInfo.restoreProvided(memCacheEntry.data);
87
120
  return callback();
88
121
  }
122
+ const buildHash = /** @type {string} */ (
123
+ /** @type {BuildInfo} */ (module.buildInfo).hash
124
+ );
89
125
  cache.get(
90
126
  module.identifier(),
91
- /** @type {BuildInfo} */
92
- (module.buildInfo).hash,
127
+ hash ? `${buildHash}|${hash}` : buildHash,
93
128
  (err, result) => {
94
129
  if (err) return callback(err);
95
130
 
@@ -421,13 +456,25 @@ class FlagDependencyExportsPlugin {
421
456
  .getRestoreProvidedData();
422
457
  const memCache =
423
458
  moduleMemCaches && moduleMemCaches.get(module);
459
+ const hash = getLazyRequestsHash(
460
+ module,
461
+ lazyBarrelController,
462
+ hashFunction
463
+ );
424
464
  if (memCache) {
425
- memCache.set(this, cachedData);
465
+ /** @type {MemCacheEntry} */
466
+ const entry = {
467
+ etag: hash,
468
+ data: cachedData
469
+ };
470
+ memCache.set(this, entry);
426
471
  }
472
+ const buildHash = /** @type {string} */ (
473
+ /** @type {BuildInfo} */ (module.buildInfo).hash
474
+ );
427
475
  cache.store(
428
476
  module.identifier(),
429
- /** @type {BuildInfo} */
430
- (module.buildInfo).hash,
477
+ hash ? `${buildHash}|${hash}` : buildHash,
431
478
  cachedData,
432
479
  callback
433
480
  );
package/lib/LazyBarrel.js CHANGED
@@ -68,7 +68,7 @@ class LazyBarrelInfo {
68
68
  this._requestToDepGroup = new Map();
69
69
  /** @type {Set<string> | undefined} locally provided export names */
70
70
  this._terminalIds = undefined;
71
- /** @type {Set<string> | undefined} request keys of star re-exports */
71
+ /** @type {Set<string> | undefined} requests of star re-exports */
72
72
  this._fallbackRequests = undefined;
73
73
  }
74
74
 
@@ -125,6 +125,14 @@ class LazyBarrelInfo {
125
125
  return this._requestToDepGroup.size === 0;
126
126
  }
127
127
 
128
+ /**
129
+ * Returns the requests of the still-lazy groups.
130
+ * @returns {Iterable<string>} still-lazy requests
131
+ */
132
+ getLazyRequests() {
133
+ return this._requestToDepGroup.keys();
134
+ }
135
+
128
136
  /**
129
137
  * Drops the export-name lookups once no group stays deferred; they only serve
130
138
  * to find still-deferred groups, so keeping them per module wastes memory.
@@ -202,13 +210,32 @@ class LazyBarrelController {
202
210
  }
203
211
 
204
212
  /**
205
- * Releases all per-module deferral state. Lazy barrel only acts while the
206
- * module graph is built, so the bookkeeping is dead weight once make is done.
213
+ * Releases all per-module deferral state. The last consumer is
214
+ * `FlagDependencyExportsPlugin`'s cache key during `finishModules`, so the
215
+ * bookkeeping is dead weight once sealing starts.
207
216
  */
208
217
  clear() {
209
218
  this._modules = new WeakMap();
210
219
  }
211
220
 
221
+ /**
222
+ * requests of the re-export targets `module` still keeps lazy.
223
+ * `FlagDependencyExportsPlugin` folds them into its provided-exports cache
224
+ * key, so a barrel whose built targets differ from a cached run does not
225
+ * reuse that run's stale exports.
226
+ * @param {Module} module the module
227
+ * @returns {Iterable<string> | undefined} still-lazy requests, if any
228
+ */
229
+ getLazyRequests(module) {
230
+ const factoryMeta = module.factoryMeta;
231
+ if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return;
232
+ const state = this._modules.get(module);
233
+ if (state === undefined) return;
234
+ const info = state.lazyBarrelInfo;
235
+ if (info === undefined || info.isEmpty()) return;
236
+ return info.getLazyRequests();
237
+ }
238
+
212
239
  /**
213
240
  * Classifies the re-export dependencies of a side-effect-free module (lazy barrel)
214
241
  * into deferrable groups and marks the deferred ones.
@@ -254,12 +254,10 @@ const walkSideEffectsIterative = (rootMod, moduleGraph, ctx) => {
254
254
  state = false;
255
255
  } else if (refModule.factoryMeta.sideEffectFree === false) {
256
256
  state = true;
257
- } else if (
258
- !(
259
- refModule.buildMeta !== undefined &&
260
- refModule.buildMeta.sideEffectFree
261
- )
262
- ) {
257
+ } else if (!(
258
+ refModule.buildMeta !== undefined &&
259
+ refModule.buildMeta.sideEffectFree
260
+ )) {
263
261
  state = true;
264
262
  } else if (refModule._isEvaluatingSideEffects) {
265
263
  ctx.circular = true;
@@ -276,12 +274,10 @@ const walkSideEffectsIterative = (rootMod, moduleGraph, ctx) => {
276
274
  descended = true;
277
275
  break;
278
276
  }
279
- } else if (
280
- !(
281
- refModule.buildMeta !== undefined &&
282
- refModule.buildMeta.sideEffectFree
283
- )
284
- ) {
277
+ } else if (!(
278
+ refModule.buildMeta !== undefined &&
279
+ refModule.buildMeta.sideEffectFree
280
+ )) {
285
281
  state = true;
286
282
  } else if (refModule._isEvaluatingSideEffects) {
287
283
  ctx.circular = true;
@@ -1729,9 +1729,9 @@ const applyOutputDefaults = (
1729
1729
  if (tp) {
1730
1730
  if (tp.global) return "global";
1731
1731
  if (tp.globalThis) return "globalThis";
1732
- // For universal target (i.e. code can be run in browser/node/worker etc.)
1733
- // `tp.module` is unset for a version-less universal target, so key off ESM output
1734
- if (tp.web === null && tp.node === null && output.module) {
1732
+ // Universal target (web + node) with no reported accessor: `globalThis`
1733
+ // is the only one defined everywhere (`null` = mixed support `self`).
1734
+ if (tp.web === null && tp.node === null && tp.globalThis === undefined) {
1735
1735
  return "globalThis";
1736
1736
  }
1737
1737
  }
@@ -48,7 +48,8 @@ const CSS_TYPE_PREFIX = `${CSS_TYPE}/`;
48
48
  /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
49
49
  /** @typedef {import("../Dependency")} Dependency */
50
50
  /** @typedef {import("../DependencyTemplate").CssData} CssData */
51
- /** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
51
+ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
52
+ /** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} CssDependencyTemplateContext */
52
53
  /** @typedef {import("../Generator").GenerateContext} GenerateContext */
53
54
  /** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */
54
55
  /** @typedef {import("../Module").BuildInfo} BuildInfo */
@@ -191,28 +192,39 @@ class CssGenerator extends Generator {
191
192
 
192
193
  /**
193
194
  * Processes the provided module.
194
- * @param {NormalModule} module the current module
195
195
  * @param {Dependency} dependency the dependency to generate
196
- * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
197
196
  * @param {ReplaceSource} source the current replace source which can be modified
198
- * @param {GenerateContext & { cssData: CssData }} generateContext the render context
197
+ * @param {DependencyTemplateContext & { cssData: CssData, type: string }} templateContext the template context (shared across all dependencies of the module)
199
198
  * @returns {void}
200
199
  */
201
- sourceDependency(module, dependency, initFragments, source, generateContext) {
200
+ sourceDependency(dependency, source, templateContext) {
202
201
  const constructor =
203
202
  /** @type {DependencyConstructor} */
204
203
  (dependency.constructor);
205
- const template = generateContext.dependencyTemplates.get(constructor);
204
+ const template = templateContext.dependencyTemplates.get(constructor);
206
205
  if (!template) {
207
206
  throw new Error(
208
207
  `No template for dependency: ${dependency.constructor.name}`
209
208
  );
210
209
  }
211
210
 
212
- /** @type {DependencyTemplateContext} */
211
+ template.apply(dependency, source, templateContext);
212
+ }
213
+
214
+ /**
215
+ * Processes the provided module.
216
+ * @param {NormalModule} module the module to generate
217
+ * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
218
+ * @param {ReplaceSource} source the current replace source which can be modified
219
+ * @param {GenerateContext & { cssData: CssData }} generateContext the generateContext
220
+ * @returns {void}
221
+ */
222
+ sourceModule(module, initFragments, source, generateContext) {
223
+ // Only `dependency` varies across `template.apply` calls, so one context
224
+ // (and one lazy `chunkInitFragments` getter) serves the whole module.
213
225
  /** @type {InitFragment<GenerateContext>[] | undefined} */
214
226
  let chunkInitFragments;
215
- /** @type {DependencyTemplateContext} */
227
+ /** @type {CssDependencyTemplateContext} */
216
228
  const templateContext = {
217
229
  runtimeTemplate: generateContext.runtimeTemplate,
218
230
  dependencyTemplates: generateContext.dependencyTemplates,
@@ -244,37 +256,13 @@ class CssGenerator extends Generator {
244
256
  }
245
257
  };
246
258
 
247
- template.apply(dependency, source, templateContext);
248
- }
249
-
250
- /**
251
- * Processes the provided module.
252
- * @param {NormalModule} module the module to generate
253
- * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
254
- * @param {ReplaceSource} source the current replace source which can be modified
255
- * @param {GenerateContext & { cssData: CssData }} generateContext the generateContext
256
- * @returns {void}
257
- */
258
- sourceModule(module, initFragments, source, generateContext) {
259
259
  for (const dependency of module.dependencies) {
260
- this.sourceDependency(
261
- module,
262
- dependency,
263
- initFragments,
264
- source,
265
- generateContext
266
- );
260
+ this.sourceDependency(dependency, source, templateContext);
267
261
  }
268
262
 
269
263
  if (module.presentationalDependencies !== undefined) {
270
264
  for (const dependency of module.presentationalDependencies) {
271
- this.sourceDependency(
272
- module,
273
- dependency,
274
- initFragments,
275
- source,
276
- generateContext
277
- );
265
+ this.sourceDependency(dependency, source, templateContext);
278
266
  }
279
267
  }
280
268
  }
@@ -623,10 +611,13 @@ class CssGenerator extends Generator {
623
611
 
624
612
  let hasJs = false;
625
613
  let hasCssText = false;
614
+ // Uncached per call (#20800) — the common link case stops scanning once
615
+ // js is seen; `hasCssText` only matters for exports-only / non-link.
616
+ const needsCssText = this._exportsOnly || exportType !== "link";
626
617
  const connections = this._moduleGraph.getIncomingConnections(module);
627
618
 
628
619
  for (const connection of connections) {
629
- if (hasJs && hasCssText) break;
620
+ if (hasJs && (hasCssText || !needsCssText)) break;
630
621
  if (
631
622
  exportType === "link" &&
632
623
  connection.dependency instanceof CssImportDependency
@@ -704,15 +695,15 @@ class CssGenerator extends Generator {
704
695
  }
705
696
  return 0;
706
697
  }
707
- const exports = cssData.exports;
708
- /** @type {Record<string, string>} */
709
- const exportsObj = {};
710
- for (const [key, value] of exports) {
711
- exportsObj[key] = value;
698
+ // `JSON.stringify({...exports}).length` computed entry-wise, without
699
+ // materializing the object or the full string.
700
+ let length = 2 + (cssData.exports.size - 1);
701
+ for (const [key, value] of cssData.exports) {
702
+ length +=
703
+ JSON.stringify(key).length + 1 + JSON.stringify(value).length;
712
704
  }
713
- const stringifiedExports = JSON.stringify(exportsObj);
714
705
 
715
- return stringifiedExports.length + 42;
706
+ return length + 42;
716
707
  }
717
708
  case CSS_TEXT_TYPE:
718
709
  case CSS_TYPE: {
@@ -133,6 +133,42 @@ const getCssInjectStyleRuntimeModule = memoize(() =>
133
133
  */
134
134
  const publicPathPlaceholderPlans = new WeakMap();
135
135
 
136
+ /**
137
+ * Memoized inheritance chain per module, revalidated against its source
138
+ * fields — `updateCacheModule` reassigns them on watch rebuilds.
139
+ * @type {WeakMap<CssModule, { cssLayer: CssModule["cssLayer"], supports: CssModule["supports"], media: CssModule["media"], inheritance: CssModule["inheritance"], chain: Inheritance }>}
140
+ */
141
+ const moduleInheritanceCache = new WeakMap();
142
+
143
+ /**
144
+ * Gets the memoized inheritance chain of a css module.
145
+ * @param {CssModule} module css module
146
+ * @returns {Inheritance} inheritance chain including the module's own entry
147
+ */
148
+ const getModuleInheritance = (module) => {
149
+ const entry = moduleInheritanceCache.get(module);
150
+ if (
151
+ entry !== undefined &&
152
+ entry.cssLayer === module.cssLayer &&
153
+ entry.supports === module.supports &&
154
+ entry.media === module.media &&
155
+ entry.inheritance === module.inheritance
156
+ ) {
157
+ return entry.chain;
158
+ }
159
+ /** @type {Inheritance} */
160
+ const chain = [[module.cssLayer, module.supports, module.media]];
161
+ if (module.inheritance) chain.push(...module.inheritance);
162
+ moduleInheritanceCache.set(module, {
163
+ cssLayer: module.cssLayer,
164
+ supports: module.supports,
165
+ media: module.media,
166
+ inheritance: module.inheritance,
167
+ chain
168
+ });
169
+ return chain;
170
+ };
171
+
136
172
  /**
137
173
  * Locate the public-path placeholders in a materialized module source.
138
174
  * @param {string} content materialized module source
@@ -857,55 +893,55 @@ class CssModulesPlugin {
857
893
  return this.getModulesInOrder(chunk, modules, compilation);
858
894
  };
859
895
 
860
- return /** @type {CssModule[]} */ ([
861
- ...orderModules(
862
- chunkGraph.getOrderedChunkModulesIterableBySourceType(
863
- chunk,
864
- CSS_IMPORT_TYPE,
865
- compareModulesByFullName(compilation.compiler)
866
- )
867
- ),
868
- ...orderModules(
869
- chunkGraph.getOrderedChunkModulesIterableBySourceType(
870
- chunk,
871
- CSS_TYPE,
872
- compareModulesByFullName(compilation.compiler)
873
- )
874
- ).map((module) => {
896
+ const comparator = compareModulesByFullName(compilation.compiler);
897
+ const importModules = orderModules(
898
+ chunkGraph.getOrderedChunkModulesIterableBySourceType(
899
+ chunk,
900
+ CSS_IMPORT_TYPE,
901
+ comparator
902
+ )
903
+ );
904
+ const cssModules = orderModules(
905
+ chunkGraph.getOrderedChunkModulesIterableBySourceType(
906
+ chunk,
907
+ CSS_TYPE,
908
+ comparator
909
+ )
910
+ );
911
+ for (const module of cssModules) {
912
+ if (
913
+ typeof (
914
+ /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
915
+ ) !== "undefined"
916
+ ) {
875
917
  if (
876
- typeof (
918
+ typeof charset !== "undefined" &&
919
+ charset !==
877
920
  /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
878
- ) !== "undefined"
879
921
  ) {
880
- if (
881
- typeof charset !== "undefined" &&
882
- charset !==
922
+ const err = new WebpackError(
923
+ `Conflicting @charset at-rules detected: the module ${module.readableIdentifier(
924
+ compilation.requestShortener
925
+ )} (in chunk ${chunk.name || chunk.id}) specifies "${
883
926
  /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
884
- ) {
885
- const err = new WebpackError(
886
- `Conflicting @charset at-rules detected: the module ${module.readableIdentifier(
887
- compilation.requestShortener
888
- )} (in chunk ${chunk.name || chunk.id}) specifies "${
889
- /** @type {CssModuleBuildInfo} */ (module.buildInfo).charset
890
- }", but "${charset}" was expected, all modules must use the same character set`
891
- );
927
+ }", but "${charset}" was expected, all modules must use the same character set`
928
+ );
892
929
 
893
- err.chunk = chunk;
894
- err.module = module;
895
- err.hideStack = true;
930
+ err.chunk = chunk;
931
+ err.module = module;
932
+ err.hideStack = true;
896
933
 
897
- compilation.warnings.push(err);
898
- }
934
+ compilation.warnings.push(err);
935
+ }
899
936
 
900
- if (typeof charset === "undefined") {
901
- charset = /** @type {CssModuleBuildInfo} */ (module.buildInfo)
902
- .charset;
903
- }
937
+ if (typeof charset === "undefined") {
938
+ charset = /** @type {CssModuleBuildInfo} */ (module.buildInfo)
939
+ .charset;
904
940
  }
941
+ }
942
+ }
905
943
 
906
- return module;
907
- })
908
- ]);
944
+ return /** @type {CssModule[]} */ ([...importModules, ...cssModules]);
909
945
  }
910
946
 
911
947
  /**
@@ -920,11 +956,7 @@ class CssModulesPlugin {
920
956
  renderContext;
921
957
  const cacheEntry = moduleFactoryCache.get(moduleSourceContent);
922
958
 
923
- /** @type {Inheritance} */
924
- const inheritance = [[module.cssLayer, module.supports, module.media]];
925
- if (module.inheritance) {
926
- inheritance.push(...module.inheritance);
927
- }
959
+ const inheritance = getModuleInheritance(module);
928
960
 
929
961
  /** @type {CachedSource} */
930
962
  let source;
@@ -932,14 +964,8 @@ class CssModulesPlugin {
932
964
  cacheEntry &&
933
965
  cacheEntry.undoPath === undoPath &&
934
966
  cacheEntry.hash === hash &&
935
- cacheEntry.inheritance.length === inheritance.length &&
936
- cacheEntry.inheritance.every(([layer, supports, media], i) => {
937
- const item = inheritance[i];
938
- if (Array.isArray(item)) {
939
- return layer === item[0] && supports === item[1] && media === item[2];
940
- }
941
- return false;
942
- })
967
+ // Memoized per module, so identity implies equal entries.
968
+ cacheEntry.inheritance === inheritance
943
969
  ) {
944
970
  source = cacheEntry.source;
945
971
  } else {