webpack 5.109.1 → 5.109.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.
@@ -7,6 +7,7 @@
7
7
 
8
8
  const FileSystemInfo = require("../FileSystemInfo");
9
9
  const ProgressPlugin = require("../ProgressPlugin");
10
+ const { getReferencedFilenames } = require("../serialization/FileMiddleware");
10
11
  const SerializerMiddleware = require("../serialization/SerializerMiddleware");
11
12
  const LazySet = require("../util/LazySet");
12
13
  const formatSize = require("../util/formatSize");
@@ -34,6 +35,17 @@ const {
34
35
  /** @typedef {Set<string>} Items */
35
36
  /** @typedef {Set<string>} BuildDependencies */
36
37
  /** @typedef {Map<string, PackItemInfo>} ItemInfo */
38
+ /** @typedef {{ firstSeen: number, size: number }} UnreferencedFile */
39
+ /** @typedef {Map<string, UnreferencedFile>} UnreferencedFiles */
40
+
41
+ // Unreferenced files are kept for this long to not race concurrent builds sharing the cache directory.
42
+ const CLEANUP_GRACE_PERIOD = 30 * 60 * 1000;
43
+ // Records when each unreferenced file was first seen. Aging by recorded time keeps
44
+ // orphans expiring across caches restored with refreshed modification times.
45
+ const UNREFERENCED_FILE = "unreferenced.json";
46
+ // A file written this recently may belong to a concurrent build that reused the name,
47
+ // in which case the recorded time describes the previous file and must not be trusted.
48
+ const CLEANUP_RECENT_WRITE_PERIOD = 60 * 1000;
37
49
 
38
50
  class PackContainer {
39
51
  /**
@@ -569,6 +581,41 @@ class Pack {
569
581
  }
570
582
  }
571
583
 
584
+ /**
585
+ * Drops every content whose items all expired. Unlike a partial collection this
586
+ * never unpacks, so it is not limited to a single content per store and lets a
587
+ * long unused cache shrink in one go instead of one pack per build.
588
+ */
589
+ _gcExpiredContent() {
590
+ const now = Date.now();
591
+ let packCount = 0;
592
+ let itemCount = 0;
593
+ for (let loc = 0; loc < this.content.length; loc++) {
594
+ const content = this.content[loc];
595
+ if (!content) continue;
596
+ let expired = true;
597
+ for (const identifier of content.items) {
598
+ const info = this.itemInfo.get(identifier);
599
+ if (info !== undefined && now - info.lastAccess <= this.maxAge) {
600
+ expired = false;
601
+ break;
602
+ }
603
+ }
604
+ if (!expired) continue;
605
+ for (const identifier of content.items) this.itemInfo.delete(identifier);
606
+ this.content[loc] = undefined;
607
+ packCount++;
608
+ itemCount += content.items.size;
609
+ }
610
+ if (packCount > 0) {
611
+ this.logger.log(
612
+ "Garbage Collected %d completely expired packs with %d items",
613
+ packCount,
614
+ itemCount
615
+ );
616
+ }
617
+ }
618
+
572
619
  /**
573
620
  * Find the content with the oldest item and run GC on that.
574
621
  * Only runs for one content to avoid large invalidation.
@@ -581,11 +628,10 @@ class Pack {
581
628
  oldest = info;
582
629
  }
583
630
  }
584
- if (
585
- Date.now() - /** @type {PackItemInfo} */ (oldest).lastAccess >
586
- this.maxAge
587
- ) {
588
- const loc = /** @type {PackItemInfo} */ (oldest).location;
631
+ // collecting expired content may have left no items at all
632
+ if (oldest === undefined) return;
633
+ if (Date.now() - oldest.lastAccess > this.maxAge) {
634
+ const loc = oldest.location;
589
635
  if (loc < 0) return;
590
636
  const content = /** @type {PackContent} */ (this.content[loc]);
591
637
  const items = new Set(content.items);
@@ -621,6 +667,7 @@ class Pack {
621
667
  this._persistFreshContent();
622
668
  this._optimizeSmallContent();
623
669
  this._optimizeUnusedContent();
670
+ this._gcExpiredContent();
624
671
  this._gcOldestContent();
625
672
  for (const identifier of this.itemInfo.keys()) {
626
673
  write(identifier);
@@ -1166,6 +1213,8 @@ class PackFileCacheStrategy {
1166
1213
  });
1167
1214
  /** @type {Compiler} */
1168
1215
  this.compiler = compiler;
1216
+ /** @type {IntermediateFileSystem} */
1217
+ this.fs = fs;
1169
1218
  /** @type {string} */
1170
1219
  this.context = context;
1171
1220
  /** @type {string} */
@@ -1182,6 +1231,10 @@ class PackFileCacheStrategy {
1182
1231
  this.readonly = readonly;
1183
1232
  /** @type {boolean | undefined} */
1184
1233
  this.allowCollectingMemory = allowCollectingMemory;
1234
+ // referenced names per on-disk file, versioned by mtime since concurrent
1235
+ // processes may rewrite packs in place under the same name
1236
+ /** @type {Map<string, { mtimeMs: number, referenced: string[] }>} */
1237
+ this._referencedFilesCache = new Map();
1185
1238
  /** @type {false | "gzip" | "brotli" | "zstd" | undefined} */
1186
1239
  this.compression = compression;
1187
1240
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
@@ -1588,10 +1641,17 @@ class PackFileCacheStrategy {
1588
1641
  /** @type {Snapshot} */
1589
1642
  (this.resolveBuildDependenciesSnapshot)
1590
1643
  );
1644
+ const cleanup = this.fs.unlink !== undefined;
1645
+ /** @type {Set<string> | undefined} */
1646
+ const writtenFiles = cleanup ? new Set() : undefined;
1647
+ /** @type {Set<string> | undefined} */
1648
+ const retainedFiles = cleanup ? new Set() : undefined;
1591
1649
  return this.fileSerializer
1592
1650
  .serialize(content, {
1593
1651
  filename: `${this.cacheLocation}/index${this._extension}`,
1594
1652
  extension: `${this._extension}`,
1653
+ writtenFiles,
1654
+ retainedFiles,
1595
1655
  logger: this.logger,
1596
1656
  profile: this.profile
1597
1657
  })
@@ -1608,9 +1668,17 @@ class PackFileCacheStrategy {
1608
1668
  stats.count,
1609
1669
  Math.round(stats.size / 1024 / 1024)
1610
1670
  );
1671
+ if (writtenFiles !== undefined) {
1672
+ return this._cleanupUnusedFiles(
1673
+ writtenFiles,
1674
+ /** @type {Set<string>} */ (retainedFiles)
1675
+ );
1676
+ }
1611
1677
  })
1612
1678
  .catch((err) => {
1613
1679
  this.logger.timeEnd("store pack");
1680
+ // files may be in an unknown state after a failed store
1681
+ this._referencedFilesCache.clear();
1614
1682
  this.logger.warn(`Caching failed for pack: ${err}`);
1615
1683
  this.logger.debug(err.stack);
1616
1684
  });
@@ -1622,6 +1690,192 @@ class PackFileCacheStrategy {
1622
1690
  }));
1623
1691
  }
1624
1692
 
1693
+ /**
1694
+ * Reads when the currently unreferenced files were first seen. A missing or
1695
+ * unreadable file just restarts the grace period for every orphan.
1696
+ * @returns {Promise<UnreferencedFiles>} first seen time and size per file name
1697
+ */
1698
+ _readUnreferencedFiles() {
1699
+ return new Promise((resolve) => {
1700
+ this.fs.readFile(
1701
+ `${this.cacheLocation}/${UNREFERENCED_FILE}`,
1702
+ (err, content) => {
1703
+ /** @type {UnreferencedFiles} */
1704
+ const result = new Map();
1705
+ if (err) return resolve(result);
1706
+ try {
1707
+ const data = JSON.parse(
1708
+ /** @type {Buffer} */ (content).toString("utf8")
1709
+ );
1710
+ for (const [file, entry] of Object.entries(data)) {
1711
+ const { firstSeen, size } = /** @type {UnreferencedFile} */ (
1712
+ entry
1713
+ );
1714
+ if (typeof firstSeen === "number" && typeof size === "number") {
1715
+ result.set(file, { firstSeen, size });
1716
+ }
1717
+ }
1718
+ } catch (_err) {
1719
+ result.clear();
1720
+ }
1721
+ resolve(result);
1722
+ }
1723
+ );
1724
+ });
1725
+ }
1726
+
1727
+ /**
1728
+ * Persists when the still unreferenced files were first seen. Failing to write
1729
+ * only costs the orphans another grace period, so errors are ignored.
1730
+ * @param {UnreferencedFiles} unreferenced first seen time and size per file name
1731
+ * @param {boolean} hadEntries whether a previous state exists that must be replaced
1732
+ * @returns {Promise<void>} promise
1733
+ */
1734
+ _writeUnreferencedFiles(unreferenced, hadEntries) {
1735
+ if (unreferenced.size === 0 && !hadEntries) return Promise.resolve();
1736
+ /** @type {Record<string, UnreferencedFile>} */
1737
+ const data = {};
1738
+ for (const [file, entry] of unreferenced) data[file] = entry;
1739
+ return new Promise((resolve) => {
1740
+ this.fs.writeFile(
1741
+ `${this.cacheLocation}/${UNREFERENCED_FILE}`,
1742
+ JSON.stringify(data),
1743
+ () => resolve()
1744
+ );
1745
+ });
1746
+ }
1747
+
1748
+ /**
1749
+ * Deletes files from the cache directory that are no longer referenced by the
1750
+ * stored pack. Retained files are walked on disk since nested lazy segments
1751
+ * reference files not visible during serialization. Errors only log a warning.
1752
+ * @param {Set<string>} writtenNames names (without extension) written by this store
1753
+ * @param {Set<string>} retainedNames names (without extension) referenced but not rewritten
1754
+ * @returns {Promise<void>} promise
1755
+ */
1756
+ async _cleanupUnusedFiles(writtenNames, retainedNames) {
1757
+ this.logger.time("cleanup unused cache files");
1758
+ const fs = this.fs;
1759
+ const extension = this._extension;
1760
+ const cacheLocation = this.cacheLocation;
1761
+ const referencedFilesCache = this._referencedFilesCache;
1762
+ try {
1763
+ // rewritten files may reference different names now
1764
+ for (const name of writtenNames) referencedFilesCache.delete(name);
1765
+ /** @type {Set<string>} */
1766
+ const liveFiles = new Set([`index${extension}`, UNREFERENCED_FILE]);
1767
+ for (const name of writtenNames) liveFiles.add(`${name}${extension}`);
1768
+ /** @type {string[]} */
1769
+ const queue = [];
1770
+ /**
1771
+ * Marks a file live and queues it for walking its references.
1772
+ * @param {string} name file name without extension
1773
+ */
1774
+ const enqueue = (name) => {
1775
+ const file = `${name}${extension}`;
1776
+ if (liveFiles.has(file)) return;
1777
+ liveFiles.add(file);
1778
+ queue.push(name);
1779
+ };
1780
+ for (const name of retainedNames) enqueue(name);
1781
+ while (queue.length > 0) {
1782
+ const name = /** @type {string} */ (queue.pop());
1783
+ const file = `${cacheLocation}/${name}${extension}`;
1784
+ // an unchanged mtime proves the memo entry still matches the disk
1785
+ const mtimeMs = await new Promise((resolve, reject) => {
1786
+ fs.stat(file, (err, stats) => {
1787
+ if (err) return reject(err);
1788
+ resolve(
1789
+ /** @type {number} */ (
1790
+ /** @type {import("../util/fs").IStats} */ (stats).mtimeMs
1791
+ )
1792
+ );
1793
+ });
1794
+ });
1795
+ const entry = referencedFilesCache.get(name);
1796
+ let referenced;
1797
+ if (entry !== undefined && entry.mtimeMs === mtimeMs) {
1798
+ referenced = entry.referenced;
1799
+ } else {
1800
+ referenced = await getReferencedFilenames(fs, file);
1801
+ referencedFilesCache.set(name, { mtimeMs, referenced });
1802
+ }
1803
+ for (const referencedName of referenced) enqueue(referencedName);
1804
+ }
1805
+ const files = await new Promise((resolve, reject) => {
1806
+ fs.readdir(cacheLocation, (err, files) => {
1807
+ if (err) return reject(err);
1808
+ resolve(/** @type {string[]} */ (files));
1809
+ });
1810
+ });
1811
+ const seenFiles = await this._readUnreferencedFiles();
1812
+ const now = Date.now();
1813
+ // every store rewrites the index backup, so a recorded time would age a file
1814
+ // that is in fact new; renaming carries the previous index mtime onto it
1815
+ const indexBackup = `index${extension}.old`;
1816
+ /** @type {UnreferencedFiles} */
1817
+ const stillUnreferenced = new Map();
1818
+ let deletedCount = 0;
1819
+ for (const file of files) {
1820
+ if (typeof file !== "string" || liveFiles.has(file)) continue;
1821
+ const path = `${cacheLocation}/${file}`;
1822
+ const stats = await new Promise((resolve) => {
1823
+ fs.stat(path, (err, stats) => {
1824
+ resolve(
1825
+ err
1826
+ ? undefined
1827
+ : /** @type {import("../util/fs").IStats} */ (stats)
1828
+ );
1829
+ });
1830
+ });
1831
+ if (stats === undefined || !stats.isFile()) continue;
1832
+ const size = /** @type {number} */ (stats.size);
1833
+ const seen = seenFiles.get(file);
1834
+ // a differing size means the name was rewritten, so it is a new orphan
1835
+ const firstSeen =
1836
+ seen !== undefined && seen.size === size ? seen.firstSeen : now;
1837
+ const expireTime = now - CLEANUP_GRACE_PERIOD;
1838
+ const mtimeMs = /** @type {number} */ (stats.mtimeMs);
1839
+ // either signal is enough: modification times are lost when a cache is
1840
+ // restored, and recorded times are lost when the cache directory is new
1841
+ const expired =
1842
+ mtimeMs <= expireTime ||
1843
+ (file !== indexBackup &&
1844
+ firstSeen <= expireTime &&
1845
+ mtimeMs <= now - CLEANUP_RECENT_WRITE_PERIOD);
1846
+ if (expired) {
1847
+ const deleted = await new Promise((resolve) => {
1848
+ /** @type {NonNullable<IntermediateFileSystem["unlink"]>} */
1849
+ (fs.unlink)(path, (err) => resolve(!err));
1850
+ });
1851
+ if (deleted) {
1852
+ deletedCount++;
1853
+ continue;
1854
+ }
1855
+ }
1856
+ if (file !== indexBackup) {
1857
+ stillUnreferenced.set(file, { firstSeen, size });
1858
+ }
1859
+ }
1860
+ await this._writeUnreferencedFiles(stillUnreferenced, seenFiles.size > 0);
1861
+ // drop entries of files that are no longer alive
1862
+ for (const name of referencedFilesCache.keys()) {
1863
+ if (!liveFiles.has(`${name}${extension}`)) {
1864
+ referencedFilesCache.delete(name);
1865
+ }
1866
+ }
1867
+ if (deletedCount > 0) {
1868
+ this.logger.log("Deleted %d unused cache files", deletedCount);
1869
+ }
1870
+ } catch (err) {
1871
+ this.logger.warn(
1872
+ `Cleanup of unused cache files failed: ${/** @type {Error} */ (err)}`
1873
+ );
1874
+ this.logger.debug(/** @type {Error} */ (err).stack);
1875
+ }
1876
+ this.logger.timeEnd("cleanup unused cache files");
1877
+ }
1878
+
1625
1879
  clear() {
1626
1880
  this.fileSystemInfo.clear();
1627
1881
  this.buildDependencies.clear();
@@ -511,7 +511,13 @@ const applyWebpackOptionsDefaults = (options, compilerIndex) => {
511
511
  /** @type {NonNullable<WebpackOptionsNormalized["loader"]>} */ (
512
512
  options.loader
513
513
  ),
514
- { targetProperties, environment: options.output.environment }
514
+ {
515
+ targetProperties,
516
+ environment: options.output.environment,
517
+ outputModule:
518
+ /** @type {NonNullable<WebpackOptionsNormalized["output"]["module"]>} */
519
+ (options.output.module)
520
+ }
515
521
  );
516
522
 
517
523
  F(options, "externalsType", () => {
@@ -2251,9 +2257,13 @@ const applyExternalsPresetsDefaults = (
2251
2257
  * @param {object} options options
2252
2258
  * @param {TargetProperties | false} options.targetProperties target properties
2253
2259
  * @param {Environment} options.environment environment
2260
+ * @param {boolean} options.outputModule is output type is module
2254
2261
  * @returns {void}
2255
2262
  */
2256
- const applyLoaderDefaults = (loader, { targetProperties, environment }) => {
2263
+ const applyLoaderDefaults = (
2264
+ loader,
2265
+ { targetProperties, environment, outputModule }
2266
+ ) => {
2257
2267
  F(loader, "target", () => {
2258
2268
  if (targetProperties) {
2259
2269
  if (targetProperties.electron) {
@@ -2267,6 +2277,15 @@ const applyLoaderDefaults = (loader, { targetProperties, environment }) => {
2267
2277
  if (targetProperties.bun) return "bun";
2268
2278
  if (targetProperties.node) return "node";
2269
2279
  if (targetProperties.web) return "web";
2280
+ // no single platform to report: the bundle runs on both (target
2281
+ // `"universal"` / `["web", "node"]`), so loaders get `"universal"`
2282
+ if (
2283
+ outputModule &&
2284
+ targetProperties.node === null &&
2285
+ targetProperties.web === null
2286
+ ) {
2287
+ return "universal";
2288
+ }
2270
2289
  }
2271
2290
  });
2272
2291
  D(loader, "environment", environment);
@@ -18,11 +18,21 @@ const HoistContainerReferences = require("./HoistContainerReferencesPlugin");
18
18
  /** @typedef {import("../Compiler")} Compiler */
19
19
  /** @typedef {import("../Dependency")} Dependency */
20
20
 
21
+ const createCompilationHooks = () => ({
22
+ /**
23
+ * @type {SyncHook<Dependency>}
24
+ * @since 5.96.0
25
+ */
26
+ addContainerEntryDependency: new SyncHook(["dependency"]),
27
+ /**
28
+ * @type {SyncHook<Dependency>}
29
+ * @since 5.96.0
30
+ */
31
+ addFederationRuntimeDependency: new SyncHook(["dependency"])
32
+ });
33
+
21
34
  /**
22
- * Defines the compilation hooks type used by this module.
23
- * @typedef {object} CompilationHooks
24
- * @property {SyncHook<Dependency>} addContainerEntryDependency
25
- * @property {SyncHook<Dependency>} addFederationRuntimeDependency
35
+ * @typedef {ReturnType<typeof createCompilationHooks>} CompilationHooks
26
36
  */
27
37
 
28
38
  const PLUGIN_NAME = "ModuleFederationPlugin";
@@ -111,11 +121,7 @@ class ModuleFederationPlugin {
111
121
  }
112
122
 
113
123
  ModuleFederationPlugin.getCompilationHooks = createHooksRegistry(
114
- () =>
115
- /** @type {CompilationHooks} */ ({
116
- addContainerEntryDependency: new SyncHook(["dependency"]),
117
- addFederationRuntimeDependency: new SyncHook(["dependency"])
118
- })
124
+ createCompilationHooks
119
125
  );
120
126
 
121
127
  module.exports = ModuleFederationPlugin;
@@ -33,6 +33,7 @@ const CssImportDependency = require("../dependencies/CssImportDependency");
33
33
  const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
34
34
 
35
35
  const { encodeMappings } = require("../util/createMappings");
36
+ const { contextifySourceUrl } = require("../util/identifier");
36
37
  const memoize = require("../util/memoize");
37
38
  const {
38
39
  PUBLIC_PATH_FULL_HASH,
@@ -549,8 +550,13 @@ class CssGenerator extends Generator {
549
550
  }
550
551
 
551
552
  const generatedJs = /** @type {string} */ (source.source());
552
- const sourceName = module.readableIdentifier(
553
- compilation.requestShortener
553
+ // Context-relative identifier, like `NormalModule.createSource` — so
554
+ // `SourceMapDevToolPlugin` can match the module and apply the
555
+ // devtool filename template instead of leaking this raw name
556
+ const sourceName = contextifySourceUrl(
557
+ /** @type {string} */ (compilation.options.context),
558
+ module.identifier(),
559
+ compilation.compiler.root
554
560
  );
555
561
 
556
562
  if (
@@ -20,12 +20,31 @@ const { chunkHasCss } = require("./CssModulesPlugin");
20
20
  /** @typedef {import("../ChunkGraph")} ChunkGraph */
21
21
  /** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
22
22
 
23
+ const createCompilationHooks = () => ({
24
+ /**
25
+ * @type {SyncWaterfallHook<[string, Chunk]>}
26
+ * @since 5.66.0
27
+ */
28
+ createStylesheet: new SyncWaterfallHook(["source", "chunk"]),
29
+ /**
30
+ * @type {SyncWaterfallHook<[string, Chunk]>}
31
+ * @since 5.91.0
32
+ */
33
+ linkPreload: new SyncWaterfallHook(["source", "chunk"]),
34
+ /**
35
+ * @type {SyncWaterfallHook<[string, Chunk]>}
36
+ * @since 5.91.0
37
+ */
38
+ linkPrefetch: new SyncWaterfallHook(["source", "chunk"]),
39
+ /**
40
+ * @type {SyncWaterfallHook<[string, Chunk]>}
41
+ * @since 5.107.0
42
+ */
43
+ linkInsert: new SyncWaterfallHook(["source", "chunk"])
44
+ });
45
+
23
46
  /**
24
- * @typedef {object} CssLoadingRuntimeModulePluginHooks
25
- * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet
26
- * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
27
- * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
28
- * @property {SyncWaterfallHook<[string, Chunk]>} linkInsert
47
+ * @typedef {ReturnType<typeof createCompilationHooks>} CssLoadingRuntimeModulePluginHooks
29
48
  */
30
49
 
31
50
  class CssLoadingRuntimeModule extends RuntimeModule {
@@ -577,13 +596,7 @@ class CssLoadingRuntimeModule extends RuntimeModule {
577
596
  }
578
597
 
579
598
  CssLoadingRuntimeModule.getCompilationHooks = createHooksRegistry(
580
- () =>
581
- /** @type {CssLoadingRuntimeModulePluginHooks} */ ({
582
- createStylesheet: new SyncWaterfallHook(["source", "chunk"]),
583
- linkPreload: new SyncWaterfallHook(["source", "chunk"]),
584
- linkPrefetch: new SyncWaterfallHook(["source", "chunk"]),
585
- linkInsert: new SyncWaterfallHook(["source", "chunk"])
586
- })
599
+ createCompilationHooks
587
600
  );
588
601
 
589
602
  module.exports = CssLoadingRuntimeModule;
@@ -95,14 +95,6 @@ const CssParser = require("./CssParser");
95
95
  * @property {Source} moduleSourceContent content
96
96
  */
97
97
 
98
- /**
99
- * Defines the compilation hooks type used by this module.
100
- * @typedef {object} CompilationHooks
101
- * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage
102
- * @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash
103
- * @property {SyncBailHook<[Chunk, Module[], Compilation], Module[] | undefined | void>} orderModules called for each CSS source type (CSS_IMPORT_TYPE, CSS_TYPE) with the chunk's modules pre-sorted by full module name; return an ordered `Module[]` to override the default import-order topological sort, or return `undefined` to keep the default
104
- */
105
-
106
98
  /**
107
99
  * Defines the module factory cache entry type used by this module.
108
100
  * @typedef {object} ModuleFactoryCacheEntry
@@ -220,6 +212,34 @@ const generatorValidationOptions = {
220
212
 
221
213
  const PLUGIN_NAME = "CssModulesPlugin";
222
214
 
215
+ const createCompilationHooks = () => ({
216
+ /**
217
+ * @type {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>}
218
+ * @since 5.94.0
219
+ */
220
+ renderModulePackage: new SyncWaterfallHook([
221
+ "source",
222
+ "module",
223
+ "renderContext"
224
+ ]),
225
+ /**
226
+ * @type {SyncHook<[Chunk, Hash, ChunkHashContext]>}
227
+ * @since 5.94.0
228
+ */
229
+ chunkHash: new SyncHook(["chunk", "hash", "context"]),
230
+ /**
231
+ * Called for each CSS source type (CSS_IMPORT_TYPE, CSS_TYPE) with the chunk's modules pre-sorted by full module name; return an ordered `Module[]` to override the default import-order topological sort, or return `undefined` to keep the default.
232
+ * @type {SyncBailHook<[Chunk, Module[], Compilation], Module[] | undefined | void>}
233
+ * @since 5.107.0
234
+ */
235
+ orderModules: new SyncBailHook(["chunk", "modules", "compilation"])
236
+ });
237
+
238
+ /**
239
+ * Defines the compilation hooks type used by this module.
240
+ * @typedef {ReturnType<typeof createCompilationHooks>} CompilationHooks
241
+ */
242
+
223
243
  class CssModulesPlugin {
224
244
  constructor() {
225
245
  /** @type {WeakMap<Source, ModuleFactoryCacheEntry>} */
@@ -1175,16 +1195,7 @@ class CssModulesPlugin {
1175
1195
  }
1176
1196
 
1177
1197
  CssModulesPlugin.getCompilationHooks = createHooksRegistry(
1178
- () =>
1179
- /** @type {CompilationHooks} */ ({
1180
- renderModulePackage: new SyncWaterfallHook([
1181
- "source",
1182
- "module",
1183
- "renderContext"
1184
- ]),
1185
- chunkHash: new SyncHook(["chunk", "hash", "context"]),
1186
- orderModules: new SyncBailHook(["chunk", "modules", "compilation"])
1187
- })
1198
+ createCompilationHooks
1188
1199
  );
1189
1200
 
1190
1201
  module.exports = CssModulesPlugin;
@@ -9,6 +9,7 @@ const Template = require("../Template");
9
9
  const { equals } = require("../util/ArrayHelpers");
10
10
  const { getTrimmedIdsAndRange } = require("../util/chainedImports");
11
11
  const makeSerializable = require("../util/makeSerializable");
12
+ const memoize = require("../util/memoize");
12
13
  const { propertyAccess } = require("../util/property");
13
14
  const {
14
15
  ESM_MODULE_EXPORTS_NAME,
@@ -19,6 +20,7 @@ const ModuleDependency = require("./ModuleDependency");
19
20
 
20
21
  /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
21
22
  /** @typedef {import("../Dependency")} Dependency */
23
+ /** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */
22
24
  /** @typedef {import("../Dependency").ReferencedExports} ReferencedExports */
23
25
  /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
24
26
  /** @typedef {import("../ModuleGraph")} ModuleGraph */
@@ -26,8 +28,11 @@ const ModuleDependency = require("./ModuleDependency");
26
28
  /** @typedef {import("../ExportsInfo").ExportInfoName} ExportInfoName */
27
29
  /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
28
30
  /** @typedef {import("../util/chainedImports").IdRanges} IdRanges */
29
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[ExportInfoName[], IdRanges | undefined, boolean, undefined | boolean]>} ObjectDeserializerContext */
30
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[ExportInfoName[], IdRanges | undefined, boolean, undefined | boolean]>} ObjectSerializerContext */
31
+ /** @typedef {import("./HarmonyImportGuard").DependencyGuard} DependencyGuard */
32
+ /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[ExportInfoName[], IdRanges | undefined, boolean, undefined | boolean, DependencyGuard[] | undefined]>} ObjectDeserializerContext */
33
+ /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[ExportInfoName[], IdRanges | undefined, boolean, undefined | boolean, DependencyGuard[] | undefined]>} ObjectSerializerContext */
34
+
35
+ const getHarmonyImportGuard = memoize(() => require("./HarmonyImportGuard"));
31
36
 
32
37
  class CommonJsFullRequireDependency extends ModuleDependency {
33
38
  /**
@@ -53,6 +58,20 @@ class CommonJsFullRequireDependency extends ModuleDependency {
53
58
  this.call = false;
54
59
  /** @type {undefined | boolean} */
55
60
  this.asiSafe = undefined;
61
+ /** @type {DependencyGuard[] | undefined} */
62
+ this.branchGuards = undefined;
63
+ }
64
+
65
+ /**
66
+ * Returns function to determine if the connection is active.
67
+ * @param {ModuleGraph} moduleGraph module graph
68
+ * @returns {null | false | GetConditionFn} function to determine if the connection is active
69
+ */
70
+ getCondition(moduleGraph) {
71
+ const guards = this.branchGuards;
72
+ if (guards === undefined) return null;
73
+ return (connection, runtime) =>
74
+ !getHarmonyImportGuard().isDeadByGuards(guards, moduleGraph, runtime);
56
75
  }
57
76
 
58
77
  /**
@@ -92,7 +111,8 @@ class CommonJsFullRequireDependency extends ModuleDependency {
92
111
  .write(this.names)
93
112
  .write(this.idRanges)
94
113
  .write(this.call)
95
- .write(this.asiSafe);
114
+ .write(this.asiSafe)
115
+ .write(this.branchGuards);
96
116
  super.serialize(context);
97
117
  }
98
118
 
@@ -108,7 +128,9 @@ class CommonJsFullRequireDependency extends ModuleDependency {
108
128
  this.call = c2.read();
109
129
  const c3 = c2.rest;
110
130
  this.asiSafe = c3.read();
111
- super.deserialize(c3.rest);
131
+ const c4 = c3.rest;
132
+ this.branchGuards = c4.read();
133
+ super.deserialize(c4.rest);
112
134
  }
113
135
 
114
136
  get type() {
@@ -137,6 +159,13 @@ CommonJsFullRequireDependency.Template = class CommonJsFullRequireDependencyTemp
137
159
  ) {
138
160
  const dep = /** @type {CommonJsFullRequireDependency} */ (dependency);
139
161
  if (!dep.range) return;
162
+ const connection = moduleGraph.getConnection(dep);
163
+ // Dead branch: module is excluded and has no id; code is never executed.
164
+ if (connection && !connection.isTargetActive(runtime)) {
165
+ // Replaces the whole member chain, so no property access is left dangling
166
+ source.replace(dep.range[0], dep.range[1] - 1, "null /* dead branch */");
167
+ return;
168
+ }
140
169
  const importedModule = moduleGraph.getModule(dep);
141
170
  let requireExpr = runtimeTemplate.moduleExports({
142
171
  module: importedModule,
@@ -642,6 +642,7 @@ class CommonJsImportsParserPlugin {
642
642
  dep.optional = Boolean(parser.scope.inTry);
643
643
  dep.loc = parser.getLocation(expr);
644
644
  parser.state.current.addDependency(dep);
645
+ getHarmonyImportGuard().attachDependencyGuards(parser, dep);
645
646
  return true;
646
647
  }
647
648
  };
@@ -680,6 +681,7 @@ class CommonJsImportsParserPlugin {
680
681
  dep.optional = Boolean(parser.scope.inTry);
681
682
  dep.loc = parser.getLocation(expr.callee);
682
683
  parser.state.current.addDependency(dep);
684
+ getHarmonyImportGuard().attachDependencyGuards(parser, dep);
683
685
  parser.walkExpressions(expr.arguments);
684
686
  return true;
685
687
  }
@@ -14,6 +14,7 @@ const memoize = require("../util/memoize");
14
14
  /** @typedef {import("../ModuleGraph")} ModuleGraph */
15
15
  /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
16
16
  /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
17
+ /** @typedef {import("./CommonJsFullRequireDependency")} CommonJsFullRequireDependency */
17
18
  /** @typedef {import("./CommonJsRequireDependency")} CommonJsRequireDependency */
18
19
  /** @typedef {import("./HarmonyEvaluatedImportSpecifierDependency")} HarmonyEvaluatedImportSpecifierDependency */
19
20
  /** @typedef {import("./HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */
@@ -29,7 +30,7 @@ const memoize = require("../util/memoize");
29
30
  /** @typedef {["&&" | "||" | "??", GuardFormula, GuardFormula]} GuardLogical */
30
31
  /** @typedef {GuardAtom | GuardPresenceAtom | GuardUnknown | GuardNot | GuardLogical} GuardFormula */
31
32
  /** @typedef {{ formula: GuardFormula, value: boolean }} DependencyGuard branch guard: the dependency is live only when the formula evaluates to `value` */
32
- /** @typedef {HarmonyImportSpecifierDependency | CommonJsRequireDependency | ImportDependency} GuardableDependency */
33
+ /** @typedef {HarmonyImportSpecifierDependency | CommonJsRequireDependency | CommonJsFullRequireDependency | ImportDependency} GuardableDependency */
33
34
 
34
35
  /**
35
36
  * A guard frame pushed onto `parser.state.guardStack` for the duration of one