@atlaspack/bundler-default 2.14.5-canary.14 → 2.14.5-canary.141

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' &&
@@ -335,6 +376,14 @@ export function createIdealGraph(
335
376
  bundleRoots.set(childAsset, [bundleId, bundleId]);
336
377
  bundleGroupBundleIds.add(bundleId);
337
378
  bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
379
+ // If this asset is relevant for merging then track it's source
380
+ // bundle id for later
381
+ if (mergeSourceBundleAssets.has(childAsset.filePath)) {
382
+ mergeSourceBundleLookup.set(
383
+ path.relative(config.projectRoot, childAsset.filePath),
384
+ bundleId,
385
+ );
386
+ }
338
387
  if (manualSharedObject) {
339
388
  // MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
340
389
  // since MSBs should not have main entry assets
@@ -371,6 +420,7 @@ export function createIdealGraph(
371
420
  type: 'bundle',
372
421
  },
373
422
  ),
423
+ // @ts-expect-error TS7053
374
424
  dependencyPriorityEdges[dependency.priority],
375
425
  );
376
426
 
@@ -482,6 +532,7 @@ export function createIdealGraph(
482
532
 
483
533
  assetReference.get(childAsset).push([dependency, bundle]);
484
534
  } else {
535
+ // @ts-expect-error TS2322
485
536
  bundleId = null;
486
537
  }
487
538
  if (manualSharedObject && bundleId != null) {
@@ -489,6 +540,7 @@ export function createIdealGraph(
489
540
  // add the asset if it doesn't already have it and set key
490
541
 
491
542
  invariant(
543
+ // @ts-expect-error TS2367
492
544
  bundle !== 'root' && bundle != null && bundleId != null,
493
545
  );
494
546
 
@@ -515,7 +567,8 @@ export function createIdealGraph(
515
567
  }
516
568
  return node;
517
569
  },
518
- exit(node) {
570
+ // @ts-expect-error TS2322
571
+ exit(node: BundleGraphTraversable) {
519
572
  if (stack[stack.length - 1]?.[0] === node.value) {
520
573
  stack.pop();
521
574
  }
@@ -564,20 +617,20 @@ export function createIdealGraph(
564
617
 
565
618
  // reachableRoots is an array of bit sets for each asset. Each bit set
566
619
  // indicates which bundle roots are reachable from that asset synchronously.
567
- let reachableRoots = [];
620
+ let reachableRoots: Array<BitSet> = [];
568
621
  for (let i = 0; i < assets.length; i++) {
569
622
  reachableRoots.push(new BitSet(bundleRootGraph.nodes.length));
570
623
  }
571
624
 
572
625
  // reachableAssets is the inverse mapping of reachableRoots. For each bundle root,
573
626
  // it contains a bit set that indicates which assets are reachable from it.
574
- let reachableAssets = [];
627
+ let reachableAssets: Array<BitSet> = [];
575
628
 
576
629
  // ancestorAssets maps bundle roots to the set of all assets available to it at runtime,
577
630
  // including in earlier parallel bundles. These are intersected through all paths to
578
631
  // the bundle to ensure that the available assets are always present no matter in which
579
632
  // order the bundles are loaded.
580
- let ancestorAssets = [];
633
+ let ancestorAssets: Array<null | BitSet> = [];
581
634
 
582
635
  let inlineConstantDeps = new DefaultMap(() => new Set());
583
636
 
@@ -806,8 +859,11 @@ export function createIdealGraph(
806
859
 
807
860
  function assignInlineConstants(parentAsset: Asset, bundle: Bundle) {
808
861
  for (let inlineConstant of inlineConstantDeps.get(parentAsset)) {
862
+ // @ts-expect-error TS2345
809
863
  if (!bundle.assets.has(inlineConstant)) {
864
+ // @ts-expect-error TS2345
810
865
  bundle.assets.add(inlineConstant);
866
+ // @ts-expect-error TS18046
811
867
  bundle.size += inlineConstant.stats.size;
812
868
  }
813
869
  }
@@ -867,7 +923,7 @@ export function createIdealGraph(
867
923
  let bundle;
868
924
  let bundleId;
869
925
  let manualSharedBundleKey = manualSharedObject.name + ',' + asset.type;
870
- let sourceBundles = [];
926
+ let sourceBundles: Array<NodeId> = [];
871
927
  reachable.forEach((id) => {
872
928
  sourceBundles.push(nullthrows(bundleRoots.get(assets[id]))[0]);
873
929
  });
@@ -982,7 +1038,7 @@ export function createIdealGraph(
982
1038
  });
983
1039
  }
984
1040
 
985
- let reachableArray = [];
1041
+ let reachableArray: Array<Asset> = [];
986
1042
  reachable.forEach((id) => {
987
1043
  reachableArray.push(assets[id]);
988
1044
  });
@@ -1057,6 +1113,8 @@ export function createIdealGraph(
1057
1113
  }
1058
1114
 
1059
1115
  let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
1116
+ let modifiedSourceBundles = new Set<Bundle>();
1117
+
1060
1118
  // Step split manual shared bundles for those that have the "split" property set
1061
1119
  let remainderMap = new DefaultMap(() => []);
1062
1120
  for (let id of manualSharedMap.values()) {
@@ -1075,9 +1133,9 @@ export function createIdealGraph(
1075
1133
  if (modNum != null) {
1076
1134
  for (let a of [...manualBundle.assets]) {
1077
1135
  let numRep = getBigIntFromContentKey(a.id);
1078
- // $FlowFixMe Flow doesn't know about BigInt
1079
1136
  let r = Number(numRep % BigInt(modNum));
1080
1137
 
1138
+ // @ts-expect-error TS2345
1081
1139
  remainderMap.get(r).push(a);
1082
1140
  }
1083
1141
 
@@ -1100,8 +1158,10 @@ export function createIdealGraph(
1100
1158
  }
1101
1159
  for (let sp of remainderMap.get(i)) {
1102
1160
  bundle.assets.add(sp);
1161
+ // @ts-expect-error TS2339
1103
1162
  bundle.size += sp.stats.size;
1104
1163
  manualBundle.assets.delete(sp);
1164
+ // @ts-expect-error TS2339
1105
1165
  manualBundle.size -= sp.stats.size;
1106
1166
  }
1107
1167
  }
@@ -1114,6 +1174,7 @@ export function createIdealGraph(
1114
1174
  // match multiple MSB's
1115
1175
  for (let [asset, msbs] of constantModuleToMSB.entries()) {
1116
1176
  for (let manualSharedObject of msbs) {
1177
+ // @ts-expect-error TS2339
1117
1178
  let bundleId = manualSharedMap.get(manualSharedObject.name + ',js');
1118
1179
  if (bundleId == null) continue;
1119
1180
  let bundle = nullthrows(bundleGraph.getNode(bundleId));
@@ -1122,17 +1183,94 @@ export function createIdealGraph(
1122
1183
  'We tried to use the root incorrectly',
1123
1184
  );
1124
1185
 
1186
+ // @ts-expect-error TS2345
1125
1187
  if (!bundle.assets.has(asset)) {
1188
+ // @ts-expect-error TS2345
1126
1189
  bundle.assets.add(asset);
1190
+ // @ts-expect-error TS18046
1127
1191
  bundle.size += asset.stats.size;
1128
1192
  }
1129
1193
  }
1130
1194
  }
1131
1195
 
1196
+ if (getFeatureFlag('supportWebpackChunkName')) {
1197
+ // Merge webpack chunk name bundles
1198
+ let chunkNameBundles = new DefaultMap(() => new Set());
1199
+ for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
1200
+ // meta.chunkName is set by the Rust transformer, so we just need to find
1201
+ // bundles that have a chunkName set.
1202
+ if (
1203
+ !node ||
1204
+ node.type !== 'dependency' ||
1205
+ node.value.meta.chunkName == null
1206
+ ) {
1207
+ continue;
1208
+ }
1209
+
1210
+ let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(
1211
+ nodeId,
1212
+ dependencyPriorityEdges[node.value.priority],
1213
+ );
1214
+
1215
+ if (connectedBundles.length === 0) {
1216
+ continue;
1217
+ }
1218
+
1219
+ invariant(
1220
+ connectedBundles.length === 1,
1221
+ 'Expected webpackChunkName dependency to be connected to no more than one bundle',
1222
+ );
1223
+
1224
+ let bundleId = connectedBundles[0];
1225
+ let bundleNode = dependencyBundleGraph.getNode(bundleId);
1226
+ invariant(bundleNode != null && bundleNode.type === 'bundle');
1227
+
1228
+ // If a bundle does not have a main entry asset, it's somehow just a
1229
+ // shared bundle, and will be merged/deleted by other means.
1230
+ if (bundleNode.value.mainEntryAsset == null) {
1231
+ continue;
1232
+ }
1233
+
1234
+ let bundleNodeId = null;
1235
+ let mainEntryAssetId = bundleNode.value.mainEntryAsset?.id;
1236
+
1237
+ if (mainEntryAssetId != null) {
1238
+ bundleNodeId = bundles.get(mainEntryAssetId);
1239
+ }
1240
+
1241
+ if (bundleNodeId == null) {
1242
+ continue;
1243
+ }
1244
+
1245
+ chunkNameBundles
1246
+ .get(node.value.meta.chunkName)
1247
+ // DependencyBundleGraph uses content keys as node ids, so we can use that
1248
+ // to get the bundle id.
1249
+ .add(bundleNodeId);
1250
+ }
1251
+
1252
+ for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
1253
+ // The `[request]` placeholder is not yet supported
1254
+ if (
1255
+ bundleIds.size <= 1 ||
1256
+ (typeof chunkName === 'string' && chunkName.includes('[request]'))
1257
+ ) {
1258
+ continue; // Nothing to merge
1259
+ }
1260
+
1261
+ // Merge all bundles with the same chunk name into the first one.
1262
+ let [firstBundleId, ...rest] = Array.from(bundleIds);
1263
+ for (let bundleId of rest) {
1264
+ // @ts-expect-error TS2345
1265
+ mergeBundles(firstBundleId, bundleId);
1266
+ }
1267
+ }
1268
+ }
1269
+
1132
1270
  // Step merge shared bundles that meet the overlap threshold
1133
1271
  // This step is skipped by default as the threshold defaults to 1
1134
- if (config.sharedBundleMergeThreshold < 1) {
1135
- mergeOverlapBundles();
1272
+ if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
1273
+ mergeOverlapBundles(config.sharedBundleMerge);
1136
1274
  }
1137
1275
 
1138
1276
  // Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
@@ -1151,7 +1289,6 @@ export function createIdealGraph(
1151
1289
  }
1152
1290
 
1153
1291
  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
1154
- let modifiedSourceBundles = new Set();
1155
1292
 
1156
1293
  if (config.disableSharedBundles === false) {
1157
1294
  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
@@ -1165,6 +1302,7 @@ export function createIdealGraph(
1165
1302
  let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
1166
1303
  let bundle = nullthrows(bundleGraph.getNode(b));
1167
1304
  invariant(bundle !== 'root');
1305
+ // @ts-expect-error TS2365
1168
1306
  return count + (bundle.bundleBehavior !== 'inline');
1169
1307
  }, 0);
1170
1308
 
@@ -1200,7 +1338,9 @@ export function createIdealGraph(
1200
1338
  numBundlesContributingToPRL > config.maxParallelRequests
1201
1339
  ) {
1202
1340
  let bundleTuple = sharedBundlesInGroup.pop();
1341
+ // @ts-expect-error TS18048
1203
1342
  let bundleToRemove = bundleTuple.bundle;
1343
+ // @ts-expect-error TS18048
1204
1344
  let bundleIdToRemove = bundleTuple.id;
1205
1345
  //TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
1206
1346
  // but total # bundles still exceeds limit due to non shared bundles
@@ -1256,28 +1396,50 @@ export function createIdealGraph(
1256
1396
  }
1257
1397
  }
1258
1398
 
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');
1399
+ function mergeBundles(bundleToKeepId: NodeId, bundleToRemoveId: NodeId) {
1400
+ let bundleToKeep = isNonRootBundle(
1401
+ bundleGraph.getNode(bundleToKeepId),
1402
+ `Bundle ${bundleToKeepId} not found`,
1403
+ );
1404
+ let bundleToRemove = isNonRootBundle(
1405
+ bundleGraph.getNode(bundleToRemoveId),
1406
+ `Bundle ${bundleToRemoveId} not found`,
1407
+ );
1408
+ modifiedSourceBundles.add(bundleToKeep);
1268
1409
  for (let asset of bundleToRemove.assets) {
1269
1410
  bundleToKeep.assets.add(asset);
1270
1411
  bundleToKeep.size += asset.stats.size;
1271
1412
 
1272
1413
  let newAssetReference = assetReference
1273
1414
  .get(asset)
1274
- .map(([dep, bundle]) =>
1415
+ .map(([dep, bundle]: [any, any]) =>
1275
1416
  bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
1276
1417
  );
1277
1418
 
1419
+ // @ts-expect-error TS2345
1278
1420
  assetReference.set(asset, newAssetReference);
1279
1421
  }
1280
1422
 
1423
+ // Merge any internalized assets
1424
+ if (getFeatureFlag('supportWebpackChunkName')) {
1425
+ if (bundleToKeep.internalizedAssets != null) {
1426
+ if (bundleToRemove.internalizedAssets != null) {
1427
+ bundleToKeep.internalizedAssets.intersect(
1428
+ bundleToRemove.internalizedAssets,
1429
+ );
1430
+ } else {
1431
+ bundleToKeep.internalizedAssets.clear();
1432
+ }
1433
+ }
1434
+ } else {
1435
+ invariant(
1436
+ bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
1437
+ 'All shared bundles should have internalized assets',
1438
+ );
1439
+ bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
1440
+ }
1441
+
1442
+ // Merge and clean up source bundles
1281
1443
  for (let sourceBundleId of bundleToRemove.sourceBundles) {
1282
1444
  if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
1283
1445
  continue;
@@ -1287,21 +1449,108 @@ export function createIdealGraph(
1287
1449
  bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
1288
1450
  }
1289
1451
 
1290
- // Merge any internalized assets
1291
- if (bundleToRemove.internalizedAssets) {
1292
- if (bundleToKeep.internalizedAssets) {
1293
- bundleToKeep.internalizedAssets.union(
1294
- bundleToRemove.internalizedAssets,
1452
+ if (getFeatureFlag('supportWebpackChunkName')) {
1453
+ bundleToKeep.sourceBundles.delete(bundleToRemoveId);
1454
+
1455
+ for (let bundle of bundleGraph.getNodeIdsConnectedFrom(
1456
+ bundleToRemoveId,
1457
+ )) {
1458
+ let bundleNode = nullthrows(bundleGraph.getNode(bundle));
1459
+ if (bundleNode === 'root') {
1460
+ continue;
1461
+ }
1462
+
1463
+ // If the bundle is a source bundle, add it to the bundle to keep
1464
+ if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
1465
+ bundleNode.sourceBundles.add(bundleToKeepId);
1466
+ bundleNode.sourceBundles.delete(bundleToRemoveId);
1467
+ bundleGraph.addEdge(bundleToKeepId, bundle);
1468
+ }
1469
+ }
1470
+
1471
+ // Merge bundle roots
1472
+ for (let bundleRoot of bundleToRemove.bundleRoots) {
1473
+ bundleToKeep.bundleRoots.add(bundleRoot);
1474
+ }
1475
+
1476
+ if (bundleToRemove.mainEntryAsset != null) {
1477
+ invariant(bundleToKeep.mainEntryAsset != null);
1478
+
1479
+ // Merge the bundles in bundle group
1480
+ let bundlesInRemoveBundleGroup =
1481
+ getBundlesForBundleGroup(bundleToRemoveId);
1482
+
1483
+ for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
1484
+ if (bundleIdInGroup === bundleToRemoveId) {
1485
+ continue;
1486
+ }
1487
+ bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
1488
+ }
1489
+
1490
+ // Remove old bundle group
1491
+ bundleGroupBundleIds.delete(bundleToRemoveId);
1492
+
1493
+ // Clean up bundle roots
1494
+ let bundleRootToRemoveNodeId = nullthrows(
1495
+ assetToBundleRootNodeId.get(
1496
+ nullthrows(bundleToRemove.mainEntryAsset),
1497
+ ),
1295
1498
  );
1296
- } else {
1297
- bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
1499
+ let bundleRootToKeepNodeId = nullthrows(
1500
+ assetToBundleRootNodeId.get(nullthrows(bundleToKeep.mainEntryAsset)),
1501
+ );
1502
+
1503
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(
1504
+ bundleRootToRemoveNodeId,
1505
+ )) {
1506
+ bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
1507
+ bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
1508
+ }
1509
+
1510
+ for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(
1511
+ bundleRootToRemoveNodeId,
1512
+ )) {
1513
+ bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
1514
+ bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
1515
+ }
1516
+
1517
+ bundleRoots.set(nullthrows(bundleToRemove.mainEntryAsset), [
1518
+ bundleToKeepId,
1519
+ bundleToKeepId,
1520
+ ]);
1521
+
1522
+ // Merge dependency bundle graph
1523
+ for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(
1524
+ dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)),
1525
+ ALL_EDGE_TYPES,
1526
+ )) {
1527
+ let dependencyNode = nullthrows(
1528
+ dependencyBundleGraph.getNode(dependencyNodeId),
1529
+ );
1530
+ invariant(dependencyNode.type === 'dependency');
1531
+
1532
+ // Add dependency to the bundle to keep
1533
+ dependencyBundleGraph.addEdge(
1534
+ dependencyNodeId,
1535
+ dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)),
1536
+ dependencyPriorityEdges[dependencyNode.value.priority],
1537
+ );
1538
+ // Remove dependency from the bundle to remove
1539
+ dependencyBundleGraph.removeEdge(
1540
+ dependencyNodeId,
1541
+ dependencyBundleGraph.getNodeIdByContentKey(
1542
+ String(bundleToRemoveId),
1543
+ ),
1544
+ dependencyPriorityEdges[dependencyNode.value.priority],
1545
+ );
1546
+ }
1298
1547
  }
1299
1548
  }
1300
1549
 
1301
1550
  bundleGraph.removeNode(bundleToRemoveId);
1302
1551
  }
1303
1552
 
1304
- function mergeOverlapBundles() {
1553
+ function mergeOverlapBundles(mergeConfig: MergeCandidates) {
1305
1554
  // Find all shared bundles
1306
1555
  let sharedBundles = new Set<NodeId>();
1307
1556
  bundleGraph.traverse((nodeId) => {
@@ -1331,22 +1580,44 @@ export function createIdealGraph(
1331
1580
  let clusters = findMergeCandidates(
1332
1581
  bundleGraph,
1333
1582
  Array.from(sharedBundles),
1334
- config.sharedBundleMergeThreshold,
1583
+ mergeConfig.map(
1584
+ (config): MergeGroup => ({
1585
+ ...config,
1586
+ sourceBundles: config.sourceBundles?.map((assetMatch: string) => {
1587
+ let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
1588
+
1589
+ if (sourceBundleNodeId == null) {
1590
+ throw new Error(
1591
+ `Source bundle ${assetMatch} not found in merge source bundle lookup`,
1592
+ );
1593
+ }
1594
+
1595
+ return sourceBundleNodeId;
1596
+ }),
1597
+ }),
1598
+ ),
1335
1599
  );
1336
1600
 
1601
+ let mergedBundles = new Set();
1602
+
1337
1603
  for (let cluster of clusters) {
1338
1604
  let [mergeTarget, ...rest] = cluster;
1339
1605
 
1340
1606
  for (let bundleIdToMerge of rest) {
1341
- mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
1607
+ mergeBundles(mergeTarget, bundleIdToMerge);
1342
1608
  }
1609
+
1610
+ mergedBundles.add(mergeTarget);
1611
+ }
1612
+
1613
+ if (getFeatureFlag('supportWebpackChunkName')) {
1614
+ return mergedBundles;
1343
1615
  }
1344
1616
  }
1345
1617
 
1346
- function getBigIntFromContentKey(contentKey) {
1618
+ function getBigIntFromContentKey(contentKey: string) {
1347
1619
  let b = Buffer.alloc(64);
1348
1620
  b.write(contentKey);
1349
- // $FlowFixMe Flow doesn't have BigInt types in this version
1350
1621
  return b.readBigInt64BE();
1351
1622
  }
1352
1623
  // Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
@@ -1373,8 +1644,8 @@ export function createIdealGraph(
1373
1644
  bundleRootGraph.removeNode(bundleRootId);
1374
1645
  }
1375
1646
  }
1376
- function getBundlesForBundleGroup(bundleGroupId) {
1377
- let bundlesInABundleGroup = [];
1647
+ function getBundlesForBundleGroup(bundleGroupId: NodeId) {
1648
+ let bundlesInABundleGroup: Array<NodeId> = [];
1378
1649
  bundleGraph.traverse((nodeId) => {
1379
1650
  bundlesInABundleGroup.push(nodeId);
1380
1651
  }, bundleGroupId);
@@ -1455,23 +1726,24 @@ export function createIdealGraph(
1455
1726
  };
1456
1727
  }
1457
1728
 
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 {
1729
+ function createBundle(opts: {
1730
+ asset?: Asset;
1731
+ bundleBehavior?: BundleBehavior | null | undefined;
1732
+ env?: Environment;
1733
+ manualSharedBundle?: string | null | undefined;
1734
+ needsStableName?: boolean;
1735
+ sourceBundles?: Set<NodeId>;
1736
+ target: Target;
1737
+ type?: string;
1738
+ uniqueKey?: string;
1739
+ }): Bundle {
1469
1740
  if (opts.asset == null) {
1470
1741
  return {
1471
1742
  assets: new Set(),
1472
1743
  bundleBehavior: opts.bundleBehavior,
1473
1744
  env: nullthrows(opts.env),
1474
1745
  mainEntryAsset: null,
1746
+ bundleRoots: new Set(),
1475
1747
  manualSharedBundle: opts.manualSharedBundle,
1476
1748
  needsStableName: Boolean(opts.needsStableName),
1477
1749
  size: 0,
@@ -1488,6 +1760,7 @@ function createBundle(opts: {|
1488
1760
  bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
1489
1761
  env: opts.env ?? asset.env,
1490
1762
  mainEntryAsset: asset,
1763
+ bundleRoots: new Set([asset]),
1491
1764
  manualSharedBundle: opts.manualSharedBundle,
1492
1765
  needsStableName: Boolean(opts.needsStableName),
1493
1766
  size: asset.stats.size,