@atlaspack/bundler-default 2.14.5-canary.20 → 2.14.5-canary.201

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +411 -0
  2. package/dist/DefaultBundler.js +84 -0
  3. package/dist/MonolithicBundler.js +68 -0
  4. package/dist/bundleMerge.js +137 -0
  5. package/dist/bundlerConfig.js +223 -0
  6. package/dist/decorateLegacyGraph.js +189 -0
  7. package/dist/idealGraph.js +1488 -0
  8. package/dist/memoize.js +31 -0
  9. package/dist/stats.js +69 -0
  10. package/lib/DefaultBundler.js +6 -1
  11. package/lib/MonolithicBundler.js +11 -3
  12. package/lib/bundleMerge.js +106 -37
  13. package/lib/bundlerConfig.js +51 -5
  14. package/lib/decorateLegacyGraph.js +24 -3
  15. package/lib/idealGraph.js +426 -50
  16. package/lib/memoize.js +39 -0
  17. package/lib/stats.js +85 -0
  18. package/lib/types/DefaultBundler.d.ts +18 -0
  19. package/lib/types/MonolithicBundler.d.ts +2 -0
  20. package/lib/types/bundleMerge.d.ts +9 -0
  21. package/lib/types/bundlerConfig.d.ts +36 -0
  22. package/lib/types/decorateLegacyGraph.d.ts +3 -0
  23. package/lib/types/idealGraph.d.ts +40 -0
  24. package/lib/types/memoize.d.ts +2 -0
  25. package/lib/types/stats.d.ts +16 -0
  26. package/package.json +20 -12
  27. package/src/{DefaultBundler.js → DefaultBundler.ts} +21 -6
  28. package/src/{MonolithicBundler.js → MonolithicBundler.ts} +17 -5
  29. package/src/bundleMerge.ts +250 -0
  30. package/src/{bundlerConfig.js → bundlerConfig.ts} +105 -44
  31. package/src/{decorateLegacyGraph.js → decorateLegacyGraph.ts} +26 -7
  32. package/src/{idealGraph.js → idealGraph.ts} +669 -102
  33. package/src/memoize.ts +32 -0
  34. package/src/stats.ts +97 -0
  35. package/tsconfig.json +30 -0
  36. package/tsconfig.tsbuildinfo +1 -0
  37. package/src/bundleMerge.js +0 -103
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 () {
@@ -48,8 +55,10 @@ function _nullthrows() {
48
55
  return data;
49
56
  }
50
57
  var _bundleMerge = require("./bundleMerge");
58
+ var _stats = require("./stats");
51
59
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
52
60
  /* BundleRoot - An asset that is the main entry of a Bundle. */
61
+
53
62
  const dependencyPriorityEdges = {
54
63
  sync: 1,
55
64
  parallel: 2,
@@ -65,12 +74,19 @@ const idealBundleGraphEdges = exports.idealBundleGraphEdges = Object.freeze({
65
74
  // which mutates the assetGraph into the bundleGraph we would
66
75
  // expect from default bundler
67
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
+ }
68
82
  function createIdealGraph(assetGraph, config, entries, logger) {
83
+ var _config$sharedBundleM;
69
84
  // Asset to the bundle and group it's an entry of
70
85
  let bundleRoots = new Map();
71
86
  let bundles = new Map();
72
87
  let dependencyBundleGraph = new (_graph().ContentGraph)();
73
88
  let assetReference = new (_utils().DefaultMap)(() => []);
89
+ let stats = new _stats.Stats(config.projectRoot);
74
90
 
75
91
  // A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their
76
92
  // referencing Bundle. There are no actual BundleGroup nodes, just bundles that take on that role.
@@ -108,15 +124,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
108
124
  }
109
125
  let assets = [];
110
126
  let assetToIndex = new Map();
111
- //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
112
- let manualSharedMap = new Map();
113
- // May need a map to be able to look up NON- bundle root assets which need special case instructions
114
- // Use this when placing assets into bundles, to avoid duplication
115
- let manualAssetToBundle = new Map();
116
- let {
117
- manualAssetToConfig,
118
- constantModuleToMSB
119
- } = function makeManualAssetToConfigLookup() {
127
+ function makeManualAssetToConfigLookup() {
120
128
  let manualAssetToConfig = new Map();
121
129
  let constantModuleToMSB = new (_utils().DefaultMap)(() => []);
122
130
  if (config.manualSharedBundles.length === 0) {
@@ -128,6 +136,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
128
136
  let parentsToConfig = new (_utils().DefaultMap)(() => []);
129
137
  for (let c of config.manualSharedBundles) {
130
138
  if (c.root != null) {
139
+ // @ts-expect-error TS2345
131
140
  parentsToConfig.get(_path().default.join(config.projectRoot, c.root)).push(c);
132
141
  }
133
142
  }
@@ -160,17 +169,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
160
169
  assetGraph.traverse((node, _, actions) => {
161
170
  if (node.type === 'asset' && (!Array.isArray(c.types) || c.types.includes(node.value.type))) {
162
171
  let projectRelativePath = _path().default.relative(config.projectRoot, node.value.filePath);
163
- if (!assetRegexes.some(regex => regex.test(projectRelativePath))) {
164
- return;
165
- }
166
172
 
167
173
  // We track all matching MSB's for constant modules as they are never duplicated
168
174
  // and need to be assigned to all matching bundles
169
175
  if (node.value.meta.isConstantModule === true) {
176
+ // @ts-expect-error TS2345
170
177
  constantModuleToMSB.get(node.value).push(c);
171
178
  }
172
- manualAssetToConfig.set(node.value, c);
173
- return;
179
+ if (assetRegexes.some(regex => regex.test(projectRelativePath))) {
180
+ manualAssetToConfig.set(node.value, c);
181
+ }
174
182
  }
175
183
  if (node.type === 'dependency' && (node.value.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional') && parentAsset) {
176
184
  // Don't walk past the bundle group assets
@@ -182,8 +190,23 @@ function createIdealGraph(assetGraph, config, entries, logger) {
182
190
  manualAssetToConfig,
183
191
  constantModuleToMSB
184
192
  };
185
- }();
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();
186
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
+ }));
187
210
 
188
211
  /**
189
212
  * Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
@@ -191,7 +214,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
191
214
  * adding only that asset to each bundle, not its entire subgraph.
192
215
  */
193
216
  assetGraph.traverse({
194
- enter(node, context, actions) {
217
+ enter(node, context,
218
+ // @ts-expect-error TS2304
219
+ actions) {
195
220
  if (node.type === 'asset') {
196
221
  if ((context === null || context === void 0 ? void 0 : context.type) === 'dependency' && context !== null && context !== void 0 && context.value.isEntry && !entries.has(node.value)) {
197
222
  // Skip whole subtrees of other targets by skipping those entries
@@ -234,15 +259,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
234
259
  manualSharedBundleKey != null && manualSharedMap.has(manualSharedBundleKey)) {
235
260
  bundleId = (0, _nullthrows().default)(manualSharedMap.get(manualSharedBundleKey));
236
261
  }
237
- 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.
238
- ) {
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') {
239
265
  if (bundleId == null) {
240
266
  let firstBundleGroup = (0, _nullthrows().default)(bundleGraph.getNode(stack[0][1]));
241
267
  (0, _assert().default)(firstBundleGroup !== 'root');
242
268
  bundle = createBundle({
243
269
  asset: childAsset,
244
270
  bundleBehavior: dependency.bundleBehavior ?? childAsset.bundleBehavior,
245
- 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,
246
272
  target: firstBundleGroup.target
247
273
  });
248
274
  bundleId = bundleGraph.addNode(bundle);
@@ -250,6 +276,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
250
276
  bundleRoots.set(childAsset, [bundleId, bundleId]);
251
277
  bundleGroupBundleIds.add(bundleId);
252
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
+ }
253
284
  if (manualSharedObject) {
254
285
  // MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
255
286
  // since MSBs should not have main entry assets
@@ -261,7 +292,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
261
292
  if (
262
293
  // If this dependency requests isolated, but the bundle is not,
263
294
  // make the bundle isolated for all uses.
264
- dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
295
+ (dependency.bundleBehavior === 'isolated' || dependency.bundleBehavior === 'inlineIsolated') && bundle.bundleBehavior == null) {
265
296
  bundle.bundleBehavior = dependency.bundleBehavior;
266
297
  }
267
298
  }
@@ -271,7 +302,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
271
302
  }), dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(bundleId), {
272
303
  value: bundle,
273
304
  type: 'bundle'
274
- }), dependencyPriorityEdges[dependency.priority]);
305
+ }),
306
+ // @ts-expect-error TS7053
307
+ dependencyPriorityEdges[dependency.priority]);
275
308
  if ((0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && dependency.priority === 'conditional') {
276
309
  let [referencingBundleRoot, bundleGroupNodeId] = (0, _nullthrows().default)(stack[stack.length - 1]);
277
310
  let referencingBundleId = (0, _nullthrows().default)(bundleRoots.get(referencingBundleRoot))[0];
@@ -300,7 +333,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
300
333
  env: childAsset.env,
301
334
  bundleBehavior: dependency.bundleBehavior ?? childAsset.bundleBehavior,
302
335
  target: referencingBundle.target,
303
- 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
304
337
  });
305
338
  bundleId = bundleGraph.addNode(bundle);
306
339
  } else {
@@ -309,7 +342,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
309
342
  if (
310
343
  // If this dependency requests isolated, but the bundle is not,
311
344
  // make the bundle isolated for all uses.
312
- dependency.bundleBehavior === 'isolated' && bundle.bundleBehavior == null) {
345
+ (dependency.bundleBehavior === 'isolated' || dependency.bundleBehavior === 'inlineIsolated') && bundle.bundleBehavior == null) {
313
346
  bundle.bundleBehavior = dependency.bundleBehavior;
314
347
  }
315
348
  }
@@ -331,13 +364,16 @@ function createIdealGraph(assetGraph, config, entries, logger) {
331
364
  }
332
365
  assetReference.get(childAsset).push([dependency, bundle]);
333
366
  } else {
367
+ // @ts-expect-error TS2322
334
368
  bundleId = null;
335
369
  }
336
370
  if (manualSharedObject && bundleId != null) {
337
371
  // MSB Step 5: At this point we've either created or found an existing MSB bundle
338
372
  // add the asset if it doesn't already have it and set key
339
373
 
340
- (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);
341
377
  manualAssetToBundle.set(childAsset, bundleId);
342
378
  if (!bundle.assets.has(childAsset)) {
343
379
  // Add asset to bundle
@@ -358,6 +394,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
358
394
  }
359
395
  return node;
360
396
  },
397
+ // @ts-expect-error TS2322
361
398
  exit(node) {
362
399
  var _stack;
363
400
  if (((_stack = stack[stack.length - 1]) === null || _stack === void 0 ? void 0 : _stack[0]) === node.value) {
@@ -495,7 +532,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
495
532
  // not true that a bundle's available assets = all assets of all the bundleGroups
496
533
  // it belongs to. It's the intersection of those sets.
497
534
  let available;
498
- if (bundleRoot.bundleBehavior === 'isolated') {
535
+ if (bundleRoot.bundleBehavior === 'isolated' || bundleRoot.bundleBehavior === 'inlineIsolated') {
499
536
  available = new (_graph().BitSet)(assets.length);
500
537
  } else {
501
538
  available = (0, _nullthrows().default)(ancestorAssets[nodeId]).clone();
@@ -558,7 +595,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
558
595
  continue;
559
596
  }
560
597
  let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, _graph().ALL_EDGE_TYPES);
561
- let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated';
598
+ let canDelete = getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated' && getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'inlineIsolated';
562
599
  if (parentRoots.length === 0) continue;
563
600
  for (let parentId of parentRoots) {
564
601
  var _ancestorAssets$paren;
@@ -586,8 +623,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
586
623
  }
587
624
  function assignInlineConstants(parentAsset, bundle) {
588
625
  for (let inlineConstant of inlineConstantDeps.get(parentAsset)) {
626
+ // @ts-expect-error TS2345
589
627
  if (!bundle.assets.has(inlineConstant)) {
628
+ // @ts-expect-error TS2345
590
629
  bundle.assets.add(inlineConstant);
630
+ // @ts-expect-error TS18046
591
631
  bundle.size += inlineConstant.stats.size;
592
632
  }
593
633
  }
@@ -623,7 +663,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
623
663
  let assetId = bundleRootGraph.getNode(nodeId);
624
664
  if (assetId == null) return; // deleted
625
665
  let a = assets[assetId];
626
- 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')) {
627
667
  // Add asset to non-splittable bundles.
628
668
  addAssetToBundleRoot(asset, a);
629
669
  } else if (!((_ancestorAssets$nodeI = ancestorAssets[nodeId]) !== null && _ancestorAssets$nodeI !== void 0 && _ancestorAssets$nodeI.has(i))) {
@@ -785,6 +825,8 @@ function createIdealGraph(assetGraph, config, entries, logger) {
785
825
  }
786
826
  }
787
827
  let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
828
+ let modifiedSourceBundles = new Set();
829
+
788
830
  // Step split manual shared bundles for those that have the "split" property set
789
831
  let remainderMap = new (_utils().DefaultMap)(() => []);
790
832
  for (let id of manualSharedMap.values()) {
@@ -801,8 +843,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
801
843
  if (modNum != null) {
802
844
  for (let a of [...manualBundle.assets]) {
803
845
  let numRep = getBigIntFromContentKey(a.id);
804
- // $FlowFixMe Flow doesn't know about BigInt
805
846
  let r = Number(numRep % BigInt(modNum));
847
+
848
+ // @ts-expect-error TS2345
806
849
  remainderMap.get(r).push(a);
807
850
  }
808
851
  for (let i = 1; i < [...remainderMap.keys()].length; i++) {
@@ -824,8 +867,10 @@ function createIdealGraph(assetGraph, config, entries, logger) {
824
867
  }
825
868
  for (let sp of remainderMap.get(i)) {
826
869
  bundle.assets.add(sp);
870
+ // @ts-expect-error TS2339
827
871
  bundle.size += sp.stats.size;
828
872
  manualBundle.assets.delete(sp);
873
+ // @ts-expect-error TS2339
829
874
  manualBundle.size -= sp.stats.size;
830
875
  }
831
876
  }
@@ -838,21 +883,80 @@ function createIdealGraph(assetGraph, config, entries, logger) {
838
883
  // match multiple MSB's
839
884
  for (let [asset, msbs] of constantModuleToMSB.entries()) {
840
885
  for (let manualSharedObject of msbs) {
886
+ // @ts-expect-error TS2339
841
887
  let bundleId = manualSharedMap.get(manualSharedObject.name + ',js');
842
888
  if (bundleId == null) continue;
843
889
  let bundle = (0, _nullthrows().default)(bundleGraph.getNode(bundleId));
844
890
  (0, _assert().default)(bundle != null && bundle !== 'root', 'We tried to use the root incorrectly');
891
+
892
+ // @ts-expect-error TS2345
845
893
  if (!bundle.assets.has(asset)) {
894
+ // @ts-expect-error TS2345
846
895
  bundle.assets.add(asset);
896
+ // @ts-expect-error TS18046
847
897
  bundle.size += asset.stats.size;
848
898
  }
849
899
  }
850
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
+ }
851
956
 
852
- // Step merge shared bundles that meet the overlap threshold
853
- // This step is skipped by default as the threshold defaults to 1
854
- if (config.sharedBundleMergeThreshold < 1) {
855
- mergeOverlapBundles();
957
+ // Step merge shared bundles that meet the configured params
958
+ if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
959
+ mergeSharedBundles(config.sharedBundleMerge);
856
960
  }
857
961
 
858
962
  // Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
@@ -866,7 +970,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
866
970
  }
867
971
 
868
972
  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
869
- let modifiedSourceBundles = new Set();
973
+
870
974
  if (config.disableSharedBundles === false) {
871
975
  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
872
976
  // Find shared bundles in this bundle group.
@@ -879,7 +983,7 @@ function createIdealGraph(assetGraph, config, entries, logger) {
879
983
  let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
880
984
  let bundle = (0, _nullthrows().default)(bundleGraph.getNode(b));
881
985
  (0, _assert().default)(bundle !== 'root');
882
- return count + (bundle.bundleBehavior !== 'inline');
986
+ return count + Number(bundle.bundleBehavior !== 'inline' && bundle.bundleBehavior !== 'inlineIsolated');
883
987
  }, 0);
884
988
  if (numBundlesContributingToPRL > config.maxParallelRequests) {
885
989
  let sharedBundleIdsInBundleGroup = bundleIdsInGroup.filter(b => {
@@ -908,7 +1012,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
908
1012
  // Remove bundles until the bundle group is within the parallel request limit.
909
1013
  while (sharedBundlesInGroup.length > 0 && numBundlesContributingToPRL > config.maxParallelRequests) {
910
1014
  let bundleTuple = sharedBundlesInGroup.pop();
1015
+ // @ts-expect-error TS18048
911
1016
  let bundleToRemove = bundleTuple.bundle;
1017
+ // @ts-expect-error TS18048
912
1018
  let bundleIdToRemove = bundleTuple.id;
913
1019
  //TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
914
1020
  // but total # bundles still exceeds limit due to non shared bundles
@@ -951,35 +1057,133 @@ function createIdealGraph(assetGraph, config, entries, logger) {
951
1057
  }
952
1058
  }
953
1059
  }
954
- function mergeBundles(bundleGraph, bundleToKeepId, bundleToRemoveId, assetReference) {
955
- let bundleToKeep = (0, _nullthrows().default)(bundleGraph.getNode(bundleToKeepId));
956
- let bundleToRemove = (0, _nullthrows().default)(bundleGraph.getNode(bundleToRemoveId));
957
- (0, _assert().default)(bundleToKeep !== 'root' && bundleToRemove !== 'root');
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);
958
1065
  for (let asset of bundleToRemove.assets) {
959
1066
  bundleToKeep.assets.add(asset);
960
1067
  bundleToKeep.size += asset.stats.size;
961
1068
  let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
1069
+
1070
+ // @ts-expect-error TS2345
962
1071
  assetReference.set(asset, newAssetReference);
963
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
964
1089
  for (let sourceBundleId of bundleToRemove.sourceBundles) {
965
1090
  if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
966
1091
  continue;
967
1092
  }
968
- bundleToKeep.sourceBundles.add(sourceBundleId);
969
- bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1093
+ if (sourceBundleId !== bundleToKeepId) {
1094
+ bundleToKeep.sourceBundles.add(sourceBundleId);
1095
+ bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1096
+ }
970
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
+ }
971
1105
 
972
- // Merge any internalized assets
973
- if (bundleToRemove.internalizedAssets) {
974
- if (bundleToKeep.internalizedAssets) {
975
- bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
976
- } else {
977
- bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
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
+ }
978
1182
  }
979
1183
  }
980
1184
  bundleGraph.removeNode(bundleToRemoveId);
981
1185
  }
982
- function mergeOverlapBundles() {
1186
+ function mergeSharedBundles(mergeConfig) {
983
1187
  // Find all shared bundles
984
1188
  let sharedBundles = new Set();
985
1189
  bundleGraph.traverse(nodeId => {
@@ -998,18 +1202,136 @@ function createIdealGraph(assetGraph, config, entries, logger) {
998
1202
  sharedBundles.add(nodeId);
999
1203
  }
1000
1204
  });
1001
- let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), config.sharedBundleMergeThreshold);
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();
1002
1219
  for (let cluster of clusters) {
1003
1220
  let [mergeTarget, ...rest] = cluster;
1004
1221
  for (let bundleIdToMerge of rest) {
1005
- mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
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));
1006
1259
  }
1007
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;
1008
1331
  }
1009
1332
  function getBigIntFromContentKey(contentKey) {
1010
1333
  let b = Buffer.alloc(64);
1011
1334
  b.write(contentKey);
1012
- // $FlowFixMe Flow doesn't have BigInt types in this version
1013
1335
  return b.readBigInt64BE();
1014
1336
  }
1015
1337
  // Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
@@ -1039,6 +1361,53 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1039
1361
  }, bundleGroupId);
1040
1362
  return bundlesInABundleGroup;
1041
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
+ }
1042
1411
  function getBundleFromBundleRoot(bundleRoot) {
1043
1412
  let bundle = bundleGraph.getNode((0, _nullthrows().default)(bundleRoots.get(bundleRoot))[0]);
1044
1413
  (0, _assert().default)(bundle !== 'root' && bundle != null);
@@ -1088,6 +1457,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
1088
1457
  }
1089
1458
  bundleGraph.removeNode(bundleId);
1090
1459
  }
1460
+ stats.report(bundleId => {
1461
+ let bundle = bundleGraph.getNode(bundleId);
1462
+ (0, _assert().default)(bundle !== 'root');
1463
+ return bundle;
1464
+ });
1091
1465
  return {
1092
1466
  assets,
1093
1467
  bundleGraph,
@@ -1104,6 +1478,7 @@ function createBundle(opts) {
1104
1478
  bundleBehavior: opts.bundleBehavior,
1105
1479
  env: (0, _nullthrows().default)(opts.env),
1106
1480
  mainEntryAsset: null,
1481
+ bundleRoots: new Set(),
1107
1482
  manualSharedBundle: opts.manualSharedBundle,
1108
1483
  needsStableName: Boolean(opts.needsStableName),
1109
1484
  size: 0,
@@ -1119,6 +1494,7 @@ function createBundle(opts) {
1119
1494
  bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
1120
1495
  env: opts.env ?? asset.env,
1121
1496
  mainEntryAsset: asset,
1497
+ bundleRoots: new Set([asset]),
1122
1498
  manualSharedBundle: opts.manualSharedBundle,
1123
1499
  needsStableName: Boolean(opts.needsStableName),
1124
1500
  size: asset.stats.size,