@atlaspack/bundler-default 2.14.5-canary.3 → 2.14.5-canary.300

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.
package/lib/idealGraph.js CHANGED
@@ -12,6 +12,13 @@ function _path() {
12
12
  };
13
13
  return data;
14
14
  }
15
+ function _sortedArrayFunctions() {
16
+ const data = _interopRequireDefault(require("sorted-array-functions"));
17
+ _sortedArrayFunctions = function () {
18
+ return data;
19
+ };
20
+ return data;
21
+ }
15
22
  function _featureFlags() {
16
23
  const data = require("@atlaspack/feature-flags");
17
24
  _featureFlags = function () {
@@ -47,8 +54,11 @@ function _nullthrows() {
47
54
  };
48
55
  return data;
49
56
  }
57
+ var _bundleMerge = require("./bundleMerge");
58
+ var _stats = require("./stats");
50
59
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
51
60
  /* BundleRoot - An asset that is the main entry of a Bundle. */
61
+
52
62
  const dependencyPriorityEdges = {
53
63
  sync: 1,
54
64
  parallel: 2,
@@ -64,12 +74,19 @@ const idealBundleGraphEdges = exports.idealBundleGraphEdges = Object.freeze({
64
74
  // which mutates the assetGraph into the bundleGraph we would
65
75
  // expect from default bundler
66
76
 
77
+ function isNonRootBundle(bundle, message) {
78
+ let existingBundle = (0, _nullthrows().default)(bundle, message);
79
+ (0, _assert().default)(existingBundle !== 'root', "Bundle cannot be 'root'");
80
+ return existingBundle;
81
+ }
67
82
  function createIdealGraph(assetGraph, config, entries, logger) {
83
+ var _config$sharedBundleM;
68
84
  // Asset to the bundle and group it's an entry of
69
85
  let bundleRoots = new Map();
70
86
  let bundles = new Map();
71
87
  let dependencyBundleGraph = new (_graph().ContentGraph)();
72
88
  let assetReference = new (_utils().DefaultMap)(() => []);
89
+ let stats = new _stats.Stats(config.projectRoot);
73
90
 
74
91
  // A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their
75
92
  // referencing Bundle. There are no actual BundleGroup nodes, just bundles that take on that role.
@@ -107,15 +124,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
107
124
  }
108
125
  let assets = [];
109
126
  let assetToIndex = new Map();
110
- //Manual is a map of the user-given name to the bundle node Id that corresponds to ALL the assets that match any glob in that user-specified array
111
- let manualSharedMap = new Map();
112
- // May need a map to be able to look up NON- bundle root assets which need special case instructions
113
- // Use this when placing assets into bundles, to avoid duplication
114
- let manualAssetToBundle = new Map();
115
- let {
116
- manualAssetToConfig,
117
- constantModuleToMSB
118
- } = function makeManualAssetToConfigLookup() {
127
+ function makeManualAssetToConfigLookup() {
119
128
  let manualAssetToConfig = new Map();
120
129
  let constantModuleToMSB = new (_utils().DefaultMap)(() => []);
121
130
  if (config.manualSharedBundles.length === 0) {
@@ -159,17 +168,15 @@ function createIdealGraph(assetGraph, config, entries, logger) {
159
168
  assetGraph.traverse((node, _, actions) => {
160
169
  if (node.type === 'asset' && (!Array.isArray(c.types) || c.types.includes(node.value.type))) {
161
170
  let projectRelativePath = _path().default.relative(config.projectRoot, node.value.filePath);
162
- if (!assetRegexes.some(regex => regex.test(projectRelativePath))) {
163
- return;
164
- }
165
171
 
166
172
  // We track all matching MSB's for constant modules as they are never duplicated
167
173
  // and need to be assigned to all matching bundles
168
174
  if (node.value.meta.isConstantModule === true) {
169
175
  constantModuleToMSB.get(node.value).push(c);
170
176
  }
171
- manualAssetToConfig.set(node.value, c);
172
- return;
177
+ if (assetRegexes.some(regex => regex.test(projectRelativePath))) {
178
+ manualAssetToConfig.set(node.value, c);
179
+ }
173
180
  }
174
181
  if (node.type === 'dependency' && (node.value.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional') && parentAsset) {
175
182
  // Don't walk past the bundle group assets
@@ -181,8 +188,23 @@ function createIdealGraph(assetGraph, config, entries, logger) {
181
188
  manualAssetToConfig,
182
189
  constantModuleToMSB
183
190
  };
184
- }();
191
+ }
192
+
193
+ //Manual is a map of the user-given name to the bundle node Id that corresponds to ALL the assets that match any glob in that user-specified array
194
+ let manualSharedMap = new Map();
195
+ // May need a map to be able to look up NON- bundle root assets which need special case instructions
196
+ // Use this when placing assets into bundles, to avoid duplication
197
+ let manualAssetToBundle = new Map();
198
+ let {
199
+ manualAssetToConfig,
200
+ constantModuleToMSB
201
+ } = makeManualAssetToConfigLookup();
185
202
  let manualBundleToInternalizedAsset = new (_utils().DefaultMap)(() => []);
203
+ let mergeSourceBundleLookup = new Map();
204
+ let mergeSourceBundleAssets = new Set((_config$sharedBundleM = config.sharedBundleMerge) === null || _config$sharedBundleM === void 0 ? void 0 : _config$sharedBundleM.flatMap(c => {
205
+ var _c$sourceBundles;
206
+ return ((_c$sourceBundles = c.sourceBundles) === null || _c$sourceBundles === void 0 ? void 0 : _c$sourceBundles.map(assetMatch => _path().default.join(config.projectRoot, assetMatch))) ?? [];
207
+ }));
186
208
 
187
209
  /**
188
210
  * Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
@@ -233,15 +255,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
233
255
  manualSharedBundleKey != null && manualSharedMap.has(manualSharedBundleKey)) {
234
256
  bundleId = (0, _nullthrows().default)(manualSharedMap.get(manualSharedBundleKey));
235
257
  }
236
- if (dependency.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional' || childAsset.bundleBehavior === 'isolated' // An isolated Dependency, or Bundle must contain all assets it needs to load.
237
- ) {
258
+ if (dependency.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional' || childAsset.bundleBehavior === 'isolated' ||
259
+ // An isolated Dependency, or Bundle must contain all assets it needs to load.
260
+ childAsset.bundleBehavior === 'inlineIsolated') {
238
261
  if (bundleId == null) {
239
262
  let firstBundleGroup = (0, _nullthrows().default)(bundleGraph.getNode(stack[0][1]));
240
263
  (0, _assert().default)(firstBundleGroup !== 'root');
241
264
  bundle = createBundle({
242
265
  asset: childAsset,
243
266
  bundleBehavior: dependency.bundleBehavior ?? childAsset.bundleBehavior,
244
- needsStableName: dependency.bundleBehavior === 'inline' || childAsset.bundleBehavior === 'inline' ? false : dependency.isEntry || dependency.needsStableName,
267
+ needsStableName: dependency.bundleBehavior === 'inline' || childAsset.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inlineIsolated' || childAsset.bundleBehavior === 'inlineIsolated' ? false : dependency.isEntry || dependency.needsStableName,
245
268
  target: firstBundleGroup.target
246
269
  });
247
270
  bundleId = bundleGraph.addNode(bundle);
@@ -249,6 +272,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
249
272
  bundleRoots.set(childAsset, [bundleId, bundleId]);
250
273
  bundleGroupBundleIds.add(bundleId);
251
274
  bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
275
+ // If this asset is relevant for merging then track it's source
276
+ // bundle id for later
277
+ if (mergeSourceBundleAssets.has(childAsset.filePath)) {
278
+ mergeSourceBundleLookup.set(_path().default.relative(config.projectRoot, childAsset.filePath), bundleId);
279
+ }
252
280
  if (manualSharedObject) {
253
281
  // MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
254
282
  // since MSBs should not have main entry assets
@@ -260,7 +288,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
260
288
  if (
261
289
  // If this dependency requests isolated, but the bundle is not,
262
290
  // make the bundle isolated for all uses.
263
- dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
291
+ (dependency.bundleBehavior === 'isolated' || dependency.bundleBehavior === 'inlineIsolated') && bundle.bundleBehavior == null) {
264
292
  bundle.bundleBehavior = dependency.bundleBehavior;
265
293
  }
266
294
  }
@@ -299,16 +327,17 @@ function createIdealGraph(assetGraph, config, entries, logger) {
299
327
  env: childAsset.env,
300
328
  bundleBehavior: dependency.bundleBehavior ?? childAsset.bundleBehavior,
301
329
  target: referencingBundle.target,
302
- needsStableName: childAsset.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inline' || dependency.priority === 'parallel' && !dependency.needsStableName ? false : referencingBundle.needsStableName
330
+ needsStableName: childAsset.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inlineIsolated' || dependency.priority === 'parallel' && !dependency.needsStableName ? false : referencingBundle.needsStableName
303
331
  });
304
332
  bundleId = bundleGraph.addNode(bundle);
305
333
  } else {
306
- bundle = bundleGraph.getNode(bundleId);
307
- (0, _assert().default)(bundle != null && bundle !== 'root');
334
+ let bundleNode = bundleGraph.getNode(bundleId);
335
+ (0, _assert().default)(bundleNode != null && bundleNode !== 'root');
336
+ bundle = bundleNode;
308
337
  if (
309
338
  // If this dependency requests isolated, but the bundle is not,
310
339
  // make the bundle isolated for all uses.
311
- dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
340
+ (dependency.bundleBehavior === 'isolated' || dependency.bundleBehavior === 'inlineIsolated') && bundle.bundleBehavior == null) {
312
341
  bundle.bundleBehavior = dependency.bundleBehavior;
313
342
  }
314
343
  }
@@ -494,7 +523,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
494
523
  // not true that a bundle's available assets = all assets of all the bundleGroups
495
524
  // it belongs to. It's the intersection of those sets.
496
525
  let available;
497
- if (bundleRoot.bundleBehavior === 'isolated') {
526
+ if (bundleRoot.bundleBehavior === 'isolated' || bundleRoot.bundleBehavior === 'inlineIsolated') {
498
527
  available = new (_graph().BitSet)(assets.length);
499
528
  } else {
500
529
  available = (0, _nullthrows().default)(ancestorAssets[nodeId]).clone();
@@ -557,7 +586,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
557
586
  continue;
558
587
  }
559
588
  let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, _graph().ALL_EDGE_TYPES);
560
- let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated';
589
+ let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated' && getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'inlineIsolated';
561
590
  if (parentRoots.length === 0) continue;
562
591
  for (let parentId of parentRoots) {
563
592
  var _ancestorAssets$paren;
@@ -622,7 +651,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
622
651
  let assetId = bundleRootGraph.getNode(nodeId);
623
652
  if (assetId == null) return; // deleted
624
653
  let a = assets[assetId];
625
- if (entries.has(a) || !a.isBundleSplittable || bundleRoots.get(a) && (getBundleFromBundleRoot(a).needsStableName || getBundleFromBundleRoot(a).bundleBehavior === 'isolated')) {
654
+ if (entries.has(a) || !a.isBundleSplittable || bundleRoots.get(a) && (getBundleFromBundleRoot(a).needsStableName || getBundleFromBundleRoot(a).bundleBehavior === 'isolated' || getBundleFromBundleRoot(a).bundleBehavior === 'inlineIsolated')) {
626
655
  // Add asset to non-splittable bundles.
627
656
  addAssetToBundleRoot(asset, a);
628
657
  } else if (!((_ancestorAssets$nodeI = ancestorAssets[nodeId]) !== null && _ancestorAssets$nodeI !== void 0 && _ancestorAssets$nodeI.has(i))) {
@@ -658,8 +687,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
658
687
  manualSharedMap.set(manualSharedBundleKey, bundleId);
659
688
  } else {
660
689
  bundleId = (0, _nullthrows().default)(manualSharedMap.get(manualSharedBundleKey));
661
- bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
662
- (0, _assert().default)(bundle != null && bundle !== 'root', 'We tried to use the root incorrectly');
690
+ let bundleNode = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
691
+ (0, _assert().default)(bundleNode != null && bundleNode !== 'root', 'We tried to use the root incorrectly');
692
+ bundle = bundleNode;
663
693
  if (!bundle.assets.has(asset)) {
664
694
  bundle.assets.add(asset);
665
695
  bundle.size += asset.stats.size;
@@ -762,8 +792,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
762
792
  bundleId = bundleGraph.addNode(bundle);
763
793
  bundles.set(key, bundleId);
764
794
  } else {
765
- bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
766
- (0, _assert().default)(bundle !== 'root');
795
+ let bundleNode = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
796
+ (0, _assert().default)(bundleNode !== 'root');
797
+ bundle = bundleNode;
767
798
  }
768
799
  bundle.assets.add(asset);
769
800
  bundle.size += asset.stats.size;
@@ -784,6 +815,8 @@ function createIdealGraph(assetGraph, config, entries, logger) {
784
815
  }
785
816
  }
786
817
  let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
818
+ let modifiedSourceBundles = new Set();
819
+
787
820
  // Step split manual shared bundles for those that have the "split" property set
788
821
  let remainderMap = new (_utils().DefaultMap)(() => []);
789
822
  for (let id of manualSharedMap.values()) {
@@ -791,8 +824,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
791
824
  (0, _assert().default)(manualBundle !== 'root' && manualBundle != null);
792
825
  if (manualBundle.sourceBundles.size > 0) {
793
826
  var _manualAssetToConfig$;
794
- let firstSourceBundle = (0, _nullthrows().default)(bundleGraph.getNode([...manualBundle.sourceBundles][0]));
795
- (0, _assert().default)(firstSourceBundle !== 'root');
827
+ let firstSourceBundleNode = (0, _nullthrows().default)(bundleGraph.getNode([...manualBundle.sourceBundles][0]));
828
+ (0, _assert().default)(firstSourceBundleNode !== 'root');
829
+ let firstSourceBundle = firstSourceBundleNode;
796
830
  let firstAsset = [...manualBundle.assets][0];
797
831
  let manualSharedObject = manualAssetToConfig.get(firstAsset);
798
832
  (0, _assert().default)(manualSharedObject != null);
@@ -800,7 +834,6 @@ function createIdealGraph(assetGraph, config, entries, logger) {
800
834
  if (modNum != null) {
801
835
  for (let a of [...manualBundle.assets]) {
802
836
  let numRep = getBigIntFromContentKey(a.id);
803
- // $FlowFixMe Flow doesn't know about BigInt
804
837
  let r = Number(numRep % BigInt(modNum));
805
838
  remainderMap.get(r).push(a);
806
839
  }
@@ -847,6 +880,66 @@ function createIdealGraph(assetGraph, config, entries, logger) {
847
880
  }
848
881
  }
849
882
  }
883
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
884
+ // Merge webpack chunk name bundles
885
+ let chunkNameBundles = new (_utils().DefaultMap)(() => new Set());
886
+ for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
887
+ var _bundleNode$value$mai;
888
+ // meta.chunkName is set by the Rust transformer, so we just need to find
889
+ // bundles that have a chunkName set.
890
+ if (!node || node.type !== 'dependency' || typeof node.value.meta.chunkName !== 'string') {
891
+ continue;
892
+ }
893
+ let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(nodeId, dependencyPriorityEdges[node.value.priority]);
894
+ if (connectedBundles.length === 0) {
895
+ continue;
896
+ }
897
+ (0, _assert().default)(connectedBundles.length === 1, 'Expected webpackChunkName dependency to be connected to no more than one bundle');
898
+ let bundleId = connectedBundles[0];
899
+ let bundleNode = dependencyBundleGraph.getNode(bundleId);
900
+ (0, _assert().default)(bundleNode != null && bundleNode.type === 'bundle');
901
+
902
+ // If a bundle does not have a main entry asset, it's somehow just a
903
+ // shared bundle, and will be merged/deleted by other means.
904
+ if (bundleNode.value.mainEntryAsset == null) {
905
+ continue;
906
+ }
907
+ let bundleNodeId = null;
908
+ let mainEntryAssetId = (_bundleNode$value$mai = bundleNode.value.mainEntryAsset) === null || _bundleNode$value$mai === void 0 ? void 0 : _bundleNode$value$mai.id;
909
+ if (mainEntryAssetId != null) {
910
+ bundleNodeId = bundles.get(mainEntryAssetId);
911
+ }
912
+ if (bundleNodeId == null) {
913
+ continue;
914
+ }
915
+ chunkNameBundles.get(node.value.meta.chunkName)
916
+ // DependencyBundleGraph uses content keys as node ids, so we can use that
917
+ // to get the bundle id.
918
+ .add(bundleNodeId);
919
+ }
920
+ for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
921
+ // The `[request]` placeholder is not yet supported
922
+ if (bundleIds.size <= 1 || typeof chunkName === 'string' && chunkName.includes('[request]')) {
923
+ continue; // Nothing to merge
924
+ }
925
+
926
+ // Merge all bundles with the same chunk name into the first one.
927
+ let [firstBundleId, ...rest] = Array.from(bundleIds);
928
+ for (let bundleId of rest) {
929
+ mergeBundles(firstBundleId, bundleId, 'webpack-chunk-name');
930
+ }
931
+ }
932
+ }
933
+
934
+ // Step merge async bundles that meet the configured params
935
+ if (config.asyncBundleMerge) {
936
+ mergeAsyncBundles(config.asyncBundleMerge);
937
+ }
938
+
939
+ // Step merge shared bundles that meet the configured params
940
+ if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
941
+ mergeSharedBundles(config.sharedBundleMerge);
942
+ }
850
943
 
851
944
  // Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
852
945
  // their source bundles, and remove the bundle.
@@ -857,9 +950,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
857
950
  removeBundle(bundleGraph, bundleNodeId, assetReference);
858
951
  }
859
952
  }
860
- let modifiedSourceBundles = new Set();
861
953
 
862
954
  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
955
+
863
956
  if (config.disableSharedBundles === false) {
864
957
  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
865
958
  // Find shared bundles in this bundle group.
@@ -872,7 +965,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
872
965
  let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
873
966
  let bundle = (0, _nullthrows().default)(bundleGraph.getNode(b));
874
967
  (0, _assert().default)(bundle !== 'root');
875
- return count + (bundle.bundleBehavior !== 'inline');
968
+ return count + Number(bundle.bundleBehavior !== 'inline' && bundle.bundleBehavior !== 'inlineIsolated');
876
969
  }, 0);
877
970
  if (numBundlesContributingToPRL > config.maxParallelRequests) {
878
971
  let sharedBundleIdsInBundleGroup = bundleIdsInGroup.filter(b => {
@@ -901,6 +994,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
901
994
  // Remove bundles until the bundle group is within the parallel request limit.
902
995
  while (sharedBundlesInGroup.length > 0 && numBundlesContributingToPRL > config.maxParallelRequests) {
903
996
  let bundleTuple = sharedBundlesInGroup.pop();
997
+ if (!bundleTuple) break;
904
998
  let bundleToRemove = bundleTuple.bundle;
905
999
  let bundleIdToRemove = bundleTuple.id;
906
1000
  //TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
@@ -944,10 +1038,279 @@ function createIdealGraph(assetGraph, config, entries, logger) {
944
1038
  }
945
1039
  }
946
1040
  }
1041
+ function mergeBundles(bundleToKeepId, bundleToRemoveId, reason) {
1042
+ stats.trackMerge(bundleToKeepId, bundleToRemoveId, reason);
1043
+ let bundleToKeep = isNonRootBundle(bundleGraph.getNode(bundleToKeepId), `Bundle ${bundleToKeepId} not found`);
1044
+ let bundleToRemove = isNonRootBundle(bundleGraph.getNode(bundleToRemoveId), `Bundle ${bundleToRemoveId} not found`);
1045
+ modifiedSourceBundles.add(bundleToKeep);
1046
+ for (let asset of bundleToRemove.assets) {
1047
+ bundleToKeep.assets.add(asset);
1048
+ bundleToKeep.size += asset.stats.size;
1049
+ let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
1050
+ assetReference.set(asset, newAssetReference);
1051
+ }
1052
+
1053
+ // Merge any internalized assets
1054
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1055
+ if (bundleToKeep.internalizedAssets != null) {
1056
+ if (bundleToRemove.internalizedAssets != null) {
1057
+ bundleToKeep.internalizedAssets.intersect(bundleToRemove.internalizedAssets);
1058
+ } else {
1059
+ bundleToKeep.internalizedAssets.clear();
1060
+ }
1061
+ }
1062
+ } else {
1063
+ (0, _assert().default)(bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets, 'All shared bundles should have internalized assets');
1064
+ bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1065
+ }
1066
+
1067
+ // Merge and clean up source bundles
1068
+ for (let sourceBundleId of bundleToRemove.sourceBundles) {
1069
+ if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
1070
+ continue;
1071
+ }
1072
+ if (sourceBundleId !== bundleToKeepId) {
1073
+ bundleToKeep.sourceBundles.add(sourceBundleId);
1074
+ bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1075
+ }
1076
+ }
1077
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1078
+ bundleToKeep.sourceBundles.delete(bundleToRemoveId);
1079
+ for (let bundle of bundleGraph.getNodeIdsConnectedFrom(bundleToRemoveId)) {
1080
+ let bundleNode = (0, _nullthrows().default)(bundleGraph.getNode(bundle));
1081
+ if (bundleNode === 'root') {
1082
+ continue;
1083
+ }
1084
+
1085
+ // If the bundle is a source bundle, add it to the bundle to keep
1086
+ if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
1087
+ bundleNode.sourceBundles.delete(bundleToRemoveId);
1088
+ if (bundle !== bundleToKeepId) {
1089
+ bundleNode.sourceBundles.add(bundleToKeepId);
1090
+ bundleGraph.addEdge(bundleToKeepId, bundle);
1091
+ }
1092
+ }
1093
+ }
1094
+
1095
+ // Merge bundle roots
1096
+ for (let bundleRoot of bundleToRemove.bundleRoots) {
1097
+ bundleToKeep.bundleRoots.add(bundleRoot);
1098
+ }
1099
+ if (bundleToRemove.mainEntryAsset != null) {
1100
+ (0, _assert().default)(bundleToKeep.mainEntryAsset != null);
1101
+
1102
+ // Merge the bundles in bundle group
1103
+ let bundlesInRemoveBundleGroup = getBundlesForBundleGroup(bundleToRemoveId);
1104
+ let removedBundleSharedBundles = new Set();
1105
+ for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
1106
+ if (bundleIdInGroup === bundleToRemoveId) {
1107
+ continue;
1108
+ }
1109
+ bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
1110
+ removedBundleSharedBundles.add(bundleIdInGroup);
1111
+ }
1112
+ if ((0, _featureFlags().getFeatureFlag)('removeRedundantSharedBundles')) {
1113
+ // Merge any shared bundles that now have the same source bundles due to
1114
+ // the current bundle merge
1115
+ let sharedBundles = new (_utils().DefaultMap)(() => []);
1116
+ for (let bundleId of removedBundleSharedBundles) {
1117
+ let bundleNode = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
1118
+ (0, _assert().default)(bundleNode !== 'root');
1119
+ if (bundleNode.mainEntryAsset != null || bundleNode.manualSharedBundle != null) {
1120
+ continue;
1121
+ }
1122
+ let key = Array.from(bundleNode.sourceBundles).filter(sourceBundle => sourceBundle !== bundleToRemoveId).sort().join(',') + '.' + bundleNode.type;
1123
+ sharedBundles.get(key).push(bundleId);
1124
+ }
1125
+ for (let sharedBundlesToMerge of sharedBundles.values()) {
1126
+ if (sharedBundlesToMerge.length > 1) {
1127
+ let [firstBundleId, ...rest] = sharedBundlesToMerge;
1128
+ for (let bundleId of rest) {
1129
+ mergeBundles(firstBundleId, bundleId, 'redundant-shared');
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+
1135
+ // Remove old bundle group
1136
+ bundleGroupBundleIds.delete(bundleToRemoveId);
1137
+
1138
+ // Clean up bundle roots
1139
+ let bundleRootToRemoveNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get((0, _nullthrows().default)(bundleToRemove.mainEntryAsset)));
1140
+ let bundleRootToKeepNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get((0, _nullthrows().default)(bundleToKeep.mainEntryAsset)));
1141
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(bundleRootToRemoveNodeId)) {
1142
+ bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
1143
+ bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
1144
+ }
1145
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(bundleRootToRemoveNodeId)) {
1146
+ bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
1147
+ bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
1148
+ }
1149
+ bundleRoots.set((0, _nullthrows().default)(bundleToRemove.mainEntryAsset), [bundleToKeepId, bundleToKeepId]);
1150
+
1151
+ // Merge dependency bundle graph
1152
+ for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)), _graph().ALL_EDGE_TYPES)) {
1153
+ let dependencyNode = (0, _nullthrows().default)(dependencyBundleGraph.getNode(dependencyNodeId));
1154
+ (0, _assert().default)(dependencyNode.type === 'dependency');
1155
+
1156
+ // Add dependency to the bundle to keep
1157
+ dependencyBundleGraph.addEdge(dependencyNodeId, dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)), dependencyPriorityEdges[dependencyNode.value.priority]);
1158
+ // Remove dependency from the bundle to remove
1159
+ dependencyBundleGraph.removeEdge(dependencyNodeId, dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)), dependencyPriorityEdges[dependencyNode.value.priority]);
1160
+ }
1161
+ }
1162
+ }
1163
+ bundleGraph.removeNode(bundleToRemoveId);
1164
+ }
1165
+ function mergeSharedBundles(mergeConfig) {
1166
+ // Find all shared bundles
1167
+ let sharedBundles = new Set();
1168
+ bundleGraph.traverse(nodeId => {
1169
+ let bundle = bundleGraph.getNode(nodeId);
1170
+ if (!bundle) {
1171
+ throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
1172
+ }
1173
+ if (bundle === 'root') {
1174
+ return;
1175
+ }
1176
+
1177
+ // Only consider JS shared bundles and non-reused bundles.
1178
+ // These count potentially be considered for merging in future but they're
1179
+ // more complicated to merge
1180
+ if (bundle.sourceBundles.size > 0 && bundle.manualSharedBundle == null && !bundle.mainEntryAsset && bundle.type === 'js') {
1181
+ sharedBundles.add(nodeId);
1182
+ }
1183
+ });
1184
+ let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), mergeConfig.map(config => {
1185
+ var _config$sourceBundles;
1186
+ return {
1187
+ ...config,
1188
+ sourceBundles: (_config$sourceBundles = config.sourceBundles) === null || _config$sourceBundles === void 0 ? void 0 : _config$sourceBundles.map(assetMatch => {
1189
+ let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
1190
+ if (sourceBundleNodeId == null) {
1191
+ throw new Error(`Source bundle ${assetMatch} not found in merge source bundle lookup`);
1192
+ }
1193
+ return sourceBundleNodeId;
1194
+ })
1195
+ };
1196
+ }));
1197
+ let mergedBundles = new Set();
1198
+ for (let cluster of clusters) {
1199
+ let [mergeTarget, ...rest] = cluster;
1200
+ for (let bundleIdToMerge of rest) {
1201
+ mergeBundles(mergeTarget, bundleIdToMerge, 'shared-merge');
1202
+ }
1203
+ mergedBundles.add(mergeTarget);
1204
+ }
1205
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1206
+ return mergedBundles;
1207
+ }
1208
+ }
1209
+ function mergeAsyncBundles({
1210
+ bundleSize,
1211
+ maxOverfetchSize,
1212
+ ignore
1213
+ }) {
1214
+ let mergeCandidates = [];
1215
+ let ignoreRegexes = (ignore === null || ignore === void 0 ? void 0 : ignore.map(glob => (0, _utils().globToRegex)(glob))) ?? [];
1216
+ let isIgnored = bundle => {
1217
+ if (!bundle.mainEntryAsset) {
1218
+ return false;
1219
+ }
1220
+ let mainEntryFilePath = _path().default.relative(config.projectRoot, (0, _nullthrows().default)(bundle.mainEntryAsset).filePath);
1221
+ return ignoreRegexes.some(regex => regex.test(mainEntryFilePath));
1222
+ };
1223
+ for (let [_bundleRootAsset, [bundleRootBundleId]] of bundleRoots) {
1224
+ let bundleRootBundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleRootBundleId));
1225
+ (0, _assert().default)(bundleRootBundle !== 'root');
1226
+ if (bundleRootBundle.type === 'js' && bundleRootBundle.bundleBehavior !== 'inline' && bundleRootBundle.bundleBehavior !== 'inlineIsolated' && bundleRootBundle.size <= bundleSize && !isIgnored(bundleRootBundle)) {
1227
+ mergeCandidates.push(bundleRootBundleId);
1228
+ }
1229
+ }
1230
+ let candidates = [];
1231
+ for (let i = 0; i < mergeCandidates.length; i++) {
1232
+ for (let j = i + 1; j < mergeCandidates.length; j++) {
1233
+ let a = mergeCandidates[i];
1234
+ let b = mergeCandidates[j];
1235
+ if (a === b) continue; // Skip self-comparison
1236
+
1237
+ candidates.push(scoreAsyncMerge(a, b, maxOverfetchSize));
1238
+ }
1239
+ }
1240
+ let sortByScore = (a, b) => {
1241
+ let diff = a.score - b.score;
1242
+ if (diff > 0) {
1243
+ return 1;
1244
+ } else if (diff < 0) {
1245
+ return -1;
1246
+ }
1247
+ return 0;
1248
+ };
1249
+ candidates = candidates.filter(({
1250
+ overfetchSize,
1251
+ score
1252
+ }) => overfetchSize <= maxOverfetchSize && score > 0).sort(sortByScore);
1253
+
1254
+ // Tracks the bundles that have been merged
1255
+ let merged = new Set();
1256
+ // Tracks the deleted bundles to the bundle they were merged into.
1257
+ let mergeRemap = new Map();
1258
+ // Tracks the bundles that have been rescored and added back into the
1259
+ // candidates.
1260
+ let rescored = new (_utils().DefaultMap)(() => new Set());
1261
+ do {
1262
+ let [a, b] = (0, _nullthrows().default)(candidates.pop()).bundleIds;
1263
+ if (bundleGraph.hasNode(a) && bundleGraph.hasNode(b) && (!merged.has(a) && !merged.has(b) || rescored.get(a).has(b))) {
1264
+ mergeRemap.set(b, a);
1265
+ merged.add(a);
1266
+ rescored.get(a).clear();
1267
+ mergeBundles(a, b, 'async-merge');
1268
+ continue;
1269
+ }
1270
+
1271
+ // One or both of the bundles have been previously merged, so we'll
1272
+ // rescore and add the result back into the list of candidates.
1273
+ let getMergedBundleId = bundleId => {
1274
+ let seen = new Set();
1275
+ while (!bundleGraph.hasNode(bundleId) && !seen.has(bundleId)) {
1276
+ seen.add(bundleId);
1277
+ bundleId = (0, _nullthrows().default)(mergeRemap.get(bundleId));
1278
+ }
1279
+ if (!bundleGraph.hasNode(bundleId)) {
1280
+ return;
1281
+ }
1282
+ return bundleId;
1283
+ };
1284
+
1285
+ // Map a and b to their merged bundle ids if they've already been merged
1286
+ let currentA = getMergedBundleId(a);
1287
+ let currentB = getMergedBundleId(b);
1288
+ if (!currentA || !currentB ||
1289
+ // Bundles are already merged
1290
+ currentA === currentB) {
1291
+ // This combiniation is not valid, so we skip it.
1292
+ continue;
1293
+ }
1294
+ let candidate = scoreAsyncMerge(currentA, currentB, maxOverfetchSize);
1295
+ if (candidate.overfetchSize <= maxOverfetchSize && candidate.score > 0) {
1296
+ _sortedArrayFunctions().default.add(candidates, candidate, sortByScore);
1297
+ rescored.get(currentA).add(currentB);
1298
+ }
1299
+ } while (candidates.length > 0);
1300
+ }
1301
+ function getBundle(bundleId) {
1302
+ let bundle = bundleGraph.getNode(bundleId);
1303
+ if (bundle === 'root') {
1304
+ throw new Error(`Cannot access root bundle`);
1305
+ }
1306
+ if (bundle == null) {
1307
+ throw new Error(`Bundle ${bundleId} not found in bundle graph`);
1308
+ }
1309
+ return bundle;
1310
+ }
947
1311
  function getBigIntFromContentKey(contentKey) {
948
1312
  let b = Buffer.alloc(64);
949
1313
  b.write(contentKey);
950
- // $FlowFixMe Flow doesn't have BigInt types in this version
951
1314
  return b.readBigInt64BE();
952
1315
  }
953
1316
  // Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
@@ -977,6 +1340,53 @@ function createIdealGraph(assetGraph, config, entries, logger) {
977
1340
  }, bundleGroupId);
978
1341
  return bundlesInABundleGroup;
979
1342
  }
1343
+ function scoreAsyncMerge(bundleAId, bundleBId, maxOverfetchSize) {
1344
+ let bundleGroupA = new Set(getBundlesForBundleGroup(bundleAId));
1345
+ let bundleGroupB = new Set(getBundlesForBundleGroup(bundleBId));
1346
+ let overlapSize = 0;
1347
+ let overfetchSize = 0;
1348
+ for (let bundleId of new Set([...bundleGroupA, ...bundleGroupB])) {
1349
+ let bundle = getBundle(bundleId);
1350
+ if (bundleGroupA.has(bundleId) && bundleGroupB.has(bundleId)) {
1351
+ overlapSize += bundle.size;
1352
+ } else {
1353
+ overfetchSize += bundle.size;
1354
+ }
1355
+ }
1356
+ let overlapPercent = overlapSize / (overfetchSize + overlapSize);
1357
+ let bundleAParents = getBundleParents(bundleAId);
1358
+ let bundleBParents = getBundleParents(bundleBId);
1359
+ let sharedParentOverlap = 0;
1360
+ let sharedParentMismatch = 0;
1361
+ for (let bundleId of new Set([...bundleAParents, ...bundleBParents])) {
1362
+ if (bundleAParents.has(bundleId) && bundleBParents.has(bundleId)) {
1363
+ sharedParentOverlap++;
1364
+ } else {
1365
+ sharedParentMismatch++;
1366
+ }
1367
+ }
1368
+ let overfetchScore = overfetchSize / maxOverfetchSize;
1369
+ let sharedParentPercent = sharedParentOverlap / (sharedParentOverlap + sharedParentMismatch);
1370
+ let score = sharedParentPercent + overlapPercent + overfetchScore;
1371
+ return {
1372
+ overfetchSize,
1373
+ score,
1374
+ bundleIds: [bundleAId, bundleBId]
1375
+ };
1376
+ }
1377
+ function getBundleParents(bundleId) {
1378
+ let parents = new Set();
1379
+ let {
1380
+ bundleRoots
1381
+ } = getBundle(bundleId);
1382
+ for (let bundleRoot of bundleRoots) {
1383
+ let bundleRootNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get(bundleRoot));
1384
+ for (let parentId of bundleRootGraph.getNodeIdsConnectedTo(bundleRootNodeId, _graph().ALL_EDGE_TYPES)) {
1385
+ parents.add(parentId);
1386
+ }
1387
+ }
1388
+ return parents;
1389
+ }
980
1390
  function getBundleFromBundleRoot(bundleRoot) {
981
1391
  let bundle = bundleGraph.getNode((0, _nullthrows().default)(bundleRoots.get(bundleRoot))[0]);
982
1392
  (0, _assert().default)(bundle !== 'root' && bundle != null);
@@ -1026,6 +1436,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1026
1436
  }
1027
1437
  bundleGraph.removeNode(bundleId);
1028
1438
  }
1439
+ stats.report(bundleId => {
1440
+ let bundle = bundleGraph.getNode(bundleId);
1441
+ (0, _assert().default)(bundle !== 'root');
1442
+ return bundle;
1443
+ });
1029
1444
  return {
1030
1445
  assets,
1031
1446
  bundleGraph,
@@ -1042,6 +1457,7 @@ function createBundle(opts) {
1042
1457
  bundleBehavior: opts.bundleBehavior,
1043
1458
  env: (0, _nullthrows().default)(opts.env),
1044
1459
  mainEntryAsset: null,
1460
+ bundleRoots: new Set(),
1045
1461
  manualSharedBundle: opts.manualSharedBundle,
1046
1462
  needsStableName: Boolean(opts.needsStableName),
1047
1463
  size: 0,
@@ -1057,6 +1473,7 @@ function createBundle(opts) {
1057
1473
  bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
1058
1474
  env: opts.env ?? asset.env,
1059
1475
  mainEntryAsset: asset,
1476
+ bundleRoots: new Set([asset]),
1060
1477
  manualSharedBundle: opts.manualSharedBundle,
1061
1478
  needsStableName: Boolean(opts.needsStableName),
1062
1479
  size: asset.stats.size,