webpack 5.58.0 → 5.59.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.

Potentially problematic release.


This version of webpack might be problematic. Click here for more details.

@@ -5,10 +5,11 @@
5
5
 
6
6
  "use strict";
7
7
 
8
- const ChunkCombination = require("../ChunkCombination");
8
+ const Chunk = require("../Chunk");
9
9
  const { STAGE_ADVANCED } = require("../OptimizationStages");
10
10
  const WebpackError = require("../WebpackError");
11
11
  const { requestToId } = require("../ids/IdHelpers");
12
+ const { isSubset } = require("../util/SetHelpers");
12
13
  const SortableSet = require("../util/SortableSet");
13
14
  const {
14
15
  compareModulesByIdentifier,
@@ -25,7 +26,6 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
25
26
  /** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksOptions} OptimizationSplitChunksOptions */
26
27
  /** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksSizes} OptimizationSplitChunksSizes */
27
28
  /** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */
28
- /** @typedef {import("../Chunk")} Chunk */
29
29
  /** @typedef {import("../ChunkGraph")} ChunkGraph */
30
30
  /** @typedef {import("../ChunkGroup")} ChunkGroup */
31
31
  /** @typedef {import("../Compilation").AssetInfo} AssetInfo */
@@ -59,6 +59,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
59
59
  * @property {ChunkFilterFunction=} chunksFilter
60
60
  * @property {boolean=} enforce
61
61
  * @property {SplitChunksSizes} minSize
62
+ * @property {SplitChunksSizes} minSizeReduction
62
63
  * @property {SplitChunksSizes} minRemainingSize
63
64
  * @property {SplitChunksSizes} enforceSizeThreshold
64
65
  * @property {SplitChunksSizes} maxAsyncSize
@@ -80,6 +81,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
80
81
  * @property {GetName=} getName
81
82
  * @property {ChunkFilterFunction=} chunksFilter
82
83
  * @property {SplitChunksSizes} minSize
84
+ * @property {SplitChunksSizes} minSizeReduction
83
85
  * @property {SplitChunksSizes} minRemainingSize
84
86
  * @property {SplitChunksSizes} enforceSizeThreshold
85
87
  * @property {SplitChunksSizes} maxAsyncSize
@@ -132,6 +134,7 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
132
134
  * @property {ChunkFilterFunction} chunksFilter
133
135
  * @property {string[]} defaultSizeTypes
134
136
  * @property {SplitChunksSizes} minSize
137
+ * @property {SplitChunksSizes} minSizeReduction
135
138
  * @property {SplitChunksSizes} minRemainingSize
136
139
  * @property {SplitChunksSizes} enforceSizeThreshold
137
140
  * @property {SplitChunksSizes} maxInitialSize
@@ -155,9 +158,9 @@ const MinMaxSizeWarning = require("./MinMaxSizeWarning");
155
158
  * @property {number} cacheGroupIndex
156
159
  * @property {string} name
157
160
  * @property {Record<string, number>} sizes
158
- * @property {ChunkCombination} chunks
161
+ * @property {Set<Chunk>} chunks
159
162
  * @property {Set<Chunk>} reuseableChunks
160
- * @property {Set<ChunkCombination>} chunkCombinations
163
+ * @property {Set<bigint | Chunk>} chunksKeys
161
164
  */
162
165
 
163
166
  const defaultGetName = /** @type {GetName} */ (() => {});
@@ -204,6 +207,19 @@ const mapObject = (obj, fn) => {
204
207
  return newObj;
205
208
  };
206
209
 
210
+ /**
211
+ * @template T
212
+ * @param {Set<T>} a set
213
+ * @param {Set<T>} b other set
214
+ * @returns {boolean} true if at least one item of a is in b
215
+ */
216
+ const isOverlap = (a, b) => {
217
+ for (const item of a) {
218
+ if (b.has(item)) return true;
219
+ }
220
+ return false;
221
+ };
222
+
207
223
  const compareModuleIterables = compareIterables(compareModulesByIdentifier);
208
224
 
209
225
  /**
@@ -323,6 +339,21 @@ const checkMinSize = (sizes, minSize) => {
323
339
  return true;
324
340
  };
325
341
 
342
+ /**
343
+ * @param {SplitChunksSizes} sizes the sizes
344
+ * @param {SplitChunksSizes} minSizeReduction the min sizes
345
+ * @param {number} chunkCount number of chunks
346
+ * @returns {boolean} true if there are sizes and all existing sizes are at least `minSizeReduction`
347
+ */
348
+ const checkMinSizeReduction = (sizes, minSizeReduction, chunkCount) => {
349
+ for (const key of Object.keys(minSizeReduction)) {
350
+ const size = sizes[key];
351
+ if (size === undefined || size === 0) continue;
352
+ if (size * chunkCount < minSizeReduction[key]) return false;
353
+ }
354
+ return true;
355
+ };
356
+
326
357
  /**
327
358
  * @param {SplitChunksSizes} sizes the sizes
328
359
  * @param {SplitChunksSizes} minSize the min sizes
@@ -535,6 +566,10 @@ const checkModuleLayer = (test, module) => {
535
566
  */
536
567
  const createCacheGroupSource = (options, key, defaultSizeTypes) => {
537
568
  const minSize = normalizeSizes(options.minSize, defaultSizeTypes);
569
+ const minSizeReduction = normalizeSizes(
570
+ options.minSizeReduction,
571
+ defaultSizeTypes
572
+ );
538
573
  const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes);
539
574
  return {
540
575
  key,
@@ -543,6 +578,7 @@ const createCacheGroupSource = (options, key, defaultSizeTypes) => {
543
578
  chunksFilter: normalizeChunksFilter(options.chunks),
544
579
  enforce: options.enforce,
545
580
  minSize,
581
+ minSizeReduction,
546
582
  minRemainingSize: mergeSizes(
547
583
  normalizeSizes(options.minRemainingSize, defaultSizeTypes),
548
584
  minSize
@@ -581,6 +617,10 @@ module.exports = class SplitChunksPlugin {
581
617
  ];
582
618
  const fallbackCacheGroup = options.fallbackCacheGroup || {};
583
619
  const minSize = normalizeSizes(options.minSize, defaultSizeTypes);
620
+ const minSizeReduction = normalizeSizes(
621
+ options.minSizeReduction,
622
+ defaultSizeTypes
623
+ );
584
624
  const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes);
585
625
 
586
626
  /** @type {SplitChunksOptions} */
@@ -588,6 +628,7 @@ module.exports = class SplitChunksPlugin {
588
628
  chunksFilter: normalizeChunksFilter(options.chunks || "all"),
589
629
  defaultSizeTypes,
590
630
  minSize,
631
+ minSizeReduction,
591
632
  minRemainingSize: mergeSizes(
592
633
  normalizeSizes(options.minRemainingSize, defaultSizeTypes),
593
634
  minSize
@@ -655,6 +696,10 @@ module.exports = class SplitChunksPlugin {
655
696
  cacheGroupSource.minSize,
656
697
  cacheGroupSource.enforce ? undefined : this.options.minSize
657
698
  );
699
+ const minSizeReduction = mergeSizes(
700
+ cacheGroupSource.minSizeReduction,
701
+ cacheGroupSource.enforce ? undefined : this.options.minSizeReduction
702
+ );
658
703
  const minRemainingSize = mergeSizes(
659
704
  cacheGroupSource.minRemainingSize,
660
705
  cacheGroupSource.enforce ? undefined : this.options.minRemainingSize
@@ -668,6 +713,7 @@ module.exports = class SplitChunksPlugin {
668
713
  priority: cacheGroupSource.priority || 0,
669
714
  chunksFilter: cacheGroupSource.chunksFilter || this.options.chunksFilter,
670
715
  minSize,
716
+ minSizeReduction,
671
717
  minRemainingSize,
672
718
  enforceSizeThreshold,
673
719
  maxAsyncSize: mergeSizes(
@@ -756,132 +802,212 @@ module.exports = class SplitChunksPlugin {
756
802
  logger.time("prepare");
757
803
  const chunkGraph = compilation.chunkGraph;
758
804
  const moduleGraph = compilation.moduleGraph;
805
+ // Give each selected chunk an index (to create strings from chunks)
806
+ /** @type {Map<Chunk, bigint>} */
807
+ const chunkIndexMap = new Map();
808
+ const ZERO = BigInt("0");
809
+ const ONE = BigInt("1");
810
+ const START = ONE << BigInt("31");
811
+ let index = START;
812
+ for (const chunk of chunks) {
813
+ chunkIndexMap.set(
814
+ chunk,
815
+ index | BigInt((Math.random() * 0x7fffffff) | 0)
816
+ );
817
+ index = index << ONE;
818
+ }
819
+ /**
820
+ * @param {Iterable<Chunk>} chunks list of chunks
821
+ * @returns {bigint | Chunk} key of the chunks
822
+ */
823
+ const getKey = chunks => {
824
+ const iterator = chunks[Symbol.iterator]();
825
+ let result = iterator.next();
826
+ if (result.done) return ZERO;
827
+ const first = result.value;
828
+ result = iterator.next();
829
+ if (result.done) return first;
830
+ let key =
831
+ chunkIndexMap.get(first) | chunkIndexMap.get(result.value);
832
+ while (!(result = iterator.next()).done) {
833
+ const raw = chunkIndexMap.get(result.value);
834
+ key = key ^ raw;
835
+ }
836
+ return key;
837
+ };
838
+ const keyToString = key => {
839
+ if (typeof key === "bigint") return key.toString(16);
840
+ return chunkIndexMap.get(key).toString(16);
841
+ };
759
842
 
760
- const getChunkCombinationsInGraph = memoize(() => {
761
- /** @type {Set<ChunkCombination>} */
762
- const chunkCombinationsInGraph = new Set();
843
+ const getChunkSetsInGraph = memoize(() => {
844
+ /** @type {Map<bigint, Set<Chunk>>} */
845
+ const chunkSetsInGraph = new Map();
846
+ /** @type {Set<Chunk>} */
847
+ const singleChunkSets = new Set();
763
848
  for (const module of compilation.modules) {
764
- const chunkCombination =
765
- chunkGraph.getModuleChunkCombination(module);
766
- chunkCombinationsInGraph.add(chunkCombination);
849
+ const chunks = chunkGraph.getModuleChunksIterable(module);
850
+ const chunksKey = getKey(chunks);
851
+ if (typeof chunksKey === "bigint") {
852
+ if (!chunkSetsInGraph.has(chunksKey)) {
853
+ chunkSetsInGraph.set(chunksKey, new Set(chunks));
854
+ }
855
+ } else {
856
+ singleChunkSets.add(chunksKey);
857
+ }
767
858
  }
768
- return chunkCombinationsInGraph;
859
+ return { chunkSetsInGraph, singleChunkSets };
769
860
  });
770
861
 
771
862
  /**
772
863
  * @param {Module} module the module
773
- * @returns {Iterable<ChunkCombination>} groups of chunks with equal exports
864
+ * @returns {Iterable<Chunk[]>} groups of chunks with equal exports
774
865
  */
775
866
  const groupChunksByExports = module => {
776
867
  const exportsInfo = moduleGraph.getExportsInfo(module);
777
868
  const groupedByUsedExports = new Map();
778
869
  for (const chunk of chunkGraph.getModuleChunksIterable(module)) {
779
870
  const key = exportsInfo.getUsageKey(chunk.runtime);
780
- const combination =
781
- groupedByUsedExports.get(key) || ChunkCombination.empty;
782
- groupedByUsedExports.set(key, combination.with(chunk));
871
+ const list = groupedByUsedExports.get(key);
872
+ if (list !== undefined) {
873
+ list.push(chunk);
874
+ } else {
875
+ groupedByUsedExports.set(key, [chunk]);
876
+ }
783
877
  }
784
878
  return groupedByUsedExports.values();
785
879
  };
786
880
 
787
- /** @type {Map<Module, Iterable<ChunkCombination>>} */
881
+ /** @type {Map<Module, Iterable<Chunk[]>>} */
788
882
  const groupedByExportsMap = new Map();
789
883
 
790
- const getExportsChunkCombinationsInGraph = memoize(() => {
791
- /** @type {Set<ChunkCombination>} */
792
- const chunkCombinationsInGraph = new Set();
884
+ const getExportsChunkSetsInGraph = memoize(() => {
885
+ /** @type {Map<bigint, Set<Chunk>>} */
886
+ const chunkSetsInGraph = new Map();
887
+ /** @type {Set<Chunk>} */
888
+ const singleChunkSets = new Set();
793
889
  for (const module of compilation.modules) {
794
890
  const groupedChunks = Array.from(groupChunksByExports(module));
795
891
  groupedByExportsMap.set(module, groupedChunks);
796
- for (const chunkCombination of groupedChunks) {
797
- chunkCombinationsInGraph.add(chunkCombination);
892
+ for (const chunks of groupedChunks) {
893
+ if (chunks.length === 1) {
894
+ singleChunkSets.add(chunks[0]);
895
+ } else {
896
+ const chunksKey = /** @type {bigint} */ (getKey(chunks));
897
+ if (!chunkSetsInGraph.has(chunksKey)) {
898
+ chunkSetsInGraph.set(chunksKey, new Set(chunks));
899
+ }
900
+ }
798
901
  }
799
902
  }
800
- return chunkCombinationsInGraph;
903
+ return { chunkSetsInGraph, singleChunkSets };
801
904
  });
802
905
 
803
906
  // group these set of chunks by count
804
907
  // to allow to check less sets via isSubset
805
908
  // (only smaller sets can be subset)
806
- const groupChunkCombinationsByCount = chunkCombinations => {
807
- /** @type {Map<number, ChunkCombination[]>} */
808
- const chunkCombinationsByCount = new Map();
809
- for (const chunksSet of chunkCombinations) {
909
+ const groupChunkSetsByCount = chunkSets => {
910
+ /** @type {Map<number, Array<Set<Chunk>>>} */
911
+ const chunkSetsByCount = new Map();
912
+ for (const chunksSet of chunkSets) {
810
913
  const count = chunksSet.size;
811
- let array = chunkCombinationsByCount.get(count);
914
+ let array = chunkSetsByCount.get(count);
812
915
  if (array === undefined) {
813
916
  array = [];
814
- chunkCombinationsByCount.set(count, array);
917
+ chunkSetsByCount.set(count, array);
815
918
  }
816
919
  array.push(chunksSet);
817
920
  }
818
- return chunkCombinationsByCount;
921
+ return chunkSetsByCount;
819
922
  };
820
- const getChunkCombinationsByCount = memoize(() =>
821
- groupChunkCombinationsByCount(getChunkCombinationsInGraph())
923
+ const getChunkSetsByCount = memoize(() =>
924
+ groupChunkSetsByCount(
925
+ getChunkSetsInGraph().chunkSetsInGraph.values()
926
+ )
822
927
  );
823
- const getExportsChunkCombinationsByCount = memoize(() =>
824
- groupChunkCombinationsByCount(getExportsChunkCombinationsInGraph())
928
+ const getExportsChunkSetsByCount = memoize(() =>
929
+ groupChunkSetsByCount(
930
+ getExportsChunkSetsInGraph().chunkSetsInGraph.values()
931
+ )
825
932
  );
826
933
 
827
- /**
828
- * Create a list of possible combinations
829
- * @param {Map<number, ChunkCombination[]>} chunkCombinationsByCount by count
830
- * @returns {function(ChunkCombination): ChunkCombination[]} get combinations function
831
- */
832
- const createGetCombinations = chunkCombinationsByCount => {
833
- /** @type {Map<ChunkCombination, ChunkCombination[]>} */
934
+ // Create a list of possible combinations
935
+ const createGetCombinations = (
936
+ chunkSets,
937
+ singleChunkSets,
938
+ chunkSetsByCount
939
+ ) => {
940
+ /** @type {Map<bigint | Chunk, (Set<Chunk> | Chunk)[]>} */
834
941
  const combinationsCache = new Map();
835
942
 
836
- /**
837
- * @param {ChunkCombination} chunkCombination chunkCombination
838
- * @returns {ChunkCombination[]} combinations
839
- */
840
- return chunkCombination => {
841
- const cacheEntry = combinationsCache.get(chunkCombination);
943
+ return key => {
944
+ const cacheEntry = combinationsCache.get(key);
842
945
  if (cacheEntry !== undefined) return cacheEntry;
843
- if (chunkCombination.size === 1) {
844
- const result = [chunkCombination];
845
- combinationsCache.set(chunkCombination, result);
946
+ if (key instanceof Chunk) {
947
+ const result = [key];
948
+ combinationsCache.set(key, result);
846
949
  return result;
847
950
  }
848
- /** @type {ChunkCombination[]} */
849
- const array = [chunkCombination];
850
- for (const [count, setArray] of chunkCombinationsByCount) {
951
+ const chunksSet = chunkSets.get(key);
952
+ /** @type {(Set<Chunk> | Chunk)[]} */
953
+ const array = [chunksSet];
954
+ for (const [count, setArray] of chunkSetsByCount) {
851
955
  // "equal" is not needed because they would have been merge in the first step
852
- if (count < chunkCombination.size) {
956
+ if (count < chunksSet.size) {
853
957
  for (const set of setArray) {
854
- if (chunkCombination.isSubset(set)) {
958
+ if (isSubset(chunksSet, set)) {
855
959
  array.push(set);
856
960
  }
857
961
  }
858
962
  }
859
963
  }
860
- combinationsCache.set(chunkCombination, array);
964
+ for (const chunk of singleChunkSets) {
965
+ if (chunksSet.has(chunk)) {
966
+ array.push(chunk);
967
+ }
968
+ }
969
+ combinationsCache.set(key, array);
861
970
  return array;
862
971
  };
863
972
  };
864
973
 
865
974
  const getCombinationsFactory = memoize(() => {
866
- return createGetCombinations(getChunkCombinationsByCount());
975
+ const { chunkSetsInGraph, singleChunkSets } = getChunkSetsInGraph();
976
+ return createGetCombinations(
977
+ chunkSetsInGraph,
978
+ singleChunkSets,
979
+ getChunkSetsByCount()
980
+ );
867
981
  });
868
982
  const getCombinations = key => getCombinationsFactory()(key);
869
983
 
870
984
  const getExportsCombinationsFactory = memoize(() => {
871
- return createGetCombinations(getExportsChunkCombinationsByCount());
985
+ const { chunkSetsInGraph, singleChunkSets } =
986
+ getExportsChunkSetsInGraph();
987
+ return createGetCombinations(
988
+ chunkSetsInGraph,
989
+ singleChunkSets,
990
+ getExportsChunkSetsByCount()
991
+ );
872
992
  });
873
993
  const getExportsCombinations = key =>
874
994
  getExportsCombinationsFactory()(key);
875
995
 
876
- /** @type {WeakMap<ChunkCombination, WeakMap<ChunkFilterFunction, ChunkCombination>>} */
996
+ /**
997
+ * @typedef {Object} SelectedChunksResult
998
+ * @property {Chunk[]} chunks the list of chunks
999
+ * @property {bigint | Chunk} key a key of the list
1000
+ */
1001
+
1002
+ /** @type {WeakMap<Set<Chunk> | Chunk, WeakMap<ChunkFilterFunction, SelectedChunksResult>>} */
877
1003
  const selectedChunksCacheByChunksSet = new WeakMap();
878
1004
 
879
1005
  /**
880
- * get chunks by applying the filter function to the list
1006
+ * get list and key by applying the filter function to the list
881
1007
  * It is cached for performance reasons
882
- * @param {ChunkCombination} chunks list of chunks
1008
+ * @param {Set<Chunk> | Chunk} chunks list of chunks
883
1009
  * @param {ChunkFilterFunction} chunkFilter filter function for chunks
884
- * @returns {ChunkCombination} selected chunks
1010
+ * @returns {SelectedChunksResult} list and key
885
1011
  */
886
1012
  const getSelectedChunks = (chunks, chunkFilter) => {
887
1013
  let entry = selectedChunksCacheByChunksSet.get(chunks);
@@ -889,16 +1015,22 @@ module.exports = class SplitChunksPlugin {
889
1015
  entry = new WeakMap();
890
1016
  selectedChunksCacheByChunksSet.set(chunks, entry);
891
1017
  }
892
- /** @type {ChunkCombination} */
1018
+ /** @type {SelectedChunksResult} */
893
1019
  let entry2 = entry.get(chunkFilter);
894
1020
  if (entry2 === undefined) {
895
- /** @type {ChunkCombination} */
896
- let selectedChunks = ChunkCombination.empty;
897
- for (const chunk of chunks.chunksIterable) {
898
- if (chunkFilter(chunk))
899
- selectedChunks = selectedChunks.with(chunk);
1021
+ /** @type {Chunk[]} */
1022
+ const selectedChunks = [];
1023
+ if (chunks instanceof Chunk) {
1024
+ if (chunkFilter(chunks)) selectedChunks.push(chunks);
1025
+ } else {
1026
+ for (const chunk of chunks) {
1027
+ if (chunkFilter(chunk)) selectedChunks.push(chunk);
1028
+ }
900
1029
  }
901
- entry2 = selectedChunks;
1030
+ entry2 = {
1031
+ chunks: selectedChunks,
1032
+ key: getKey(selectedChunks)
1033
+ };
902
1034
  entry.set(chunkFilter, entry2);
903
1035
  }
904
1036
  return entry2;
@@ -917,7 +1049,8 @@ module.exports = class SplitChunksPlugin {
917
1049
  /**
918
1050
  * @param {CacheGroup} cacheGroup the current cache group
919
1051
  * @param {number} cacheGroupIndex the index of the cache group of ordering
920
- * @param {ChunkCombination} selectedChunks chunks selected for this module
1052
+ * @param {Chunk[]} selectedChunks chunks selected for this module
1053
+ * @param {bigint | Chunk} selectedChunksKey a key of selectedChunks
921
1054
  * @param {Module} module the current module
922
1055
  * @returns {void}
923
1056
  */
@@ -925,20 +1058,25 @@ module.exports = class SplitChunksPlugin {
925
1058
  cacheGroup,
926
1059
  cacheGroupIndex,
927
1060
  selectedChunks,
1061
+ selectedChunksKey,
928
1062
  module
929
1063
  ) => {
930
1064
  // Break if minimum number of chunks is not reached
931
- if (selectedChunks.size < cacheGroup.minChunks) return;
1065
+ if (selectedChunks.length < cacheGroup.minChunks) return;
932
1066
  // Determine name for split chunk
933
1067
  const name = cacheGroup.getName(
934
1068
  module,
935
- selectedChunks.getChunks(),
1069
+ selectedChunks,
936
1070
  cacheGroup.key
937
1071
  );
938
1072
  // Check if the name is ok
939
1073
  const existingChunk = compilation.namedChunks.get(name);
940
1074
  if (existingChunk) {
941
- const parentValidationKey = `${name}|${selectedChunks.debugId}`;
1075
+ const parentValidationKey = `${name}|${
1076
+ typeof selectedChunksKey === "bigint"
1077
+ ? selectedChunksKey
1078
+ : selectedChunksKey.debugId
1079
+ }`;
942
1080
  const valid = alreadyValidatedParents.get(parentValidationKey);
943
1081
  if (valid === false) return;
944
1082
  if (valid === undefined) {
@@ -947,7 +1085,7 @@ module.exports = class SplitChunksPlugin {
947
1085
  let isInAllParents = true;
948
1086
  /** @type {Set<ChunkGroup>} */
949
1087
  const queue = new Set();
950
- for (const chunk of selectedChunks.chunksIterable) {
1088
+ for (const chunk of selectedChunks) {
951
1089
  for (const group of chunk.groupsIterable) {
952
1090
  queue.add(group);
953
1091
  }
@@ -993,7 +1131,9 @@ module.exports = class SplitChunksPlugin {
993
1131
  // This automatically merges equal names
994
1132
  const key =
995
1133
  cacheGroup.key +
996
- (name ? ` name:${name}` : ` chunks:${selectedChunks.debugId}`);
1134
+ (name
1135
+ ? ` name:${name}`
1136
+ : ` chunks:${keyToString(selectedChunksKey)}`);
997
1137
  // Add module to maps
998
1138
  let info = chunksInfoMap.get(key);
999
1139
  if (info === undefined) {
@@ -1008,9 +1148,9 @@ module.exports = class SplitChunksPlugin {
1008
1148
  cacheGroupIndex,
1009
1149
  name,
1010
1150
  sizes: {},
1011
- chunks: ChunkCombination.empty,
1151
+ chunks: new Set(),
1012
1152
  reuseableChunks: new Set(),
1013
- chunkCombinations: new Set()
1153
+ chunksKeys: new Set()
1014
1154
  })
1015
1155
  );
1016
1156
  }
@@ -1021,10 +1161,12 @@ module.exports = class SplitChunksPlugin {
1021
1161
  info.sizes[type] = (info.sizes[type] || 0) + module.size(type);
1022
1162
  }
1023
1163
  }
1024
- const oldChunksKeysSize = info.chunkCombinations.size;
1025
- info.chunkCombinations.add(selectedChunks);
1026
- if (oldChunksKeysSize !== info.chunkCombinations.size) {
1027
- info.chunks = info.chunks.withAll(selectedChunks);
1164
+ const oldChunksKeysSize = info.chunksKeys.size;
1165
+ info.chunksKeys.add(selectedChunksKey);
1166
+ if (oldChunksKeysSize !== info.chunksKeys.size) {
1167
+ for (const chunk of selectedChunks) {
1168
+ info.chunks.add(chunk);
1169
+ }
1028
1170
  }
1029
1171
  };
1030
1172
 
@@ -1045,56 +1187,50 @@ module.exports = class SplitChunksPlugin {
1045
1187
  continue;
1046
1188
  }
1047
1189
 
1048
- const chunkCombination =
1049
- chunkGraph.getModuleChunkCombination(module);
1190
+ // Prepare some values (usedExports = false)
1191
+ const getCombs = memoize(() => {
1192
+ const chunks = chunkGraph.getModuleChunksIterable(module);
1193
+ const chunksKey = getKey(chunks);
1194
+ return getCombinations(chunksKey);
1195
+ });
1196
+
1197
+ // Prepare some values (usedExports = true)
1198
+ const getCombsByUsedExports = memoize(() => {
1199
+ // fill the groupedByExportsMap
1200
+ getExportsChunkSetsInGraph();
1201
+ /** @type {Set<Set<Chunk> | Chunk>} */
1202
+ const set = new Set();
1203
+ const groupedByUsedExports = groupedByExportsMap.get(module);
1204
+ for (const chunks of groupedByUsedExports) {
1205
+ const chunksKey = getKey(chunks);
1206
+ for (const comb of getExportsCombinations(chunksKey))
1207
+ set.add(comb);
1208
+ }
1209
+ return set;
1210
+ });
1050
1211
 
1051
1212
  let cacheGroupIndex = 0;
1052
1213
  for (const cacheGroupSource of cacheGroups) {
1053
1214
  const cacheGroup = this._getCacheGroup(cacheGroupSource);
1054
1215
 
1055
- // Break if minimum number of chunks is not reached
1056
- if (chunkCombination.size < cacheGroup.minChunks) continue;
1057
-
1058
- /** @type {Iterable<ChunkCombination>} */
1059
- let combs;
1060
- if (cacheGroup.usedExports) {
1061
- // fill the groupedByExportsMap
1062
- getExportsChunkCombinationsInGraph();
1063
- /** @type {Set<ChunkCombination>} */
1064
- const set = new Set();
1065
- const groupedByUsedExports = groupedByExportsMap.get(module);
1066
- for (const chunkCombination of groupedByUsedExports) {
1067
- const preSelectedChunks = getSelectedChunks(
1068
- chunkCombination,
1069
- cacheGroup.chunksFilter
1070
- );
1071
- // Break if minimum number of chunks is not reached
1072
- if (preSelectedChunks.size < cacheGroup.minChunks) continue;
1073
-
1074
- for (const comb of getExportsCombinations(preSelectedChunks))
1075
- set.add(comb);
1076
- }
1077
- combs = set;
1078
- } else {
1079
- const preSelectedChunks = getSelectedChunks(
1080
- chunkCombination,
1081
- cacheGroup.chunksFilter
1082
- );
1083
- // Break if minimum number of chunks is not reached
1084
- if (preSelectedChunks.size < cacheGroup.minChunks) continue;
1085
-
1086
- combs = getCombinations(preSelectedChunks);
1087
- }
1216
+ const combs = cacheGroup.usedExports
1217
+ ? getCombsByUsedExports()
1218
+ : getCombs();
1088
1219
  // For all combination of chunk selection
1089
- for (const selectedChunks of combs) {
1220
+ for (const chunkCombination of combs) {
1090
1221
  // Break if minimum number of chunks is not reached
1091
- const count = chunkCombination.size;
1222
+ const count =
1223
+ chunkCombination instanceof Chunk ? 1 : chunkCombination.size;
1092
1224
  if (count < cacheGroup.minChunks) continue;
1225
+ // Select chunks by configuration
1226
+ const { chunks: selectedChunks, key: selectedChunksKey } =
1227
+ getSelectedChunks(chunkCombination, cacheGroup.chunksFilter);
1093
1228
 
1094
1229
  addModuleToChunksInfoMap(
1095
1230
  cacheGroup,
1096
1231
  cacheGroupIndex,
1097
1232
  selectedChunks,
1233
+ selectedChunksKey,
1098
1234
  module
1099
1235
  );
1100
1236
  }
@@ -1141,6 +1277,14 @@ module.exports = class SplitChunksPlugin {
1141
1277
  for (const [key, info] of chunksInfoMap) {
1142
1278
  if (removeMinSizeViolatingModules(info)) {
1143
1279
  chunksInfoMap.delete(key);
1280
+ } else if (
1281
+ !checkMinSizeReduction(
1282
+ info.sizes,
1283
+ info.cacheGroup.minSizeReduction,
1284
+ info.chunks.size
1285
+ )
1286
+ ) {
1287
+ chunksInfoMap.delete(key);
1144
1288
  }
1145
1289
  }
1146
1290
 
@@ -1186,12 +1330,12 @@ module.exports = class SplitChunksPlugin {
1186
1330
  const chunkByName = compilation.namedChunks.get(chunkName);
1187
1331
  if (chunkByName !== undefined) {
1188
1332
  newChunk = chunkByName;
1189
- const newChunks = item.chunks.without(newChunk);
1190
- isExistingChunk = newChunks !== item.chunks;
1191
- if (isExistingChunk) item.chunks = newChunks;
1333
+ const oldSize = item.chunks.size;
1334
+ item.chunks.delete(newChunk);
1335
+ isExistingChunk = item.chunks.size !== oldSize;
1192
1336
  }
1193
1337
  } else if (item.cacheGroup.reuseExistingChunk) {
1194
- outer: for (const chunk of item.chunks.chunksIterable) {
1338
+ outer: for (const chunk of item.chunks) {
1195
1339
  if (
1196
1340
  chunkGraph.getNumberOfChunkModules(chunk) !==
1197
1341
  item.modules.size
@@ -1225,7 +1369,7 @@ module.exports = class SplitChunksPlugin {
1225
1369
  }
1226
1370
  }
1227
1371
  if (newChunk) {
1228
- item.chunks = item.chunks.without(newChunk);
1372
+ item.chunks.delete(newChunk);
1229
1373
  chunkName = undefined;
1230
1374
  isExistingChunk = true;
1231
1375
  isReusedWithAllModules = true;
@@ -1236,7 +1380,7 @@ module.exports = class SplitChunksPlugin {
1236
1380
  item.cacheGroup._conditionalEnforce &&
1237
1381
  checkMinSize(item.sizes, item.cacheGroup.enforceSizeThreshold);
1238
1382
 
1239
- let usedChunks = item.chunks;
1383
+ const usedChunks = new Set(item.chunks);
1240
1384
 
1241
1385
  // Check if maxRequests condition can be fulfilled
1242
1386
  if (
@@ -1244,7 +1388,7 @@ module.exports = class SplitChunksPlugin {
1244
1388
  (Number.isFinite(item.cacheGroup.maxInitialRequests) ||
1245
1389
  Number.isFinite(item.cacheGroup.maxAsyncRequests))
1246
1390
  ) {
1247
- for (const chunk of usedChunks.chunksIterable) {
1391
+ for (const chunk of usedChunks) {
1248
1392
  // respect max requests
1249
1393
  const maxRequests = chunk.isOnlyInitial()
1250
1394
  ? item.cacheGroup.maxInitialRequests
@@ -1258,28 +1402,30 @@ module.exports = class SplitChunksPlugin {
1258
1402
  isFinite(maxRequests) &&
1259
1403
  getRequests(chunk) >= maxRequests
1260
1404
  ) {
1261
- usedChunks = usedChunks.without(chunk);
1405
+ usedChunks.delete(chunk);
1262
1406
  }
1263
1407
  }
1264
1408
  }
1265
1409
 
1266
- outer: for (const chunk of usedChunks.chunksIterable) {
1410
+ outer: for (const chunk of usedChunks) {
1267
1411
  for (const module of item.modules) {
1268
1412
  if (chunkGraph.isModuleInChunk(module, chunk)) continue outer;
1269
1413
  }
1270
- usedChunks = usedChunks.without(chunk);
1414
+ usedChunks.delete(chunk);
1271
1415
  }
1272
1416
 
1273
1417
  // Were some (invalid) chunks removed from usedChunks?
1274
1418
  // => readd all modules to the queue, as things could have been changed
1275
- if (usedChunks !== item.chunks) {
1276
- if (isExistingChunk) usedChunks = usedChunks.with(newChunk);
1419
+ if (usedChunks.size < item.chunks.size) {
1420
+ if (isExistingChunk) usedChunks.add(newChunk);
1277
1421
  if (usedChunks.size >= item.cacheGroup.minChunks) {
1422
+ const chunksArr = Array.from(usedChunks);
1278
1423
  for (const module of item.modules) {
1279
1424
  addModuleToChunksInfoMap(
1280
1425
  item.cacheGroup,
1281
1426
  item.cacheGroupIndex,
1282
- usedChunks,
1427
+ chunksArr,
1428
+ getKey(usedChunks),
1283
1429
  module
1284
1430
  );
1285
1431
  }
@@ -1293,7 +1439,7 @@ module.exports = class SplitChunksPlugin {
1293
1439
  item.cacheGroup._validateRemainingSize &&
1294
1440
  usedChunks.size === 1
1295
1441
  ) {
1296
- const [chunk] = usedChunks.chunksIterable;
1442
+ const [chunk] = usedChunks;
1297
1443
  let chunkSizes = Object.create(null);
1298
1444
  for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
1299
1445
  if (!item.modules.has(module)) {
@@ -1327,7 +1473,7 @@ module.exports = class SplitChunksPlugin {
1327
1473
  newChunk = compilation.addChunk(chunkName);
1328
1474
  }
1329
1475
  // Walk through all chunks
1330
- for (const chunk of usedChunks.chunksIterable) {
1476
+ for (const chunk of usedChunks) {
1331
1477
  // Add graph connections for splitted chunk
1332
1478
  chunk.split(newChunk);
1333
1479
  }
@@ -1357,14 +1503,14 @@ module.exports = class SplitChunksPlugin {
1357
1503
  // Add module to new chunk
1358
1504
  chunkGraph.connectChunkAndModule(newChunk, module);
1359
1505
  // Remove module from used chunks
1360
- for (const chunk of usedChunks.chunksIterable) {
1506
+ for (const chunk of usedChunks) {
1361
1507
  chunkGraph.disconnectChunkAndModule(chunk, module);
1362
1508
  }
1363
1509
  }
1364
1510
  } else {
1365
1511
  // Remove all modules from used chunks
1366
1512
  for (const module of item.modules) {
1367
- for (const chunk of usedChunks.chunksIterable) {
1513
+ for (const chunk of usedChunks) {
1368
1514
  chunkGraph.disconnectChunkAndModule(chunk, module);
1369
1515
  }
1370
1516
  }
@@ -1406,7 +1552,7 @@ module.exports = class SplitChunksPlugin {
1406
1552
 
1407
1553
  // remove all modules from other entries and update size
1408
1554
  for (const [key, info] of chunksInfoMap) {
1409
- if (info.chunks.hasSharedChunks(usedChunks)) {
1555
+ if (isOverlap(info.chunks, usedChunks)) {
1410
1556
  // update modules and total size
1411
1557
  // may remove it from the map when < minSize
1412
1558
  let updated = false;
@@ -1426,7 +1572,14 @@ module.exports = class SplitChunksPlugin {
1426
1572
  chunksInfoMap.delete(key);
1427
1573
  continue;
1428
1574
  }
1429
- if (removeMinSizeViolatingModules(info)) {
1575
+ if (
1576
+ removeMinSizeViolatingModules(info) ||
1577
+ !checkMinSizeReduction(
1578
+ info.sizes,
1579
+ info.cacheGroup.minSizeReduction,
1580
+ info.chunks.size
1581
+ )
1582
+ ) {
1430
1583
  chunksInfoMap.delete(key);
1431
1584
  continue;
1432
1585
  }