@atlaspack/bundler-default 2.14.5-canary.2 → 2.14.5-canary.200

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) {
@@ -127,6 +136,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
127
136
  let parentsToConfig = new (_utils().DefaultMap)(() => []);
128
137
  for (let c of config.manualSharedBundles) {
129
138
  if (c.root != null) {
139
+ // @ts-expect-error TS2345
130
140
  parentsToConfig.get(_path().default.join(config.projectRoot, c.root)).push(c);
131
141
  }
132
142
  }
@@ -159,17 +169,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
159
169
  assetGraph.traverse((node, _, actions) => {
160
170
  if (node.type === 'asset' && (!Array.isArray(c.types) || c.types.includes(node.value.type))) {
161
171
  let projectRelativePath = _path().default.relative(config.projectRoot, node.value.filePath);
162
- if (!assetRegexes.some(regex => regex.test(projectRelativePath))) {
163
- return;
164
- }
165
172
 
166
173
  // We track all matching MSB's for constant modules as they are never duplicated
167
174
  // and need to be assigned to all matching bundles
168
175
  if (node.value.meta.isConstantModule === true) {
176
+ // @ts-expect-error TS2345
169
177
  constantModuleToMSB.get(node.value).push(c);
170
178
  }
171
- manualAssetToConfig.set(node.value, c);
172
- return;
179
+ if (assetRegexes.some(regex => regex.test(projectRelativePath))) {
180
+ manualAssetToConfig.set(node.value, c);
181
+ }
173
182
  }
174
183
  if (node.type === 'dependency' && (node.value.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional') && parentAsset) {
175
184
  // Don't walk past the bundle group assets
@@ -181,8 +190,23 @@ function createIdealGraph(assetGraph, config, entries, logger) {
181
190
  manualAssetToConfig,
182
191
  constantModuleToMSB
183
192
  };
184
- }();
193
+ }
194
+
195
+ //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
196
+ let manualSharedMap = new Map();
197
+ // May need a map to be able to look up NON- bundle root assets which need special case instructions
198
+ // Use this when placing assets into bundles, to avoid duplication
199
+ let manualAssetToBundle = new Map();
200
+ let {
201
+ manualAssetToConfig,
202
+ constantModuleToMSB
203
+ } = makeManualAssetToConfigLookup();
185
204
  let manualBundleToInternalizedAsset = new (_utils().DefaultMap)(() => []);
205
+ let mergeSourceBundleLookup = new Map();
206
+ let mergeSourceBundleAssets = new Set((_config$sharedBundleM = config.sharedBundleMerge) === null || _config$sharedBundleM === void 0 ? void 0 : _config$sharedBundleM.flatMap(c => {
207
+ var _c$sourceBundles;
208
+ return ((_c$sourceBundles = c.sourceBundles) === null || _c$sourceBundles === void 0 ? void 0 : _c$sourceBundles.map(assetMatch => _path().default.join(config.projectRoot, assetMatch))) ?? [];
209
+ }));
186
210
 
187
211
  /**
188
212
  * Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
@@ -190,7 +214,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
190
214
  * adding only that asset to each bundle, not its entire subgraph.
191
215
  */
192
216
  assetGraph.traverse({
193
- enter(node, context, actions) {
217
+ enter(node, context,
218
+ // @ts-expect-error TS2304
219
+ actions) {
194
220
  if (node.type === 'asset') {
195
221
  if ((context === null || context === void 0 ? void 0 : context.type) === 'dependency' && context !== null && context !== void 0 && context.value.isEntry && !entries.has(node.value)) {
196
222
  // Skip whole subtrees of other targets by skipping those entries
@@ -233,15 +259,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
233
259
  manualSharedBundleKey != null && manualSharedMap.has(manualSharedBundleKey)) {
234
260
  bundleId = (0, _nullthrows().default)(manualSharedMap.get(manualSharedBundleKey));
235
261
  }
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
- ) {
262
+ if (dependency.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional' || childAsset.bundleBehavior === 'isolated' ||
263
+ // An isolated Dependency, or Bundle must contain all assets it needs to load.
264
+ childAsset.bundleBehavior === 'inlineIsolated') {
238
265
  if (bundleId == null) {
239
266
  let firstBundleGroup = (0, _nullthrows().default)(bundleGraph.getNode(stack[0][1]));
240
267
  (0, _assert().default)(firstBundleGroup !== 'root');
241
268
  bundle = createBundle({
242
269
  asset: childAsset,
243
270
  bundleBehavior: dependency.bundleBehavior ?? childAsset.bundleBehavior,
244
- needsStableName: dependency.bundleBehavior === 'inline' || childAsset.bundleBehavior === 'inline' ? false : dependency.isEntry || dependency.needsStableName,
271
+ needsStableName: dependency.bundleBehavior === 'inline' || childAsset.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inlineIsolated' || childAsset.bundleBehavior === 'inlineIsolated' ? false : dependency.isEntry || dependency.needsStableName,
245
272
  target: firstBundleGroup.target
246
273
  });
247
274
  bundleId = bundleGraph.addNode(bundle);
@@ -249,6 +276,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
249
276
  bundleRoots.set(childAsset, [bundleId, bundleId]);
250
277
  bundleGroupBundleIds.add(bundleId);
251
278
  bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
279
+ // If this asset is relevant for merging then track it's source
280
+ // bundle id for later
281
+ if (mergeSourceBundleAssets.has(childAsset.filePath)) {
282
+ mergeSourceBundleLookup.set(_path().default.relative(config.projectRoot, childAsset.filePath), bundleId);
283
+ }
252
284
  if (manualSharedObject) {
253
285
  // MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
254
286
  // since MSBs should not have main entry assets
@@ -260,7 +292,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
260
292
  if (
261
293
  // If this dependency requests isolated, but the bundle is not,
262
294
  // make the bundle isolated for all uses.
263
- dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
295
+ (dependency.bundleBehavior === 'isolated' || dependency.bundleBehavior === 'inlineIsolated') && bundle.bundleBehavior == null) {
264
296
  bundle.bundleBehavior = dependency.bundleBehavior;
265
297
  }
266
298
  }
@@ -270,7 +302,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
270
302
  }), dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(bundleId), {
271
303
  value: bundle,
272
304
  type: 'bundle'
273
- }), dependencyPriorityEdges[dependency.priority]);
305
+ }),
306
+ // @ts-expect-error TS7053
307
+ dependencyPriorityEdges[dependency.priority]);
274
308
  if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && dependency.priority === 'conditional') {
275
309
  let [referencingBundleRoot, bundleGroupNodeId] = (0, _nullthrows().default)(stack[stack.length - 1]);
276
310
  let referencingBundleId = (0, _nullthrows().default)(bundleRoots.get(referencingBundleRoot))[0];
@@ -299,7 +333,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
299
333
  env: childAsset.env,
300
334
  bundleBehavior: dependency.bundleBehavior ?? childAsset.bundleBehavior,
301
335
  target: referencingBundle.target,
302
- needsStableName: childAsset.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inline' || dependency.priority === 'parallel' && !dependency.needsStableName ? false : referencingBundle.needsStableName
336
+ needsStableName: childAsset.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inline' || dependency.bundleBehavior === 'inlineIsolated' || dependency.priority === 'parallel' && !dependency.needsStableName ? false : referencingBundle.needsStableName
303
337
  });
304
338
  bundleId = bundleGraph.addNode(bundle);
305
339
  } else {
@@ -308,7 +342,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
308
342
  if (
309
343
  // If this dependency requests isolated, but the bundle is not,
310
344
  // make the bundle isolated for all uses.
311
- dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
345
+ (dependency.bundleBehavior === 'isolated' || dependency.bundleBehavior === 'inlineIsolated') && bundle.bundleBehavior == null) {
312
346
  bundle.bundleBehavior = dependency.bundleBehavior;
313
347
  }
314
348
  }
@@ -330,13 +364,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
330
364
  }
331
365
  assetReference.get(childAsset).push([dependency, bundle]);
332
366
  } else {
367
+ // @ts-expect-error TS2322
333
368
  bundleId = null;
334
369
  }
335
370
  if (manualSharedObject && bundleId != null) {
336
371
  // MSB Step 5: At this point we've either created or found an existing MSB bundle
337
372
  // add the asset if it doesn't already have it and set key
338
373
 
339
- (0, _assert().default)(bundle !== 'root' && bundle != null && bundleId != null);
374
+ (0, _assert().default)(
375
+ // @ts-expect-error TS2367
376
+ bundle !== 'root' && bundle != null && bundleId != null);
340
377
  manualAssetToBundle.set(childAsset, bundleId);
341
378
  if (!bundle.assets.has(childAsset)) {
342
379
  // Add asset to bundle
@@ -357,6 +394,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
357
394
  }
358
395
  return node;
359
396
  },
397
+ // @ts-expect-error TS2322
360
398
  exit(node) {
361
399
  var _stack;
362
400
  if (((_stack = stack[stack.length - 1]) === null || _stack === void 0 ? void 0 : _stack[0]) === node.value) {
@@ -494,7 +532,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
494
532
  // not true that a bundle's available assets = all assets of all the bundleGroups
495
533
  // it belongs to. It's the intersection of those sets.
496
534
  let available;
497
- if (bundleRoot.bundleBehavior === 'isolated') {
535
+ if (bundleRoot.bundleBehavior === 'isolated' || bundleRoot.bundleBehavior === 'inlineIsolated') {
498
536
  available = new (_graph().BitSet)(assets.length);
499
537
  } else {
500
538
  available = (0, _nullthrows().default)(ancestorAssets[nodeId]).clone();
@@ -557,7 +595,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
557
595
  continue;
558
596
  }
559
597
  let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, _graph().ALL_EDGE_TYPES);
560
- let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated';
598
+ let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated' && getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'inlineIsolated';
561
599
  if (parentRoots.length === 0) continue;
562
600
  for (let parentId of parentRoots) {
563
601
  var _ancestorAssets$paren;
@@ -585,8 +623,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
585
623
  }
586
624
  function assignInlineConstants(parentAsset, bundle) {
587
625
  for (let inlineConstant of inlineConstantDeps.get(parentAsset)) {
626
+ // @ts-expect-error TS2345
588
627
  if (!bundle.assets.has(inlineConstant)) {
628
+ // @ts-expect-error TS2345
589
629
  bundle.assets.add(inlineConstant);
630
+ // @ts-expect-error TS18046
590
631
  bundle.size += inlineConstant.stats.size;
591
632
  }
592
633
  }
@@ -622,7 +663,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
622
663
  let assetId = bundleRootGraph.getNode(nodeId);
623
664
  if (assetId == null) return; // deleted
624
665
  let a = assets[assetId];
625
- if (entries.has(a) || !a.isBundleSplittable || bundleRoots.get(a) && (getBundleFromBundleRoot(a).needsStableName || getBundleFromBundleRoot(a).bundleBehavior === 'isolated')) {
666
+ if (entries.has(a) || !a.isBundleSplittable || bundleRoots.get(a) && (getBundleFromBundleRoot(a).needsStableName || getBundleFromBundleRoot(a).bundleBehavior === 'isolated' || getBundleFromBundleRoot(a).bundleBehavior === 'inlineIsolated')) {
626
667
  // Add asset to non-splittable bundles.
627
668
  addAssetToBundleRoot(asset, a);
628
669
  } else if (!((_ancestorAssets$nodeI = ancestorAssets[nodeId]) !== null && _ancestorAssets$nodeI !== void 0 && _ancestorAssets$nodeI.has(i))) {
@@ -784,6 +825,8 @@ function createIdealGraph(assetGraph, config, entries, logger) {
784
825
  }
785
826
  }
786
827
  let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
828
+ let modifiedSourceBundles = new Set();
829
+
787
830
  // Step split manual shared bundles for those that have the "split" property set
788
831
  let remainderMap = new (_utils().DefaultMap)(() => []);
789
832
  for (let id of manualSharedMap.values()) {
@@ -800,8 +843,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
800
843
  if (modNum != null) {
801
844
  for (let a of [...manualBundle.assets]) {
802
845
  let numRep = getBigIntFromContentKey(a.id);
803
- // $FlowFixMe Flow doesn't know about BigInt
804
846
  let r = Number(numRep % BigInt(modNum));
847
+
848
+ // @ts-expect-error TS2345
805
849
  remainderMap.get(r).push(a);
806
850
  }
807
851
  for (let i = 1; i < [...remainderMap.keys()].length; i++) {
@@ -823,8 +867,10 @@ function createIdealGraph(assetGraph, config, entries, logger) {
823
867
  }
824
868
  for (let sp of remainderMap.get(i)) {
825
869
  bundle.assets.add(sp);
870
+ // @ts-expect-error TS2339
826
871
  bundle.size += sp.stats.size;
827
872
  manualBundle.assets.delete(sp);
873
+ // @ts-expect-error TS2339
828
874
  manualBundle.size -= sp.stats.size;
829
875
  }
830
876
  }
@@ -837,16 +883,81 @@ function createIdealGraph(assetGraph, config, entries, logger) {
837
883
  // match multiple MSB's
838
884
  for (let [asset, msbs] of constantModuleToMSB.entries()) {
839
885
  for (let manualSharedObject of msbs) {
886
+ // @ts-expect-error TS2339
840
887
  let bundleId = manualSharedMap.get(manualSharedObject.name + ',js');
841
888
  if (bundleId == null) continue;
842
889
  let bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
843
890
  (0, _assert().default)(bundle != null && bundle !== 'root', 'We tried to use the root incorrectly');
891
+
892
+ // @ts-expect-error TS2345
844
893
  if (!bundle.assets.has(asset)) {
894
+ // @ts-expect-error TS2345
845
895
  bundle.assets.add(asset);
896
+ // @ts-expect-error TS18046
846
897
  bundle.size += asset.stats.size;
847
898
  }
848
899
  }
849
900
  }
901
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
902
+ // Merge webpack chunk name bundles
903
+ let chunkNameBundles = new (_utils().DefaultMap)(() => new Set());
904
+ for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
905
+ var _bundleNode$value$mai;
906
+ // meta.chunkName is set by the Rust transformer, so we just need to find
907
+ // bundles that have a chunkName set.
908
+ if (!node || node.type !== 'dependency' || typeof node.value.meta.chunkName !== 'string') {
909
+ continue;
910
+ }
911
+ let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(nodeId, dependencyPriorityEdges[node.value.priority]);
912
+ if (connectedBundles.length === 0) {
913
+ continue;
914
+ }
915
+ (0, _assert().default)(connectedBundles.length === 1, 'Expected webpackChunkName dependency to be connected to no more than one bundle');
916
+ let bundleId = connectedBundles[0];
917
+ let bundleNode = dependencyBundleGraph.getNode(bundleId);
918
+ (0, _assert().default)(bundleNode != null && bundleNode.type === 'bundle');
919
+
920
+ // If a bundle does not have a main entry asset, it's somehow just a
921
+ // shared bundle, and will be merged/deleted by other means.
922
+ if (bundleNode.value.mainEntryAsset == null) {
923
+ continue;
924
+ }
925
+ let bundleNodeId = null;
926
+ let mainEntryAssetId = (_bundleNode$value$mai = bundleNode.value.mainEntryAsset) === null || _bundleNode$value$mai === void 0 ? void 0 : _bundleNode$value$mai.id;
927
+ if (mainEntryAssetId != null) {
928
+ bundleNodeId = bundles.get(mainEntryAssetId);
929
+ }
930
+ if (bundleNodeId == null) {
931
+ continue;
932
+ }
933
+ chunkNameBundles.get(node.value.meta.chunkName)
934
+ // DependencyBundleGraph uses content keys as node ids, so we can use that
935
+ // to get the bundle id.
936
+ .add(bundleNodeId);
937
+ }
938
+ for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
939
+ // The `[request]` placeholder is not yet supported
940
+ if (bundleIds.size <= 1 || typeof chunkName === 'string' && chunkName.includes('[request]')) {
941
+ continue; // Nothing to merge
942
+ }
943
+
944
+ // Merge all bundles with the same chunk name into the first one.
945
+ let [firstBundleId, ...rest] = Array.from(bundleIds);
946
+ for (let bundleId of rest) {
947
+ mergeBundles(firstBundleId, bundleId, 'webpack-chunk-name');
948
+ }
949
+ }
950
+ }
951
+
952
+ // Step merge async bundles that meet the configured params
953
+ if (config.asyncBundleMerge) {
954
+ mergeAsyncBundles(config.asyncBundleMerge);
955
+ }
956
+
957
+ // Step merge shared bundles that meet the configured params
958
+ if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
959
+ mergeSharedBundles(config.sharedBundleMerge);
960
+ }
850
961
 
851
962
  // Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
852
963
  // their source bundles, and remove the bundle.
@@ -857,9 +968,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
857
968
  removeBundle(bundleGraph, bundleNodeId, assetReference);
858
969
  }
859
970
  }
860
- let modifiedSourceBundles = new Set();
861
971
 
862
972
  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
973
+
863
974
  if (config.disableSharedBundles === false) {
864
975
  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
865
976
  // Find shared bundles in this bundle group.
@@ -872,7 +983,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
872
983
  let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
873
984
  let bundle = (0, _nullthrows().default)(bundleGraph.getNode(b));
874
985
  (0, _assert().default)(bundle !== 'root');
875
- return count + (bundle.bundleBehavior !== 'inline');
986
+ return count + Number(bundle.bundleBehavior !== 'inline' && bundle.bundleBehavior !== 'inlineIsolated');
876
987
  }, 0);
877
988
  if (numBundlesContributingToPRL > config.maxParallelRequests) {
878
989
  let sharedBundleIdsInBundleGroup = bundleIdsInGroup.filter(b => {
@@ -901,7 +1012,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
901
1012
  // Remove bundles until the bundle group is within the parallel request limit.
902
1013
  while (sharedBundlesInGroup.length > 0 && numBundlesContributingToPRL > config.maxParallelRequests) {
903
1014
  let bundleTuple = sharedBundlesInGroup.pop();
1015
+ // @ts-expect-error TS18048
904
1016
  let bundleToRemove = bundleTuple.bundle;
1017
+ // @ts-expect-error TS18048
905
1018
  let bundleIdToRemove = bundleTuple.id;
906
1019
  //TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
907
1020
  // but total # bundles still exceeds limit due to non shared bundles
@@ -944,10 +1057,281 @@ function createIdealGraph(assetGraph, config, entries, logger) {
944
1057
  }
945
1058
  }
946
1059
  }
1060
+ function mergeBundles(bundleToKeepId, bundleToRemoveId, reason) {
1061
+ stats.trackMerge(bundleToKeepId, bundleToRemoveId, reason);
1062
+ let bundleToKeep = isNonRootBundle(bundleGraph.getNode(bundleToKeepId), `Bundle ${bundleToKeepId} not found`);
1063
+ let bundleToRemove = isNonRootBundle(bundleGraph.getNode(bundleToRemoveId), `Bundle ${bundleToRemoveId} not found`);
1064
+ modifiedSourceBundles.add(bundleToKeep);
1065
+ for (let asset of bundleToRemove.assets) {
1066
+ bundleToKeep.assets.add(asset);
1067
+ bundleToKeep.size += asset.stats.size;
1068
+ let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
1069
+
1070
+ // @ts-expect-error TS2345
1071
+ assetReference.set(asset, newAssetReference);
1072
+ }
1073
+
1074
+ // Merge any internalized assets
1075
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1076
+ if (bundleToKeep.internalizedAssets != null) {
1077
+ if (bundleToRemove.internalizedAssets != null) {
1078
+ bundleToKeep.internalizedAssets.intersect(bundleToRemove.internalizedAssets);
1079
+ } else {
1080
+ bundleToKeep.internalizedAssets.clear();
1081
+ }
1082
+ }
1083
+ } else {
1084
+ (0, _assert().default)(bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets, 'All shared bundles should have internalized assets');
1085
+ bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1086
+ }
1087
+
1088
+ // Merge and clean up source bundles
1089
+ for (let sourceBundleId of bundleToRemove.sourceBundles) {
1090
+ if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
1091
+ continue;
1092
+ }
1093
+ if (sourceBundleId !== bundleToKeepId) {
1094
+ bundleToKeep.sourceBundles.add(sourceBundleId);
1095
+ bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1096
+ }
1097
+ }
1098
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1099
+ bundleToKeep.sourceBundles.delete(bundleToRemoveId);
1100
+ for (let bundle of bundleGraph.getNodeIdsConnectedFrom(bundleToRemoveId)) {
1101
+ let bundleNode = (0, _nullthrows().default)(bundleGraph.getNode(bundle));
1102
+ if (bundleNode === 'root') {
1103
+ continue;
1104
+ }
1105
+
1106
+ // If the bundle is a source bundle, add it to the bundle to keep
1107
+ if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
1108
+ bundleNode.sourceBundles.delete(bundleToRemoveId);
1109
+ if (bundle !== bundleToKeepId) {
1110
+ bundleNode.sourceBundles.add(bundleToKeepId);
1111
+ bundleGraph.addEdge(bundleToKeepId, bundle);
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ // Merge bundle roots
1117
+ for (let bundleRoot of bundleToRemove.bundleRoots) {
1118
+ bundleToKeep.bundleRoots.add(bundleRoot);
1119
+ }
1120
+ if (bundleToRemove.mainEntryAsset != null) {
1121
+ (0, _assert().default)(bundleToKeep.mainEntryAsset != null);
1122
+
1123
+ // Merge the bundles in bundle group
1124
+ let bundlesInRemoveBundleGroup = getBundlesForBundleGroup(bundleToRemoveId);
1125
+ let removedBundleSharedBundles = new Set();
1126
+ for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
1127
+ if (bundleIdInGroup === bundleToRemoveId) {
1128
+ continue;
1129
+ }
1130
+ bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
1131
+ removedBundleSharedBundles.add(bundleIdInGroup);
1132
+ }
1133
+ if ((0, _featureFlags().getFeatureFlag)('removeRedundantSharedBundles')) {
1134
+ // Merge any shared bundles that now have the same source bundles due to
1135
+ // the current bundle merge
1136
+ let sharedBundles = new (_utils().DefaultMap)(() => []);
1137
+ for (let bundleId of removedBundleSharedBundles) {
1138
+ let bundleNode = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
1139
+ (0, _assert().default)(bundleNode !== 'root');
1140
+ if (bundleNode.mainEntryAsset != null || bundleNode.manualSharedBundle != null) {
1141
+ continue;
1142
+ }
1143
+ let key = Array.from(bundleNode.sourceBundles).filter(sourceBundle => sourceBundle !== bundleToRemoveId).sort().join(',') + '.' + bundleNode.type;
1144
+ sharedBundles.get(key).push(bundleId);
1145
+ }
1146
+ for (let sharedBundlesToMerge of sharedBundles.values()) {
1147
+ if (sharedBundlesToMerge.length > 1) {
1148
+ let [firstBundleId, ...rest] = sharedBundlesToMerge;
1149
+ for (let bundleId of rest) {
1150
+ mergeBundles(firstBundleId, bundleId, 'redundant-shared');
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ // Remove old bundle group
1157
+ bundleGroupBundleIds.delete(bundleToRemoveId);
1158
+
1159
+ // Clean up bundle roots
1160
+ let bundleRootToRemoveNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get((0, _nullthrows().default)(bundleToRemove.mainEntryAsset)));
1161
+ let bundleRootToKeepNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get((0, _nullthrows().default)(bundleToKeep.mainEntryAsset)));
1162
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(bundleRootToRemoveNodeId)) {
1163
+ bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
1164
+ bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
1165
+ }
1166
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(bundleRootToRemoveNodeId)) {
1167
+ bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
1168
+ bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
1169
+ }
1170
+ bundleRoots.set((0, _nullthrows().default)(bundleToRemove.mainEntryAsset), [bundleToKeepId, bundleToKeepId]);
1171
+
1172
+ // Merge dependency bundle graph
1173
+ for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)), _graph().ALL_EDGE_TYPES)) {
1174
+ let dependencyNode = (0, _nullthrows().default)(dependencyBundleGraph.getNode(dependencyNodeId));
1175
+ (0, _assert().default)(dependencyNode.type === 'dependency');
1176
+
1177
+ // Add dependency to the bundle to keep
1178
+ dependencyBundleGraph.addEdge(dependencyNodeId, dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)), dependencyPriorityEdges[dependencyNode.value.priority]);
1179
+ // Remove dependency from the bundle to remove
1180
+ dependencyBundleGraph.removeEdge(dependencyNodeId, dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)), dependencyPriorityEdges[dependencyNode.value.priority]);
1181
+ }
1182
+ }
1183
+ }
1184
+ bundleGraph.removeNode(bundleToRemoveId);
1185
+ }
1186
+ function mergeSharedBundles(mergeConfig) {
1187
+ // Find all shared bundles
1188
+ let sharedBundles = new Set();
1189
+ bundleGraph.traverse(nodeId => {
1190
+ let bundle = bundleGraph.getNode(nodeId);
1191
+ if (!bundle) {
1192
+ throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
1193
+ }
1194
+ if (bundle === 'root') {
1195
+ return;
1196
+ }
1197
+
1198
+ // Only consider JS shared bundles and non-reused bundles.
1199
+ // These count potentially be considered for merging in future but they're
1200
+ // more complicated to merge
1201
+ if (bundle.sourceBundles.size > 0 && bundle.manualSharedBundle == null && !bundle.mainEntryAsset && bundle.type === 'js') {
1202
+ sharedBundles.add(nodeId);
1203
+ }
1204
+ });
1205
+ let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), mergeConfig.map(config => {
1206
+ var _config$sourceBundles;
1207
+ return {
1208
+ ...config,
1209
+ sourceBundles: (_config$sourceBundles = config.sourceBundles) === null || _config$sourceBundles === void 0 ? void 0 : _config$sourceBundles.map(assetMatch => {
1210
+ let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
1211
+ if (sourceBundleNodeId == null) {
1212
+ throw new Error(`Source bundle ${assetMatch} not found in merge source bundle lookup`);
1213
+ }
1214
+ return sourceBundleNodeId;
1215
+ })
1216
+ };
1217
+ }));
1218
+ let mergedBundles = new Set();
1219
+ for (let cluster of clusters) {
1220
+ let [mergeTarget, ...rest] = cluster;
1221
+ for (let bundleIdToMerge of rest) {
1222
+ mergeBundles(mergeTarget, bundleIdToMerge, 'shared-merge');
1223
+ }
1224
+ mergedBundles.add(mergeTarget);
1225
+ }
1226
+ if ((0, _featureFlags().getFeatureFlag)('supportWebpackChunkName')) {
1227
+ return mergedBundles;
1228
+ }
1229
+ }
1230
+ function mergeAsyncBundles({
1231
+ bundleSize,
1232
+ maxOverfetchSize,
1233
+ ignore
1234
+ }) {
1235
+ let mergeCandidates = [];
1236
+ let ignoreRegexes = (ignore === null || ignore === void 0 ? void 0 : ignore.map(glob => (0, _utils().globToRegex)(glob))) ?? [];
1237
+ let isIgnored = bundle => {
1238
+ if (!bundle.mainEntryAsset) {
1239
+ return false;
1240
+ }
1241
+ let mainEntryFilePath = _path().default.relative(config.projectRoot, (0, _nullthrows().default)(bundle.mainEntryAsset).filePath);
1242
+ return ignoreRegexes.some(regex => regex.test(mainEntryFilePath));
1243
+ };
1244
+ for (let [_bundleRootAsset, [bundleRootBundleId]] of bundleRoots) {
1245
+ let bundleRootBundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleRootBundleId));
1246
+ (0, _assert().default)(bundleRootBundle !== 'root');
1247
+ if (bundleRootBundle.type === 'js' && bundleRootBundle.bundleBehavior !== 'inline' && bundleRootBundle.bundleBehavior !== 'inlineIsolated' && bundleRootBundle.size <= bundleSize && !isIgnored(bundleRootBundle)) {
1248
+ mergeCandidates.push(bundleRootBundleId);
1249
+ }
1250
+ }
1251
+ let candidates = [];
1252
+ for (let i = 0; i < mergeCandidates.length; i++) {
1253
+ for (let j = i + 1; j < mergeCandidates.length; j++) {
1254
+ let a = mergeCandidates[i];
1255
+ let b = mergeCandidates[j];
1256
+ if (a === b) continue; // Skip self-comparison
1257
+
1258
+ candidates.push(scoreAsyncMerge(a, b, maxOverfetchSize));
1259
+ }
1260
+ }
1261
+ let sortByScore = (a, b) => {
1262
+ let diff = a.score - b.score;
1263
+ if (diff > 0) {
1264
+ return 1;
1265
+ } else if (diff < 0) {
1266
+ return -1;
1267
+ }
1268
+ return 0;
1269
+ };
1270
+ candidates = candidates.filter(({
1271
+ overfetchSize,
1272
+ score
1273
+ }) => overfetchSize <= maxOverfetchSize && score > 0).sort(sortByScore);
1274
+
1275
+ // Tracks the bundles that have been merged
1276
+ let merged = new Set();
1277
+ // Tracks the deleted bundles to the bundle they were merged into.
1278
+ let mergeRemap = new Map();
1279
+ // Tracks the bundles that have been rescored and added back into the
1280
+ // candidates.
1281
+ let rescored = new (_utils().DefaultMap)(() => new Set());
1282
+ do {
1283
+ let [a, b] = (0, _nullthrows().default)(candidates.pop()).bundleIds;
1284
+ if (bundleGraph.hasNode(a) && bundleGraph.hasNode(b) && (!merged.has(a) && !merged.has(b) || rescored.get(a).has(b))) {
1285
+ mergeRemap.set(b, a);
1286
+ merged.add(a);
1287
+ rescored.get(a).clear();
1288
+ mergeBundles(a, b, 'async-merge');
1289
+ continue;
1290
+ }
1291
+
1292
+ // One or both of the bundles have been previously merged, so we'll
1293
+ // rescore and add the result back into the list of candidates.
1294
+ let getMergedBundleId = bundleId => {
1295
+ let seen = new Set();
1296
+ while (!bundleGraph.hasNode(bundleId) && !seen.has(bundleId)) {
1297
+ seen.add(bundleId);
1298
+ bundleId = (0, _nullthrows().default)(mergeRemap.get(bundleId));
1299
+ }
1300
+ if (!bundleGraph.hasNode(bundleId)) {
1301
+ return;
1302
+ }
1303
+ return bundleId;
1304
+ };
1305
+
1306
+ // Map a and b to their merged bundle ids if they've already been merged
1307
+ let currentA = getMergedBundleId(a);
1308
+ let currentB = getMergedBundleId(b);
1309
+ if (!currentA || !currentB ||
1310
+ // Bundles are already merged
1311
+ currentA === currentB) {
1312
+ // This combiniation is not valid, so we skip it.
1313
+ continue;
1314
+ }
1315
+ let candidate = scoreAsyncMerge(currentA, currentB, maxOverfetchSize);
1316
+ if (candidate.overfetchSize <= maxOverfetchSize && candidate.score > 0) {
1317
+ _sortedArrayFunctions().default.add(candidates, candidate, sortByScore);
1318
+ rescored.get(currentA).add(currentB);
1319
+ }
1320
+ } while (candidates.length > 0);
1321
+ }
1322
+ function getBundle(bundleId) {
1323
+ let bundle = bundleGraph.getNode(bundleId);
1324
+ if (bundle === 'root') {
1325
+ throw new Error(`Cannot access root bundle`);
1326
+ }
1327
+ if (bundle == null) {
1328
+ throw new Error(`Bundle ${bundleId} not found in bundle graph`);
1329
+ }
1330
+ return bundle;
1331
+ }
947
1332
  function getBigIntFromContentKey(contentKey) {
948
1333
  let b = Buffer.alloc(64);
949
1334
  b.write(contentKey);
950
- // $FlowFixMe Flow doesn't have BigInt types in this version
951
1335
  return b.readBigInt64BE();
952
1336
  }
953
1337
  // Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
@@ -977,6 +1361,53 @@ function createIdealGraph(assetGraph, config, entries, logger) {
977
1361
  }, bundleGroupId);
978
1362
  return bundlesInABundleGroup;
979
1363
  }
1364
+ function scoreAsyncMerge(bundleAId, bundleBId, maxOverfetchSize) {
1365
+ let bundleGroupA = new Set(getBundlesForBundleGroup(bundleAId));
1366
+ let bundleGroupB = new Set(getBundlesForBundleGroup(bundleBId));
1367
+ let overlapSize = 0;
1368
+ let overfetchSize = 0;
1369
+ for (let bundleId of new Set([...bundleGroupA, ...bundleGroupB])) {
1370
+ let bundle = getBundle(bundleId);
1371
+ if (bundleGroupA.has(bundleId) && bundleGroupB.has(bundleId)) {
1372
+ overlapSize += bundle.size;
1373
+ } else {
1374
+ overfetchSize += bundle.size;
1375
+ }
1376
+ }
1377
+ let overlapPercent = overlapSize / (overfetchSize + overlapSize);
1378
+ let bundleAParents = getBundleParents(bundleAId);
1379
+ let bundleBParents = getBundleParents(bundleBId);
1380
+ let sharedParentOverlap = 0;
1381
+ let sharedParentMismatch = 0;
1382
+ for (let bundleId of new Set([...bundleAParents, ...bundleBParents])) {
1383
+ if (bundleAParents.has(bundleId) && bundleBParents.has(bundleId)) {
1384
+ sharedParentOverlap++;
1385
+ } else {
1386
+ sharedParentMismatch++;
1387
+ }
1388
+ }
1389
+ let overfetchScore = overfetchSize / maxOverfetchSize;
1390
+ let sharedParentPercent = sharedParentOverlap / (sharedParentOverlap + sharedParentMismatch);
1391
+ let score = sharedParentPercent + overlapPercent + overfetchScore;
1392
+ return {
1393
+ overfetchSize,
1394
+ score,
1395
+ bundleIds: [bundleAId, bundleBId]
1396
+ };
1397
+ }
1398
+ function getBundleParents(bundleId) {
1399
+ let parents = new Set();
1400
+ let {
1401
+ bundleRoots
1402
+ } = getBundle(bundleId);
1403
+ for (let bundleRoot of bundleRoots) {
1404
+ let bundleRootNodeId = (0, _nullthrows().default)(assetToBundleRootNodeId.get(bundleRoot));
1405
+ for (let parentId of bundleRootGraph.getNodeIdsConnectedTo(bundleRootNodeId, _graph().ALL_EDGE_TYPES)) {
1406
+ parents.add(parentId);
1407
+ }
1408
+ }
1409
+ return parents;
1410
+ }
980
1411
  function getBundleFromBundleRoot(bundleRoot) {
981
1412
  let bundle = bundleGraph.getNode((0, _nullthrows().default)(bundleRoots.get(bundleRoot))[0]);
982
1413
  (0, _assert().default)(bundle !== 'root' && bundle != null);
@@ -1026,6 +1457,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1026
1457
  }
1027
1458
  bundleGraph.removeNode(bundleId);
1028
1459
  }
1460
+ stats.report(bundleId => {
1461
+ let bundle = bundleGraph.getNode(bundleId);
1462
+ (0, _assert().default)(bundle !== 'root');
1463
+ return bundle;
1464
+ });
1029
1465
  return {
1030
1466
  assets,
1031
1467
  bundleGraph,
@@ -1042,6 +1478,7 @@ function createBundle(opts) {
1042
1478
  bundleBehavior: opts.bundleBehavior,
1043
1479
  env: (0, _nullthrows().default)(opts.env),
1044
1480
  mainEntryAsset: null,
1481
+ bundleRoots: new Set(),
1045
1482
  manualSharedBundle: opts.manualSharedBundle,
1046
1483
  needsStableName: Boolean(opts.needsStableName),
1047
1484
  size: 0,
@@ -1057,6 +1494,7 @@ function createBundle(opts) {
1057
1494
  bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
1058
1495
  env: opts.env ?? asset.env,
1059
1496
  mainEntryAsset: asset,
1497
+ bundleRoots: new Set([asset]),
1060
1498
  manualSharedBundle: opts.manualSharedBundle,
1061
1499
  needsStableName: Boolean(opts.needsStableName),
1062
1500
  size: asset.stats.size,