@atlaspack/bundler-default 2.14.5-canary.16 → 2.14.5-canary.160

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.
@@ -1,5 +1,3 @@
1
- // @flow strict-local
2
-
3
1
  import path from 'path';
4
2
 
5
3
  import {getFeatureFlag} from '@atlaspack/feature-flags';
@@ -8,7 +6,7 @@ import {
8
6
  BitSet,
9
7
  ContentGraph,
10
8
  Graph,
11
- type NodeId,
9
+ NodeId,
12
10
  } from '@atlaspack/graph';
13
11
  import type {
14
12
  Asset,
@@ -23,37 +21,38 @@ import {DefaultMap, globToRegex} from '@atlaspack/utils';
23
21
  import invariant from 'assert';
24
22
  import nullthrows from 'nullthrows';
25
23
 
26
- import {findMergeCandidates} from './bundleMerge';
27
- import type {ResolvedBundlerConfig} from './bundlerConfig';
24
+ import {findMergeCandidates, MergeGroup} from './bundleMerge';
25
+ import type {ResolvedBundlerConfig, MergeCandidates} from './bundlerConfig';
28
26
 
29
27
  /* BundleRoot - An asset that is the main entry of a Bundle. */
30
28
  type BundleRoot = Asset;
31
29
 
32
- export type Bundle = {|
33
- uniqueKey: ?string,
34
- assets: Set<Asset>,
35
- internalizedAssets?: BitSet,
36
- bundleBehavior?: ?BundleBehavior,
37
- needsStableName: boolean,
38
- mainEntryAsset: ?Asset,
39
- size: number,
40
- sourceBundles: Set<NodeId>,
41
- target: Target,
42
- env: Environment,
43
- type: string,
44
- manualSharedBundle: ?string, // for naming purposes
45
- |};
30
+ export type Bundle = {
31
+ uniqueKey: string | null | undefined;
32
+ assets: Set<Asset>;
33
+ internalizedAssets?: BitSet;
34
+ bundleBehavior?: BundleBehavior | null | undefined;
35
+ needsStableName: boolean;
36
+ mainEntryAsset: Asset | null | undefined;
37
+ bundleRoots: Set<Asset>;
38
+ size: number;
39
+ sourceBundles: Set<NodeId>;
40
+ target: Target;
41
+ env: Environment;
42
+ type: string;
43
+ manualSharedBundle: string | null | undefined; // for naming purposes;
44
+ };
46
45
 
47
46
  export type DependencyBundleGraph = ContentGraph<
48
- | {|
49
- value: Bundle,
50
- type: 'bundle',
51
- |}
52
- | {|
53
- value: Dependency,
54
- type: 'dependency',
55
- |},
56
- number,
47
+ | {
48
+ value: Bundle;
49
+ type: 'bundle';
50
+ }
51
+ | {
52
+ value: Dependency;
53
+ type: 'dependency';
54
+ },
55
+ number
57
56
  >;
58
57
 
59
58
  const dependencyPriorityEdges = {
@@ -61,7 +60,7 @@ const dependencyPriorityEdges = {
61
60
  parallel: 2,
62
61
  lazy: 3,
63
62
  conditional: 4,
64
- };
63
+ } as const;
65
64
 
66
65
  export const idealBundleGraphEdges = Object.freeze({
67
66
  default: 1,
@@ -70,20 +69,30 @@ export const idealBundleGraphEdges = Object.freeze({
70
69
 
71
70
  export type IdealBundleGraph = Graph<
72
71
  Bundle | 'root',
73
- $Values<typeof idealBundleGraphEdges>,
72
+ (typeof idealBundleGraphEdges)[keyof typeof idealBundleGraphEdges]
74
73
  >;
75
74
 
76
75
  // IdealGraph is the structure we will pass to decorate,
77
76
  // which mutates the assetGraph into the bundleGraph we would
78
77
  // expect from default bundler
79
- export type IdealGraph = {|
80
- assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>,
81
- assets: Array<Asset>,
82
- bundleGraph: IdealBundleGraph,
83
- bundleGroupBundleIds: Set<NodeId>,
84
- dependencyBundleGraph: DependencyBundleGraph,
85
- manualAssetToBundle: Map<Asset, NodeId>,
86
- |};
78
+ export type IdealGraph = {
79
+ assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>;
80
+ assets: Array<Asset>;
81
+ bundleGraph: IdealBundleGraph;
82
+ bundleGroupBundleIds: Set<NodeId>;
83
+ dependencyBundleGraph: DependencyBundleGraph;
84
+ manualAssetToBundle: Map<Asset, NodeId>;
85
+ };
86
+
87
+ function isNonRootBundle(
88
+ bundle?: Bundle | 'root' | null,
89
+ message?: string,
90
+ ): Bundle {
91
+ let existingBundle = nullthrows(bundle, message);
92
+ invariant(existingBundle !== 'root', "Bundle cannot be 'root'");
93
+
94
+ return existingBundle;
95
+ }
87
96
 
88
97
  export function createIdealGraph(
89
98
  assetGraph: MutableBundleGraph,
@@ -97,7 +106,7 @@ export function createIdealGraph(
97
106
  let dependencyBundleGraph: DependencyBundleGraph = new ContentGraph();
98
107
  let assetReference: DefaultMap<
99
108
  Asset,
100
- Array<[Dependency, Bundle]>,
109
+ Array<[Dependency, Bundle]>
101
110
  > = new DefaultMap(() => []);
102
111
 
103
112
  // A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their
@@ -111,8 +120,9 @@ export function createIdealGraph(
111
120
  };
112
121
  // Graph that models bundleRoots, with parallel & async deps only to inform reachability
113
122
  let bundleRootGraph: Graph<
114
- number, // asset index
115
- $Values<typeof bundleRootEdgeTypes>,
123
+ // asset index
124
+ number,
125
+ (typeof bundleRootEdgeTypes)[keyof typeof bundleRootEdgeTypes]
116
126
  > = new Graph();
117
127
  let assetToBundleRootNodeId = new Map<BundleRoot, number>();
118
128
 
@@ -146,7 +156,7 @@ export function createIdealGraph(
146
156
  bundleGroupBundleIds.add(nodeId);
147
157
  }
148
158
 
149
- let assets = [];
159
+ let assets: Array<Asset> = [];
150
160
  let assetToIndex = new Map<Asset, number>();
151
161
 
152
162
  function makeManualAssetToConfigLookup() {
@@ -161,6 +171,7 @@ export function createIdealGraph(
161
171
 
162
172
  for (let c of config.manualSharedBundles) {
163
173
  if (c.root != null) {
174
+ // @ts-expect-error TS2345
164
175
  parentsToConfig.get(path.join(config.projectRoot, c.root)).push(c);
165
176
  }
166
177
  }
@@ -204,17 +215,17 @@ export function createIdealGraph(
204
215
  config.projectRoot,
205
216
  node.value.filePath,
206
217
  );
207
- if (!assetRegexes.some((regex) => regex.test(projectRelativePath))) {
208
- return;
209
- }
210
218
 
211
219
  // We track all matching MSB's for constant modules as they are never duplicated
212
220
  // and need to be assigned to all matching bundles
213
221
  if (node.value.meta.isConstantModule === true) {
222
+ // @ts-expect-error TS2345
214
223
  constantModuleToMSB.get(node.value).push(c);
215
224
  }
216
- manualAssetToConfig.set(node.value, c);
217
- return;
225
+
226
+ if (assetRegexes.some((regex) => regex.test(projectRelativePath))) {
227
+ manualAssetToConfig.set(node.value, c);
228
+ }
218
229
  }
219
230
 
220
231
  if (
@@ -242,9 +253,19 @@ export function createIdealGraph(
242
253
  makeManualAssetToConfigLookup();
243
254
  let manualBundleToInternalizedAsset: DefaultMap<
244
255
  NodeId,
245
- Array<Asset>,
256
+ Array<Asset>
246
257
  > = new DefaultMap(() => []);
247
258
 
259
+ let mergeSourceBundleLookup = new Map<string, NodeId>();
260
+ let mergeSourceBundleAssets = new Set(
261
+ config.sharedBundleMerge?.flatMap(
262
+ (c) =>
263
+ c.sourceBundles?.map((assetMatch: string) =>
264
+ path.join(config.projectRoot, assetMatch),
265
+ ) ?? [],
266
+ ),
267
+ );
268
+
248
269
  /**
249
270
  * Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
250
271
  * for asset type changes, parallel, inline, and async or lazy dependencies,
@@ -252,7 +273,27 @@ export function createIdealGraph(
252
273
  */
253
274
  assetGraph.traverse(
254
275
  {
255
- enter(node, context, actions) {
276
+ enter(
277
+ node: // @ts-expect-error TS2304
278
+ | BundleGraphTraversable
279
+ | {
280
+ readonly type: 'dependency';
281
+ value: Dependency;
282
+ },
283
+ context:
284
+ | {
285
+ readonly type: 'asset';
286
+ value: Asset;
287
+ }
288
+ | null
289
+ | undefined
290
+ | {
291
+ readonly type: 'dependency';
292
+ value: Dependency;
293
+ },
294
+ // @ts-expect-error TS2304
295
+ actions: TraversalActions,
296
+ ) {
256
297
  if (node.type === 'asset') {
257
298
  if (
258
299
  context?.type === 'dependency' &&
@@ -312,7 +353,8 @@ export function createIdealGraph(
312
353
  dependency.priority === 'lazy' ||
313
354
  (getFeatureFlag('conditionalBundlingApi') &&
314
355
  node.value.priority === 'conditional') ||
315
- childAsset.bundleBehavior === 'isolated' // An isolated Dependency, or Bundle must contain all assets it needs to load.
356
+ childAsset.bundleBehavior === 'isolated' || // An isolated Dependency, or Bundle must contain all assets it needs to load.
357
+ childAsset.bundleBehavior === 'inlineIsolated'
316
358
  ) {
317
359
  if (bundleId == null) {
318
360
  let firstBundleGroup = nullthrows(
@@ -325,7 +367,9 @@ export function createIdealGraph(
325
367
  dependency.bundleBehavior ?? childAsset.bundleBehavior,
326
368
  needsStableName:
327
369
  dependency.bundleBehavior === 'inline' ||
328
- childAsset.bundleBehavior === 'inline'
370
+ childAsset.bundleBehavior === 'inline' ||
371
+ dependency.bundleBehavior === 'inlineIsolated' ||
372
+ childAsset.bundleBehavior === 'inlineIsolated'
329
373
  ? false
330
374
  : dependency.isEntry || dependency.needsStableName,
331
375
  target: firstBundleGroup.target,
@@ -335,6 +379,14 @@ export function createIdealGraph(
335
379
  bundleRoots.set(childAsset, [bundleId, bundleId]);
336
380
  bundleGroupBundleIds.add(bundleId);
337
381
  bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
382
+ // If this asset is relevant for merging then track it's source
383
+ // bundle id for later
384
+ if (mergeSourceBundleAssets.has(childAsset.filePath)) {
385
+ mergeSourceBundleLookup.set(
386
+ path.relative(config.projectRoot, childAsset.filePath),
387
+ bundleId,
388
+ );
389
+ }
338
390
  if (manualSharedObject) {
339
391
  // MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
340
392
  // since MSBs should not have main entry assets
@@ -349,7 +401,8 @@ export function createIdealGraph(
349
401
  if (
350
402
  // If this dependency requests isolated, but the bundle is not,
351
403
  // make the bundle isolated for all uses.
352
- dependency.bundleBehavior === 'isolated' &&
404
+ (dependency.bundleBehavior === 'isolated' ||
405
+ dependency.bundleBehavior === 'inlineIsolated') &&
353
406
  bundle.bundleBehavior == null
354
407
  ) {
355
408
  bundle.bundleBehavior = dependency.bundleBehavior;
@@ -371,6 +424,7 @@ export function createIdealGraph(
371
424
  type: 'bundle',
372
425
  },
373
426
  ),
427
+ // @ts-expect-error TS7053
374
428
  dependencyPriorityEdges[dependency.priority],
375
429
  );
376
430
 
@@ -432,6 +486,7 @@ export function createIdealGraph(
432
486
  needsStableName:
433
487
  childAsset.bundleBehavior === 'inline' ||
434
488
  dependency.bundleBehavior === 'inline' ||
489
+ dependency.bundleBehavior === 'inlineIsolated' ||
435
490
  (dependency.priority === 'parallel' &&
436
491
  !dependency.needsStableName)
437
492
  ? false
@@ -445,7 +500,8 @@ export function createIdealGraph(
445
500
  if (
446
501
  // If this dependency requests isolated, but the bundle is not,
447
502
  // make the bundle isolated for all uses.
448
- dependency.bundleBehavior === 'isolated' &&
503
+ (dependency.bundleBehavior === 'isolated' ||
504
+ dependency.bundleBehavior === 'inlineIsolated') &&
449
505
  bundle.bundleBehavior == null
450
506
  ) {
451
507
  bundle.bundleBehavior = dependency.bundleBehavior;
@@ -482,6 +538,7 @@ export function createIdealGraph(
482
538
 
483
539
  assetReference.get(childAsset).push([dependency, bundle]);
484
540
  } else {
541
+ // @ts-expect-error TS2322
485
542
  bundleId = null;
486
543
  }
487
544
  if (manualSharedObject && bundleId != null) {
@@ -489,6 +546,7 @@ export function createIdealGraph(
489
546
  // add the asset if it doesn't already have it and set key
490
547
 
491
548
  invariant(
549
+ // @ts-expect-error TS2367
492
550
  bundle !== 'root' && bundle != null && bundleId != null,
493
551
  );
494
552
 
@@ -515,7 +573,8 @@ export function createIdealGraph(
515
573
  }
516
574
  return node;
517
575
  },
518
- exit(node) {
576
+ // @ts-expect-error TS2322
577
+ exit(node: BundleGraphTraversable) {
519
578
  if (stack[stack.length - 1]?.[0] === node.value) {
520
579
  stack.pop();
521
580
  }
@@ -564,20 +623,20 @@ export function createIdealGraph(
564
623
 
565
624
  // reachableRoots is an array of bit sets for each asset. Each bit set
566
625
  // indicates which bundle roots are reachable from that asset synchronously.
567
- let reachableRoots = [];
626
+ let reachableRoots: Array<BitSet> = [];
568
627
  for (let i = 0; i < assets.length; i++) {
569
628
  reachableRoots.push(new BitSet(bundleRootGraph.nodes.length));
570
629
  }
571
630
 
572
631
  // reachableAssets is the inverse mapping of reachableRoots. For each bundle root,
573
632
  // it contains a bit set that indicates which assets are reachable from it.
574
- let reachableAssets = [];
633
+ let reachableAssets: Array<BitSet> = [];
575
634
 
576
635
  // ancestorAssets maps bundle roots to the set of all assets available to it at runtime,
577
636
  // including in earlier parallel bundles. These are intersected through all paths to
578
637
  // the bundle to ensure that the available assets are always present no matter in which
579
638
  // order the bundles are loaded.
580
- let ancestorAssets = [];
639
+ let ancestorAssets: Array<null | BitSet> = [];
581
640
 
582
641
  let inlineConstantDeps = new DefaultMap(() => new Set());
583
642
 
@@ -688,7 +747,10 @@ export function createIdealGraph(
688
747
  // not true that a bundle's available assets = all assets of all the bundleGroups
689
748
  // it belongs to. It's the intersection of those sets.
690
749
  let available;
691
- if (bundleRoot.bundleBehavior === 'isolated') {
750
+ if (
751
+ bundleRoot.bundleBehavior === 'isolated' ||
752
+ bundleRoot.bundleBehavior === 'inlineIsolated'
753
+ ) {
692
754
  available = new BitSet(assets.length);
693
755
  } else {
694
756
  available = nullthrows(ancestorAssets[nodeId]).clone();
@@ -772,7 +834,8 @@ export function createIdealGraph(
772
834
 
773
835
  let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, ALL_EDGE_TYPES);
774
836
  let canDelete =
775
- getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated';
837
+ getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated' &&
838
+ getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'inlineIsolated';
776
839
  if (parentRoots.length === 0) continue;
777
840
  for (let parentId of parentRoots) {
778
841
  if (parentId === rootNodeId) {
@@ -806,8 +869,11 @@ export function createIdealGraph(
806
869
 
807
870
  function assignInlineConstants(parentAsset: Asset, bundle: Bundle) {
808
871
  for (let inlineConstant of inlineConstantDeps.get(parentAsset)) {
872
+ // @ts-expect-error TS2345
809
873
  if (!bundle.assets.has(inlineConstant)) {
874
+ // @ts-expect-error TS2345
810
875
  bundle.assets.add(inlineConstant);
876
+ // @ts-expect-error TS18046
811
877
  bundle.size += inlineConstant.stats.size;
812
878
  }
813
879
  }
@@ -849,7 +915,8 @@ export function createIdealGraph(
849
915
  !a.isBundleSplittable ||
850
916
  (bundleRoots.get(a) &&
851
917
  (getBundleFromBundleRoot(a).needsStableName ||
852
- getBundleFromBundleRoot(a).bundleBehavior === 'isolated'))
918
+ getBundleFromBundleRoot(a).bundleBehavior === 'isolated' ||
919
+ getBundleFromBundleRoot(a).bundleBehavior === 'inlineIsolated'))
853
920
  ) {
854
921
  // Add asset to non-splittable bundles.
855
922
  addAssetToBundleRoot(asset, a);
@@ -867,7 +934,7 @@ export function createIdealGraph(
867
934
  let bundle;
868
935
  let bundleId;
869
936
  let manualSharedBundleKey = manualSharedObject.name + ',' + asset.type;
870
- let sourceBundles = [];
937
+ let sourceBundles: Array<NodeId> = [];
871
938
  reachable.forEach((id) => {
872
939
  sourceBundles.push(nullthrows(bundleRoots.get(assets[id]))[0]);
873
940
  });
@@ -982,7 +1049,7 @@ export function createIdealGraph(
982
1049
  });
983
1050
  }
984
1051
 
985
- let reachableArray = [];
1052
+ let reachableArray: Array<Asset> = [];
986
1053
  reachable.forEach((id) => {
987
1054
  reachableArray.push(assets[id]);
988
1055
  });
@@ -1057,6 +1124,8 @@ export function createIdealGraph(
1057
1124
  }
1058
1125
 
1059
1126
  let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
1127
+ let modifiedSourceBundles = new Set<Bundle>();
1128
+
1060
1129
  // Step split manual shared bundles for those that have the "split" property set
1061
1130
  let remainderMap = new DefaultMap(() => []);
1062
1131
  for (let id of manualSharedMap.values()) {
@@ -1075,9 +1144,9 @@ export function createIdealGraph(
1075
1144
  if (modNum != null) {
1076
1145
  for (let a of [...manualBundle.assets]) {
1077
1146
  let numRep = getBigIntFromContentKey(a.id);
1078
- // $FlowFixMe Flow doesn't know about BigInt
1079
1147
  let r = Number(numRep % BigInt(modNum));
1080
1148
 
1149
+ // @ts-expect-error TS2345
1081
1150
  remainderMap.get(r).push(a);
1082
1151
  }
1083
1152
 
@@ -1100,8 +1169,10 @@ export function createIdealGraph(
1100
1169
  }
1101
1170
  for (let sp of remainderMap.get(i)) {
1102
1171
  bundle.assets.add(sp);
1172
+ // @ts-expect-error TS2339
1103
1173
  bundle.size += sp.stats.size;
1104
1174
  manualBundle.assets.delete(sp);
1175
+ // @ts-expect-error TS2339
1105
1176
  manualBundle.size -= sp.stats.size;
1106
1177
  }
1107
1178
  }
@@ -1114,6 +1185,7 @@ export function createIdealGraph(
1114
1185
  // match multiple MSB's
1115
1186
  for (let [asset, msbs] of constantModuleToMSB.entries()) {
1116
1187
  for (let manualSharedObject of msbs) {
1188
+ // @ts-expect-error TS2339
1117
1189
  let bundleId = manualSharedMap.get(manualSharedObject.name + ',js');
1118
1190
  if (bundleId == null) continue;
1119
1191
  let bundle = nullthrows(bundleGraph.getNode(bundleId));
@@ -1122,17 +1194,94 @@ export function createIdealGraph(
1122
1194
  'We tried to use the root incorrectly',
1123
1195
  );
1124
1196
 
1197
+ // @ts-expect-error TS2345
1125
1198
  if (!bundle.assets.has(asset)) {
1199
+ // @ts-expect-error TS2345
1126
1200
  bundle.assets.add(asset);
1201
+ // @ts-expect-error TS18046
1127
1202
  bundle.size += asset.stats.size;
1128
1203
  }
1129
1204
  }
1130
1205
  }
1131
1206
 
1207
+ if (getFeatureFlag('supportWebpackChunkName')) {
1208
+ // Merge webpack chunk name bundles
1209
+ let chunkNameBundles = new DefaultMap(() => new Set());
1210
+ for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
1211
+ // meta.chunkName is set by the Rust transformer, so we just need to find
1212
+ // bundles that have a chunkName set.
1213
+ if (
1214
+ !node ||
1215
+ node.type !== 'dependency' ||
1216
+ node.value.meta.chunkName == null
1217
+ ) {
1218
+ continue;
1219
+ }
1220
+
1221
+ let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(
1222
+ nodeId,
1223
+ dependencyPriorityEdges[node.value.priority],
1224
+ );
1225
+
1226
+ if (connectedBundles.length === 0) {
1227
+ continue;
1228
+ }
1229
+
1230
+ invariant(
1231
+ connectedBundles.length === 1,
1232
+ 'Expected webpackChunkName dependency to be connected to no more than one bundle',
1233
+ );
1234
+
1235
+ let bundleId = connectedBundles[0];
1236
+ let bundleNode = dependencyBundleGraph.getNode(bundleId);
1237
+ invariant(bundleNode != null && bundleNode.type === 'bundle');
1238
+
1239
+ // If a bundle does not have a main entry asset, it's somehow just a
1240
+ // shared bundle, and will be merged/deleted by other means.
1241
+ if (bundleNode.value.mainEntryAsset == null) {
1242
+ continue;
1243
+ }
1244
+
1245
+ let bundleNodeId = null;
1246
+ let mainEntryAssetId = bundleNode.value.mainEntryAsset?.id;
1247
+
1248
+ if (mainEntryAssetId != null) {
1249
+ bundleNodeId = bundles.get(mainEntryAssetId);
1250
+ }
1251
+
1252
+ if (bundleNodeId == null) {
1253
+ continue;
1254
+ }
1255
+
1256
+ chunkNameBundles
1257
+ .get(node.value.meta.chunkName)
1258
+ // DependencyBundleGraph uses content keys as node ids, so we can use that
1259
+ // to get the bundle id.
1260
+ .add(bundleNodeId);
1261
+ }
1262
+
1263
+ for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
1264
+ // The `[request]` placeholder is not yet supported
1265
+ if (
1266
+ bundleIds.size <= 1 ||
1267
+ (typeof chunkName === 'string' && chunkName.includes('[request]'))
1268
+ ) {
1269
+ continue; // Nothing to merge
1270
+ }
1271
+
1272
+ // Merge all bundles with the same chunk name into the first one.
1273
+ let [firstBundleId, ...rest] = Array.from(bundleIds);
1274
+ for (let bundleId of rest) {
1275
+ // @ts-expect-error TS2345
1276
+ mergeBundles(firstBundleId, bundleId);
1277
+ }
1278
+ }
1279
+ }
1280
+
1132
1281
  // Step merge shared bundles that meet the overlap threshold
1133
1282
  // This step is skipped by default as the threshold defaults to 1
1134
- if (config.sharedBundleMergeThreshold < 1) {
1135
- mergeOverlapBundles();
1283
+ if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
1284
+ mergeOverlapBundles(config.sharedBundleMerge);
1136
1285
  }
1137
1286
 
1138
1287
  // Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
@@ -1151,7 +1300,6 @@ export function createIdealGraph(
1151
1300
  }
1152
1301
 
1153
1302
  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
1154
- let modifiedSourceBundles = new Set();
1155
1303
 
1156
1304
  if (config.disableSharedBundles === false) {
1157
1305
  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
@@ -1165,7 +1313,13 @@ export function createIdealGraph(
1165
1313
  let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
1166
1314
  let bundle = nullthrows(bundleGraph.getNode(b));
1167
1315
  invariant(bundle !== 'root');
1168
- return count + (bundle.bundleBehavior !== 'inline');
1316
+ return (
1317
+ count +
1318
+ Number(
1319
+ bundle.bundleBehavior !== 'inline' &&
1320
+ bundle.bundleBehavior !== 'inlineIsolated',
1321
+ )
1322
+ );
1169
1323
  }, 0);
1170
1324
 
1171
1325
  if (numBundlesContributingToPRL > config.maxParallelRequests) {
@@ -1200,7 +1354,9 @@ export function createIdealGraph(
1200
1354
  numBundlesContributingToPRL > config.maxParallelRequests
1201
1355
  ) {
1202
1356
  let bundleTuple = sharedBundlesInGroup.pop();
1357
+ // @ts-expect-error TS18048
1203
1358
  let bundleToRemove = bundleTuple.bundle;
1359
+ // @ts-expect-error TS18048
1204
1360
  let bundleIdToRemove = bundleTuple.id;
1205
1361
  //TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
1206
1362
  // but total # bundles still exceeds limit due to non shared bundles
@@ -1256,28 +1412,50 @@ export function createIdealGraph(
1256
1412
  }
1257
1413
  }
1258
1414
 
1259
- function mergeBundles(
1260
- bundleGraph: IdealBundleGraph,
1261
- bundleToKeepId: NodeId,
1262
- bundleToRemoveId: NodeId,
1263
- assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>,
1264
- ) {
1265
- let bundleToKeep = nullthrows(bundleGraph.getNode(bundleToKeepId));
1266
- let bundleToRemove = nullthrows(bundleGraph.getNode(bundleToRemoveId));
1267
- invariant(bundleToKeep !== 'root' && bundleToRemove !== 'root');
1415
+ function mergeBundles(bundleToKeepId: NodeId, bundleToRemoveId: NodeId) {
1416
+ let bundleToKeep = isNonRootBundle(
1417
+ bundleGraph.getNode(bundleToKeepId),
1418
+ `Bundle ${bundleToKeepId} not found`,
1419
+ );
1420
+ let bundleToRemove = isNonRootBundle(
1421
+ bundleGraph.getNode(bundleToRemoveId),
1422
+ `Bundle ${bundleToRemoveId} not found`,
1423
+ );
1424
+ modifiedSourceBundles.add(bundleToKeep);
1268
1425
  for (let asset of bundleToRemove.assets) {
1269
1426
  bundleToKeep.assets.add(asset);
1270
1427
  bundleToKeep.size += asset.stats.size;
1271
1428
 
1272
1429
  let newAssetReference = assetReference
1273
1430
  .get(asset)
1274
- .map(([dep, bundle]) =>
1431
+ .map(([dep, bundle]: [any, any]) =>
1275
1432
  bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
1276
1433
  );
1277
1434
 
1435
+ // @ts-expect-error TS2345
1278
1436
  assetReference.set(asset, newAssetReference);
1279
1437
  }
1280
1438
 
1439
+ // Merge any internalized assets
1440
+ if (getFeatureFlag('supportWebpackChunkName')) {
1441
+ if (bundleToKeep.internalizedAssets != null) {
1442
+ if (bundleToRemove.internalizedAssets != null) {
1443
+ bundleToKeep.internalizedAssets.intersect(
1444
+ bundleToRemove.internalizedAssets,
1445
+ );
1446
+ } else {
1447
+ bundleToKeep.internalizedAssets.clear();
1448
+ }
1449
+ }
1450
+ } else {
1451
+ invariant(
1452
+ bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
1453
+ 'All shared bundles should have internalized assets',
1454
+ );
1455
+ bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1456
+ }
1457
+
1458
+ // Merge and clean up source bundles
1281
1459
  for (let sourceBundleId of bundleToRemove.sourceBundles) {
1282
1460
  if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
1283
1461
  continue;
@@ -1287,21 +1465,108 @@ export function createIdealGraph(
1287
1465
  bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1288
1466
  }
1289
1467
 
1290
- // Merge any internalized assets
1291
- if (bundleToRemove.internalizedAssets) {
1292
- if (bundleToKeep.internalizedAssets) {
1293
- bundleToKeep.internalizedAssets.union(
1294
- bundleToRemove.internalizedAssets,
1468
+ if (getFeatureFlag('supportWebpackChunkName')) {
1469
+ bundleToKeep.sourceBundles.delete(bundleToRemoveId);
1470
+
1471
+ for (let bundle of bundleGraph.getNodeIdsConnectedFrom(
1472
+ bundleToRemoveId,
1473
+ )) {
1474
+ let bundleNode = nullthrows(bundleGraph.getNode(bundle));
1475
+ if (bundleNode === 'root') {
1476
+ continue;
1477
+ }
1478
+
1479
+ // If the bundle is a source bundle, add it to the bundle to keep
1480
+ if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
1481
+ bundleNode.sourceBundles.add(bundleToKeepId);
1482
+ bundleNode.sourceBundles.delete(bundleToRemoveId);
1483
+ bundleGraph.addEdge(bundleToKeepId, bundle);
1484
+ }
1485
+ }
1486
+
1487
+ // Merge bundle roots
1488
+ for (let bundleRoot of bundleToRemove.bundleRoots) {
1489
+ bundleToKeep.bundleRoots.add(bundleRoot);
1490
+ }
1491
+
1492
+ if (bundleToRemove.mainEntryAsset != null) {
1493
+ invariant(bundleToKeep.mainEntryAsset != null);
1494
+
1495
+ // Merge the bundles in bundle group
1496
+ let bundlesInRemoveBundleGroup =
1497
+ getBundlesForBundleGroup(bundleToRemoveId);
1498
+
1499
+ for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
1500
+ if (bundleIdInGroup === bundleToRemoveId) {
1501
+ continue;
1502
+ }
1503
+ bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
1504
+ }
1505
+
1506
+ // Remove old bundle group
1507
+ bundleGroupBundleIds.delete(bundleToRemoveId);
1508
+
1509
+ // Clean up bundle roots
1510
+ let bundleRootToRemoveNodeId = nullthrows(
1511
+ assetToBundleRootNodeId.get(
1512
+ nullthrows(bundleToRemove.mainEntryAsset),
1513
+ ),
1295
1514
  );
1296
- } else {
1297
- bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
1515
+ let bundleRootToKeepNodeId = nullthrows(
1516
+ assetToBundleRootNodeId.get(nullthrows(bundleToKeep.mainEntryAsset)),
1517
+ );
1518
+
1519
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(
1520
+ bundleRootToRemoveNodeId,
1521
+ )) {
1522
+ bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
1523
+ bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
1524
+ }
1525
+
1526
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(
1527
+ bundleRootToRemoveNodeId,
1528
+ )) {
1529
+ bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
1530
+ bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
1531
+ }
1532
+
1533
+ bundleRoots.set(nullthrows(bundleToRemove.mainEntryAsset), [
1534
+ bundleToKeepId,
1535
+ bundleToKeepId,
1536
+ ]);
1537
+
1538
+ // Merge dependency bundle graph
1539
+ for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(
1540
+ dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)),
1541
+ ALL_EDGE_TYPES,
1542
+ )) {
1543
+ let dependencyNode = nullthrows(
1544
+ dependencyBundleGraph.getNode(dependencyNodeId),
1545
+ );
1546
+ invariant(dependencyNode.type === 'dependency');
1547
+
1548
+ // Add dependency to the bundle to keep
1549
+ dependencyBundleGraph.addEdge(
1550
+ dependencyNodeId,
1551
+ dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)),
1552
+ dependencyPriorityEdges[dependencyNode.value.priority],
1553
+ );
1554
+ // Remove dependency from the bundle to remove
1555
+ dependencyBundleGraph.removeEdge(
1556
+ dependencyNodeId,
1557
+ dependencyBundleGraph.getNodeIdByContentKey(
1558
+ String(bundleToRemoveId),
1559
+ ),
1560
+ dependencyPriorityEdges[dependencyNode.value.priority],
1561
+ );
1562
+ }
1298
1563
  }
1299
1564
  }
1300
1565
 
1301
1566
  bundleGraph.removeNode(bundleToRemoveId);
1302
1567
  }
1303
1568
 
1304
- function mergeOverlapBundles() {
1569
+ function mergeOverlapBundles(mergeConfig: MergeCandidates) {
1305
1570
  // Find all shared bundles
1306
1571
  let sharedBundles = new Set<NodeId>();
1307
1572
  bundleGraph.traverse((nodeId) => {
@@ -1331,22 +1596,44 @@ export function createIdealGraph(
1331
1596
  let clusters = findMergeCandidates(
1332
1597
  bundleGraph,
1333
1598
  Array.from(sharedBundles),
1334
- config.sharedBundleMergeThreshold,
1599
+ mergeConfig.map(
1600
+ (config): MergeGroup => ({
1601
+ ...config,
1602
+ sourceBundles: config.sourceBundles?.map((assetMatch: string) => {
1603
+ let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
1604
+
1605
+ if (sourceBundleNodeId == null) {
1606
+ throw new Error(
1607
+ `Source bundle ${assetMatch} not found in merge source bundle lookup`,
1608
+ );
1609
+ }
1610
+
1611
+ return sourceBundleNodeId;
1612
+ }),
1613
+ }),
1614
+ ),
1335
1615
  );
1336
1616
 
1617
+ let mergedBundles = new Set();
1618
+
1337
1619
  for (let cluster of clusters) {
1338
1620
  let [mergeTarget, ...rest] = cluster;
1339
1621
 
1340
1622
  for (let bundleIdToMerge of rest) {
1341
- mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
1623
+ mergeBundles(mergeTarget, bundleIdToMerge);
1342
1624
  }
1625
+
1626
+ mergedBundles.add(mergeTarget);
1627
+ }
1628
+
1629
+ if (getFeatureFlag('supportWebpackChunkName')) {
1630
+ return mergedBundles;
1343
1631
  }
1344
1632
  }
1345
1633
 
1346
- function getBigIntFromContentKey(contentKey) {
1634
+ function getBigIntFromContentKey(contentKey: string) {
1347
1635
  let b = Buffer.alloc(64);
1348
1636
  b.write(contentKey);
1349
- // $FlowFixMe Flow doesn't have BigInt types in this version
1350
1637
  return b.readBigInt64BE();
1351
1638
  }
1352
1639
  // Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
@@ -1373,8 +1660,8 @@ export function createIdealGraph(
1373
1660
  bundleRootGraph.removeNode(bundleRootId);
1374
1661
  }
1375
1662
  }
1376
- function getBundlesForBundleGroup(bundleGroupId) {
1377
- let bundlesInABundleGroup = [];
1663
+ function getBundlesForBundleGroup(bundleGroupId: NodeId) {
1664
+ let bundlesInABundleGroup: Array<NodeId> = [];
1378
1665
  bundleGraph.traverse((nodeId) => {
1379
1666
  bundlesInABundleGroup.push(nodeId);
1380
1667
  }, bundleGroupId);
@@ -1455,23 +1742,24 @@ export function createIdealGraph(
1455
1742
  };
1456
1743
  }
1457
1744
 
1458
- function createBundle(opts: {|
1459
- asset?: Asset,
1460
- bundleBehavior?: ?BundleBehavior,
1461
- env?: Environment,
1462
- manualSharedBundle?: ?string,
1463
- needsStableName?: boolean,
1464
- sourceBundles?: Set<NodeId>,
1465
- target: Target,
1466
- type?: string,
1467
- uniqueKey?: string,
1468
- |}): Bundle {
1745
+ function createBundle(opts: {
1746
+ asset?: Asset;
1747
+ bundleBehavior?: BundleBehavior | null | undefined;
1748
+ env?: Environment;
1749
+ manualSharedBundle?: string | null | undefined;
1750
+ needsStableName?: boolean;
1751
+ sourceBundles?: Set<NodeId>;
1752
+ target: Target;
1753
+ type?: string;
1754
+ uniqueKey?: string;
1755
+ }): Bundle {
1469
1756
  if (opts.asset == null) {
1470
1757
  return {
1471
1758
  assets: new Set(),
1472
1759
  bundleBehavior: opts.bundleBehavior,
1473
1760
  env: nullthrows(opts.env),
1474
1761
  mainEntryAsset: null,
1762
+ bundleRoots: new Set(),
1475
1763
  manualSharedBundle: opts.manualSharedBundle,
1476
1764
  needsStableName: Boolean(opts.needsStableName),
1477
1765
  size: 0,
@@ -1488,6 +1776,7 @@ function createBundle(opts: {|
1488
1776
  bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
1489
1777
  env: opts.env ?? asset.env,
1490
1778
  mainEntryAsset: asset,
1779
+ bundleRoots: new Set([asset]),
1491
1780
  manualSharedBundle: opts.manualSharedBundle,
1492
1781
  needsStableName: Boolean(opts.needsStableName),
1493
1782
  size: asset.stats.size,