@atlaspack/bundler-default 2.14.5-canary.2 → 2.14.5-canary.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +468 -0
- package/lib/DefaultBundler.js +6 -1
- package/lib/MonolithicBundler.js +11 -3
- package/lib/bundleMerge.js +160 -0
- package/lib/bundlerConfig.js +75 -8
- package/lib/decorateLegacyGraph.js +24 -3
- package/lib/idealGraph.js +469 -31
- package/lib/memoize.js +39 -0
- package/lib/stats.js +85 -0
- package/lib/types/DefaultBundler.d.ts +18 -0
- package/lib/types/MonolithicBundler.d.ts +2 -0
- package/lib/types/bundleMerge.d.ts +9 -0
- package/lib/types/bundlerConfig.d.ts +36 -0
- package/lib/types/decorateLegacyGraph.d.ts +3 -0
- package/lib/types/idealGraph.d.ts +40 -0
- package/lib/types/memoize.d.ts +2 -0
- package/lib/types/stats.d.ts +16 -0
- package/package.json +21 -12
- package/src/{DefaultBundler.js → DefaultBundler.ts} +21 -6
- package/src/{MonolithicBundler.js → MonolithicBundler.ts} +17 -5
- package/src/bundleMerge.ts +250 -0
- package/src/{bundlerConfig.js → bundlerConfig.ts} +123 -43
- package/src/{decorateLegacyGraph.js → decorateLegacyGraph.ts} +26 -7
- package/src/{idealGraph.js → idealGraph.ts} +742 -81
- package/src/memoize.ts +32 -0
- package/src/stats.ts +97 -0
- package/tsconfig.json +4 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// @flow strict-local
|
|
2
|
-
|
|
3
1
|
import path from 'path';
|
|
2
|
+
import sortedArray from 'sorted-array-functions';
|
|
4
3
|
|
|
5
4
|
import {getFeatureFlag} from '@atlaspack/feature-flags';
|
|
6
5
|
import {
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
BitSet,
|
|
9
8
|
ContentGraph,
|
|
10
9
|
Graph,
|
|
11
|
-
|
|
10
|
+
NodeId,
|
|
12
11
|
} from '@atlaspack/graph';
|
|
13
12
|
import type {
|
|
14
13
|
Asset,
|
|
@@ -23,36 +22,43 @@ import {DefaultMap, globToRegex} from '@atlaspack/utils';
|
|
|
23
22
|
import invariant from 'assert';
|
|
24
23
|
import nullthrows from 'nullthrows';
|
|
25
24
|
|
|
26
|
-
import
|
|
25
|
+
import {findMergeCandidates, MergeGroup} from './bundleMerge';
|
|
26
|
+
import type {
|
|
27
|
+
ResolvedBundlerConfig,
|
|
28
|
+
SharedBundleMergeCandidates,
|
|
29
|
+
AsyncBundleMerge,
|
|
30
|
+
} from './bundlerConfig';
|
|
31
|
+
import {Stats} from './stats';
|
|
27
32
|
|
|
28
33
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
29
34
|
type BundleRoot = Asset;
|
|
30
35
|
|
|
31
|
-
export type Bundle = {
|
|
32
|
-
uniqueKey:
|
|
33
|
-
assets: Set<Asset
|
|
34
|
-
internalizedAssets?: BitSet
|
|
35
|
-
bundleBehavior?:
|
|
36
|
-
needsStableName: boolean
|
|
37
|
-
mainEntryAsset:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
|
36
|
+
export type Bundle = {
|
|
37
|
+
uniqueKey: string | null | undefined;
|
|
38
|
+
assets: Set<Asset>;
|
|
39
|
+
internalizedAssets?: BitSet;
|
|
40
|
+
bundleBehavior?: BundleBehavior | null | undefined;
|
|
41
|
+
needsStableName: boolean;
|
|
42
|
+
mainEntryAsset: Asset | null | undefined;
|
|
43
|
+
bundleRoots: Set<Asset>;
|
|
44
|
+
size: number;
|
|
45
|
+
sourceBundles: Set<NodeId>;
|
|
46
|
+
target: Target;
|
|
47
|
+
env: Environment;
|
|
48
|
+
type: string;
|
|
49
|
+
manualSharedBundle: string | null | undefined; // for naming purposes;
|
|
50
|
+
};
|
|
45
51
|
|
|
46
52
|
export type DependencyBundleGraph = ContentGraph<
|
|
47
|
-
| {
|
|
48
|
-
value: Bundle
|
|
49
|
-
type: 'bundle'
|
|
50
|
-
|
|
51
|
-
| {
|
|
52
|
-
value: Dependency
|
|
53
|
-
type: 'dependency'
|
|
54
|
-
|
|
55
|
-
number
|
|
53
|
+
| {
|
|
54
|
+
value: Bundle;
|
|
55
|
+
type: 'bundle';
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
value: Dependency;
|
|
59
|
+
type: 'dependency';
|
|
60
|
+
},
|
|
61
|
+
number
|
|
56
62
|
>;
|
|
57
63
|
|
|
58
64
|
const dependencyPriorityEdges = {
|
|
@@ -60,29 +66,39 @@ const dependencyPriorityEdges = {
|
|
|
60
66
|
parallel: 2,
|
|
61
67
|
lazy: 3,
|
|
62
68
|
conditional: 4,
|
|
63
|
-
};
|
|
69
|
+
} as const;
|
|
64
70
|
|
|
65
71
|
export const idealBundleGraphEdges = Object.freeze({
|
|
66
72
|
default: 1,
|
|
67
73
|
conditional: 2,
|
|
68
74
|
});
|
|
69
75
|
|
|
70
|
-
type IdealBundleGraph = Graph<
|
|
76
|
+
export type IdealBundleGraph = Graph<
|
|
71
77
|
Bundle | 'root',
|
|
72
|
-
|
|
78
|
+
(typeof idealBundleGraphEdges)[keyof typeof idealBundleGraphEdges]
|
|
73
79
|
>;
|
|
74
80
|
|
|
75
81
|
// IdealGraph is the structure we will pass to decorate,
|
|
76
82
|
// which mutates the assetGraph into the bundleGraph we would
|
|
77
83
|
// expect from default bundler
|
|
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
|
-
|
|
84
|
+
export type IdealGraph = {
|
|
85
|
+
assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>;
|
|
86
|
+
assets: Array<Asset>;
|
|
87
|
+
bundleGraph: IdealBundleGraph;
|
|
88
|
+
bundleGroupBundleIds: Set<NodeId>;
|
|
89
|
+
dependencyBundleGraph: DependencyBundleGraph;
|
|
90
|
+
manualAssetToBundle: Map<Asset, NodeId>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function isNonRootBundle(
|
|
94
|
+
bundle?: Bundle | 'root' | null,
|
|
95
|
+
message?: string,
|
|
96
|
+
): Bundle {
|
|
97
|
+
let existingBundle = nullthrows(bundle, message);
|
|
98
|
+
invariant(existingBundle !== 'root', "Bundle cannot be 'root'");
|
|
99
|
+
|
|
100
|
+
return existingBundle;
|
|
101
|
+
}
|
|
86
102
|
|
|
87
103
|
export function createIdealGraph(
|
|
88
104
|
assetGraph: MutableBundleGraph,
|
|
@@ -96,8 +112,9 @@ export function createIdealGraph(
|
|
|
96
112
|
let dependencyBundleGraph: DependencyBundleGraph = new ContentGraph();
|
|
97
113
|
let assetReference: DefaultMap<
|
|
98
114
|
Asset,
|
|
99
|
-
Array<[Dependency, Bundle]
|
|
115
|
+
Array<[Dependency, Bundle]>
|
|
100
116
|
> = new DefaultMap(() => []);
|
|
117
|
+
let stats = new Stats(config.projectRoot);
|
|
101
118
|
|
|
102
119
|
// A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their
|
|
103
120
|
// referencing Bundle. There are no actual BundleGroup nodes, just bundles that take on that role.
|
|
@@ -110,8 +127,9 @@ export function createIdealGraph(
|
|
|
110
127
|
};
|
|
111
128
|
// Graph that models bundleRoots, with parallel & async deps only to inform reachability
|
|
112
129
|
let bundleRootGraph: Graph<
|
|
113
|
-
|
|
114
|
-
|
|
130
|
+
// asset index
|
|
131
|
+
number,
|
|
132
|
+
(typeof bundleRootEdgeTypes)[keyof typeof bundleRootEdgeTypes]
|
|
115
133
|
> = new Graph();
|
|
116
134
|
let assetToBundleRootNodeId = new Map<BundleRoot, number>();
|
|
117
135
|
|
|
@@ -145,7 +163,7 @@ export function createIdealGraph(
|
|
|
145
163
|
bundleGroupBundleIds.add(nodeId);
|
|
146
164
|
}
|
|
147
165
|
|
|
148
|
-
let assets = [];
|
|
166
|
+
let assets: Array<Asset> = [];
|
|
149
167
|
let assetToIndex = new Map<Asset, number>();
|
|
150
168
|
|
|
151
169
|
function makeManualAssetToConfigLookup() {
|
|
@@ -160,6 +178,7 @@ export function createIdealGraph(
|
|
|
160
178
|
|
|
161
179
|
for (let c of config.manualSharedBundles) {
|
|
162
180
|
if (c.root != null) {
|
|
181
|
+
// @ts-expect-error TS2345
|
|
163
182
|
parentsToConfig.get(path.join(config.projectRoot, c.root)).push(c);
|
|
164
183
|
}
|
|
165
184
|
}
|
|
@@ -203,17 +222,17 @@ export function createIdealGraph(
|
|
|
203
222
|
config.projectRoot,
|
|
204
223
|
node.value.filePath,
|
|
205
224
|
);
|
|
206
|
-
if (!assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
225
|
|
|
210
226
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
211
227
|
// and need to be assigned to all matching bundles
|
|
212
228
|
if (node.value.meta.isConstantModule === true) {
|
|
229
|
+
// @ts-expect-error TS2345
|
|
213
230
|
constantModuleToMSB.get(node.value).push(c);
|
|
214
231
|
}
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
|
|
233
|
+
if (assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
234
|
+
manualAssetToConfig.set(node.value, c);
|
|
235
|
+
}
|
|
217
236
|
}
|
|
218
237
|
|
|
219
238
|
if (
|
|
@@ -241,9 +260,19 @@ export function createIdealGraph(
|
|
|
241
260
|
makeManualAssetToConfigLookup();
|
|
242
261
|
let manualBundleToInternalizedAsset: DefaultMap<
|
|
243
262
|
NodeId,
|
|
244
|
-
Array<Asset
|
|
263
|
+
Array<Asset>
|
|
245
264
|
> = new DefaultMap(() => []);
|
|
246
265
|
|
|
266
|
+
let mergeSourceBundleLookup = new Map<string, NodeId>();
|
|
267
|
+
let mergeSourceBundleAssets = new Set(
|
|
268
|
+
config.sharedBundleMerge?.flatMap(
|
|
269
|
+
(c) =>
|
|
270
|
+
c.sourceBundles?.map((assetMatch: string) =>
|
|
271
|
+
path.join(config.projectRoot, assetMatch),
|
|
272
|
+
) ?? [],
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
|
|
247
276
|
/**
|
|
248
277
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
249
278
|
* for asset type changes, parallel, inline, and async or lazy dependencies,
|
|
@@ -251,7 +280,27 @@ export function createIdealGraph(
|
|
|
251
280
|
*/
|
|
252
281
|
assetGraph.traverse(
|
|
253
282
|
{
|
|
254
|
-
enter(
|
|
283
|
+
enter(
|
|
284
|
+
node: // @ts-expect-error TS2304
|
|
285
|
+
| BundleGraphTraversable
|
|
286
|
+
| {
|
|
287
|
+
readonly type: 'dependency';
|
|
288
|
+
value: Dependency;
|
|
289
|
+
},
|
|
290
|
+
context:
|
|
291
|
+
| {
|
|
292
|
+
readonly type: 'asset';
|
|
293
|
+
value: Asset;
|
|
294
|
+
}
|
|
295
|
+
| null
|
|
296
|
+
| undefined
|
|
297
|
+
| {
|
|
298
|
+
readonly type: 'dependency';
|
|
299
|
+
value: Dependency;
|
|
300
|
+
},
|
|
301
|
+
// @ts-expect-error TS2304
|
|
302
|
+
actions: TraversalActions,
|
|
303
|
+
) {
|
|
255
304
|
if (node.type === 'asset') {
|
|
256
305
|
if (
|
|
257
306
|
context?.type === 'dependency' &&
|
|
@@ -311,7 +360,8 @@ export function createIdealGraph(
|
|
|
311
360
|
dependency.priority === 'lazy' ||
|
|
312
361
|
(getFeatureFlag('conditionalBundlingApi') &&
|
|
313
362
|
node.value.priority === 'conditional') ||
|
|
314
|
-
childAsset.bundleBehavior === 'isolated' // An isolated Dependency, or Bundle must contain all assets it needs to load.
|
|
363
|
+
childAsset.bundleBehavior === 'isolated' || // An isolated Dependency, or Bundle must contain all assets it needs to load.
|
|
364
|
+
childAsset.bundleBehavior === 'inlineIsolated'
|
|
315
365
|
) {
|
|
316
366
|
if (bundleId == null) {
|
|
317
367
|
let firstBundleGroup = nullthrows(
|
|
@@ -324,7 +374,9 @@ export function createIdealGraph(
|
|
|
324
374
|
dependency.bundleBehavior ?? childAsset.bundleBehavior,
|
|
325
375
|
needsStableName:
|
|
326
376
|
dependency.bundleBehavior === 'inline' ||
|
|
327
|
-
childAsset.bundleBehavior === 'inline'
|
|
377
|
+
childAsset.bundleBehavior === 'inline' ||
|
|
378
|
+
dependency.bundleBehavior === 'inlineIsolated' ||
|
|
379
|
+
childAsset.bundleBehavior === 'inlineIsolated'
|
|
328
380
|
? false
|
|
329
381
|
: dependency.isEntry || dependency.needsStableName,
|
|
330
382
|
target: firstBundleGroup.target,
|
|
@@ -334,6 +386,14 @@ export function createIdealGraph(
|
|
|
334
386
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
335
387
|
bundleGroupBundleIds.add(bundleId);
|
|
336
388
|
bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
|
|
389
|
+
// If this asset is relevant for merging then track it's source
|
|
390
|
+
// bundle id for later
|
|
391
|
+
if (mergeSourceBundleAssets.has(childAsset.filePath)) {
|
|
392
|
+
mergeSourceBundleLookup.set(
|
|
393
|
+
path.relative(config.projectRoot, childAsset.filePath),
|
|
394
|
+
bundleId,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
337
397
|
if (manualSharedObject) {
|
|
338
398
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
339
399
|
// since MSBs should not have main entry assets
|
|
@@ -348,7 +408,8 @@ export function createIdealGraph(
|
|
|
348
408
|
if (
|
|
349
409
|
// If this dependency requests isolated, but the bundle is not,
|
|
350
410
|
// make the bundle isolated for all uses.
|
|
351
|
-
dependency.bundleBehavior === 'isolated'
|
|
411
|
+
(dependency.bundleBehavior === 'isolated' ||
|
|
412
|
+
dependency.bundleBehavior === 'inlineIsolated') &&
|
|
352
413
|
bundle.bundleBehavior == null
|
|
353
414
|
) {
|
|
354
415
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
@@ -370,6 +431,7 @@ export function createIdealGraph(
|
|
|
370
431
|
type: 'bundle',
|
|
371
432
|
},
|
|
372
433
|
),
|
|
434
|
+
// @ts-expect-error TS7053
|
|
373
435
|
dependencyPriorityEdges[dependency.priority],
|
|
374
436
|
);
|
|
375
437
|
|
|
@@ -431,6 +493,7 @@ export function createIdealGraph(
|
|
|
431
493
|
needsStableName:
|
|
432
494
|
childAsset.bundleBehavior === 'inline' ||
|
|
433
495
|
dependency.bundleBehavior === 'inline' ||
|
|
496
|
+
dependency.bundleBehavior === 'inlineIsolated' ||
|
|
434
497
|
(dependency.priority === 'parallel' &&
|
|
435
498
|
!dependency.needsStableName)
|
|
436
499
|
? false
|
|
@@ -444,7 +507,8 @@ export function createIdealGraph(
|
|
|
444
507
|
if (
|
|
445
508
|
// If this dependency requests isolated, but the bundle is not,
|
|
446
509
|
// make the bundle isolated for all uses.
|
|
447
|
-
dependency.bundleBehavior === 'isolated'
|
|
510
|
+
(dependency.bundleBehavior === 'isolated' ||
|
|
511
|
+
dependency.bundleBehavior === 'inlineIsolated') &&
|
|
448
512
|
bundle.bundleBehavior == null
|
|
449
513
|
) {
|
|
450
514
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
@@ -481,6 +545,7 @@ export function createIdealGraph(
|
|
|
481
545
|
|
|
482
546
|
assetReference.get(childAsset).push([dependency, bundle]);
|
|
483
547
|
} else {
|
|
548
|
+
// @ts-expect-error TS2322
|
|
484
549
|
bundleId = null;
|
|
485
550
|
}
|
|
486
551
|
if (manualSharedObject && bundleId != null) {
|
|
@@ -488,6 +553,7 @@ export function createIdealGraph(
|
|
|
488
553
|
// add the asset if it doesn't already have it and set key
|
|
489
554
|
|
|
490
555
|
invariant(
|
|
556
|
+
// @ts-expect-error TS2367
|
|
491
557
|
bundle !== 'root' && bundle != null && bundleId != null,
|
|
492
558
|
);
|
|
493
559
|
|
|
@@ -514,7 +580,8 @@ export function createIdealGraph(
|
|
|
514
580
|
}
|
|
515
581
|
return node;
|
|
516
582
|
},
|
|
517
|
-
|
|
583
|
+
// @ts-expect-error TS2322
|
|
584
|
+
exit(node: BundleGraphTraversable) {
|
|
518
585
|
if (stack[stack.length - 1]?.[0] === node.value) {
|
|
519
586
|
stack.pop();
|
|
520
587
|
}
|
|
@@ -563,20 +630,20 @@ export function createIdealGraph(
|
|
|
563
630
|
|
|
564
631
|
// reachableRoots is an array of bit sets for each asset. Each bit set
|
|
565
632
|
// indicates which bundle roots are reachable from that asset synchronously.
|
|
566
|
-
let reachableRoots = [];
|
|
633
|
+
let reachableRoots: Array<BitSet> = [];
|
|
567
634
|
for (let i = 0; i < assets.length; i++) {
|
|
568
635
|
reachableRoots.push(new BitSet(bundleRootGraph.nodes.length));
|
|
569
636
|
}
|
|
570
637
|
|
|
571
638
|
// reachableAssets is the inverse mapping of reachableRoots. For each bundle root,
|
|
572
639
|
// it contains a bit set that indicates which assets are reachable from it.
|
|
573
|
-
let reachableAssets = [];
|
|
640
|
+
let reachableAssets: Array<BitSet> = [];
|
|
574
641
|
|
|
575
642
|
// ancestorAssets maps bundle roots to the set of all assets available to it at runtime,
|
|
576
643
|
// including in earlier parallel bundles. These are intersected through all paths to
|
|
577
644
|
// the bundle to ensure that the available assets are always present no matter in which
|
|
578
645
|
// order the bundles are loaded.
|
|
579
|
-
let ancestorAssets = [];
|
|
646
|
+
let ancestorAssets: Array<null | BitSet> = [];
|
|
580
647
|
|
|
581
648
|
let inlineConstantDeps = new DefaultMap(() => new Set());
|
|
582
649
|
|
|
@@ -687,7 +754,10 @@ export function createIdealGraph(
|
|
|
687
754
|
// not true that a bundle's available assets = all assets of all the bundleGroups
|
|
688
755
|
// it belongs to. It's the intersection of those sets.
|
|
689
756
|
let available;
|
|
690
|
-
if (
|
|
757
|
+
if (
|
|
758
|
+
bundleRoot.bundleBehavior === 'isolated' ||
|
|
759
|
+
bundleRoot.bundleBehavior === 'inlineIsolated'
|
|
760
|
+
) {
|
|
691
761
|
available = new BitSet(assets.length);
|
|
692
762
|
} else {
|
|
693
763
|
available = nullthrows(ancestorAssets[nodeId]).clone();
|
|
@@ -771,7 +841,8 @@ export function createIdealGraph(
|
|
|
771
841
|
|
|
772
842
|
let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, ALL_EDGE_TYPES);
|
|
773
843
|
let canDelete =
|
|
774
|
-
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated'
|
|
844
|
+
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated' &&
|
|
845
|
+
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'inlineIsolated';
|
|
775
846
|
if (parentRoots.length === 0) continue;
|
|
776
847
|
for (let parentId of parentRoots) {
|
|
777
848
|
if (parentId === rootNodeId) {
|
|
@@ -805,8 +876,11 @@ export function createIdealGraph(
|
|
|
805
876
|
|
|
806
877
|
function assignInlineConstants(parentAsset: Asset, bundle: Bundle) {
|
|
807
878
|
for (let inlineConstant of inlineConstantDeps.get(parentAsset)) {
|
|
879
|
+
// @ts-expect-error TS2345
|
|
808
880
|
if (!bundle.assets.has(inlineConstant)) {
|
|
881
|
+
// @ts-expect-error TS2345
|
|
809
882
|
bundle.assets.add(inlineConstant);
|
|
883
|
+
// @ts-expect-error TS18046
|
|
810
884
|
bundle.size += inlineConstant.stats.size;
|
|
811
885
|
}
|
|
812
886
|
}
|
|
@@ -848,7 +922,8 @@ export function createIdealGraph(
|
|
|
848
922
|
!a.isBundleSplittable ||
|
|
849
923
|
(bundleRoots.get(a) &&
|
|
850
924
|
(getBundleFromBundleRoot(a).needsStableName ||
|
|
851
|
-
getBundleFromBundleRoot(a).bundleBehavior === 'isolated'
|
|
925
|
+
getBundleFromBundleRoot(a).bundleBehavior === 'isolated' ||
|
|
926
|
+
getBundleFromBundleRoot(a).bundleBehavior === 'inlineIsolated'))
|
|
852
927
|
) {
|
|
853
928
|
// Add asset to non-splittable bundles.
|
|
854
929
|
addAssetToBundleRoot(asset, a);
|
|
@@ -866,7 +941,7 @@ export function createIdealGraph(
|
|
|
866
941
|
let bundle;
|
|
867
942
|
let bundleId;
|
|
868
943
|
let manualSharedBundleKey = manualSharedObject.name + ',' + asset.type;
|
|
869
|
-
let sourceBundles = [];
|
|
944
|
+
let sourceBundles: Array<NodeId> = [];
|
|
870
945
|
reachable.forEach((id) => {
|
|
871
946
|
sourceBundles.push(nullthrows(bundleRoots.get(assets[id]))[0]);
|
|
872
947
|
});
|
|
@@ -981,7 +1056,7 @@ export function createIdealGraph(
|
|
|
981
1056
|
});
|
|
982
1057
|
}
|
|
983
1058
|
|
|
984
|
-
let reachableArray = [];
|
|
1059
|
+
let reachableArray: Array<Asset> = [];
|
|
985
1060
|
reachable.forEach((id) => {
|
|
986
1061
|
reachableArray.push(assets[id]);
|
|
987
1062
|
});
|
|
@@ -1056,6 +1131,8 @@ export function createIdealGraph(
|
|
|
1056
1131
|
}
|
|
1057
1132
|
|
|
1058
1133
|
let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
|
|
1134
|
+
let modifiedSourceBundles = new Set<Bundle>();
|
|
1135
|
+
|
|
1059
1136
|
// Step split manual shared bundles for those that have the "split" property set
|
|
1060
1137
|
let remainderMap = new DefaultMap(() => []);
|
|
1061
1138
|
for (let id of manualSharedMap.values()) {
|
|
@@ -1074,9 +1151,9 @@ export function createIdealGraph(
|
|
|
1074
1151
|
if (modNum != null) {
|
|
1075
1152
|
for (let a of [...manualBundle.assets]) {
|
|
1076
1153
|
let numRep = getBigIntFromContentKey(a.id);
|
|
1077
|
-
// $FlowFixMe Flow doesn't know about BigInt
|
|
1078
1154
|
let r = Number(numRep % BigInt(modNum));
|
|
1079
1155
|
|
|
1156
|
+
// @ts-expect-error TS2345
|
|
1080
1157
|
remainderMap.get(r).push(a);
|
|
1081
1158
|
}
|
|
1082
1159
|
|
|
@@ -1099,8 +1176,10 @@ export function createIdealGraph(
|
|
|
1099
1176
|
}
|
|
1100
1177
|
for (let sp of remainderMap.get(i)) {
|
|
1101
1178
|
bundle.assets.add(sp);
|
|
1179
|
+
// @ts-expect-error TS2339
|
|
1102
1180
|
bundle.size += sp.stats.size;
|
|
1103
1181
|
manualBundle.assets.delete(sp);
|
|
1182
|
+
// @ts-expect-error TS2339
|
|
1104
1183
|
manualBundle.size -= sp.stats.size;
|
|
1105
1184
|
}
|
|
1106
1185
|
}
|
|
@@ -1113,6 +1192,7 @@ export function createIdealGraph(
|
|
|
1113
1192
|
// match multiple MSB's
|
|
1114
1193
|
for (let [asset, msbs] of constantModuleToMSB.entries()) {
|
|
1115
1194
|
for (let manualSharedObject of msbs) {
|
|
1195
|
+
// @ts-expect-error TS2339
|
|
1116
1196
|
let bundleId = manualSharedMap.get(manualSharedObject.name + ',js');
|
|
1117
1197
|
if (bundleId == null) continue;
|
|
1118
1198
|
let bundle = nullthrows(bundleGraph.getNode(bundleId));
|
|
@@ -1121,13 +1201,99 @@ export function createIdealGraph(
|
|
|
1121
1201
|
'We tried to use the root incorrectly',
|
|
1122
1202
|
);
|
|
1123
1203
|
|
|
1204
|
+
// @ts-expect-error TS2345
|
|
1124
1205
|
if (!bundle.assets.has(asset)) {
|
|
1206
|
+
// @ts-expect-error TS2345
|
|
1125
1207
|
bundle.assets.add(asset);
|
|
1208
|
+
// @ts-expect-error TS18046
|
|
1126
1209
|
bundle.size += asset.stats.size;
|
|
1127
1210
|
}
|
|
1128
1211
|
}
|
|
1129
1212
|
}
|
|
1130
1213
|
|
|
1214
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1215
|
+
// Merge webpack chunk name bundles
|
|
1216
|
+
let chunkNameBundles = new DefaultMap<string, Set<NodeId>>(() => new Set());
|
|
1217
|
+
for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
|
|
1218
|
+
// meta.chunkName is set by the Rust transformer, so we just need to find
|
|
1219
|
+
// bundles that have a chunkName set.
|
|
1220
|
+
if (
|
|
1221
|
+
!node ||
|
|
1222
|
+
node.type !== 'dependency' ||
|
|
1223
|
+
typeof node.value.meta.chunkName !== 'string'
|
|
1224
|
+
) {
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(
|
|
1229
|
+
nodeId,
|
|
1230
|
+
dependencyPriorityEdges[node.value.priority],
|
|
1231
|
+
);
|
|
1232
|
+
|
|
1233
|
+
if (connectedBundles.length === 0) {
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
invariant(
|
|
1238
|
+
connectedBundles.length === 1,
|
|
1239
|
+
'Expected webpackChunkName dependency to be connected to no more than one bundle',
|
|
1240
|
+
);
|
|
1241
|
+
|
|
1242
|
+
let bundleId = connectedBundles[0];
|
|
1243
|
+
let bundleNode = dependencyBundleGraph.getNode(bundleId);
|
|
1244
|
+
invariant(bundleNode != null && bundleNode.type === 'bundle');
|
|
1245
|
+
|
|
1246
|
+
// If a bundle does not have a main entry asset, it's somehow just a
|
|
1247
|
+
// shared bundle, and will be merged/deleted by other means.
|
|
1248
|
+
if (bundleNode.value.mainEntryAsset == null) {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
let bundleNodeId = null;
|
|
1253
|
+
let mainEntryAssetId = bundleNode.value.mainEntryAsset?.id;
|
|
1254
|
+
|
|
1255
|
+
if (mainEntryAssetId != null) {
|
|
1256
|
+
bundleNodeId = bundles.get(mainEntryAssetId);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (bundleNodeId == null) {
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
chunkNameBundles
|
|
1264
|
+
.get(node.value.meta.chunkName)
|
|
1265
|
+
// DependencyBundleGraph uses content keys as node ids, so we can use that
|
|
1266
|
+
// to get the bundle id.
|
|
1267
|
+
.add(bundleNodeId);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
|
|
1271
|
+
// The `[request]` placeholder is not yet supported
|
|
1272
|
+
if (
|
|
1273
|
+
bundleIds.size <= 1 ||
|
|
1274
|
+
(typeof chunkName === 'string' && chunkName.includes('[request]'))
|
|
1275
|
+
) {
|
|
1276
|
+
continue; // Nothing to merge
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Merge all bundles with the same chunk name into the first one.
|
|
1280
|
+
let [firstBundleId, ...rest] = Array.from(bundleIds);
|
|
1281
|
+
for (let bundleId of rest) {
|
|
1282
|
+
mergeBundles(firstBundleId, bundleId, 'webpack-chunk-name');
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Step merge async bundles that meet the configured params
|
|
1288
|
+
if (config.asyncBundleMerge) {
|
|
1289
|
+
mergeAsyncBundles(config.asyncBundleMerge);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Step merge shared bundles that meet the configured params
|
|
1293
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
1294
|
+
mergeSharedBundles(config.sharedBundleMerge);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1131
1297
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
1132
1298
|
// their source bundles, and remove the bundle.
|
|
1133
1299
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
@@ -1143,9 +1309,8 @@ export function createIdealGraph(
|
|
|
1143
1309
|
}
|
|
1144
1310
|
}
|
|
1145
1311
|
|
|
1146
|
-
let modifiedSourceBundles = new Set();
|
|
1147
|
-
|
|
1148
1312
|
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1313
|
+
|
|
1149
1314
|
if (config.disableSharedBundles === false) {
|
|
1150
1315
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
1151
1316
|
// Find shared bundles in this bundle group.
|
|
@@ -1158,7 +1323,13 @@ export function createIdealGraph(
|
|
|
1158
1323
|
let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
|
|
1159
1324
|
let bundle = nullthrows(bundleGraph.getNode(b));
|
|
1160
1325
|
invariant(bundle !== 'root');
|
|
1161
|
-
return
|
|
1326
|
+
return (
|
|
1327
|
+
count +
|
|
1328
|
+
Number(
|
|
1329
|
+
bundle.bundleBehavior !== 'inline' &&
|
|
1330
|
+
bundle.bundleBehavior !== 'inlineIsolated',
|
|
1331
|
+
)
|
|
1332
|
+
);
|
|
1162
1333
|
}, 0);
|
|
1163
1334
|
|
|
1164
1335
|
if (numBundlesContributingToPRL > config.maxParallelRequests) {
|
|
@@ -1193,7 +1364,9 @@ export function createIdealGraph(
|
|
|
1193
1364
|
numBundlesContributingToPRL > config.maxParallelRequests
|
|
1194
1365
|
) {
|
|
1195
1366
|
let bundleTuple = sharedBundlesInGroup.pop();
|
|
1367
|
+
// @ts-expect-error TS18048
|
|
1196
1368
|
let bundleToRemove = bundleTuple.bundle;
|
|
1369
|
+
// @ts-expect-error TS18048
|
|
1197
1370
|
let bundleIdToRemove = bundleTuple.id;
|
|
1198
1371
|
//TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
|
|
1199
1372
|
// but total # bundles still exceeds limit due to non shared bundles
|
|
@@ -1249,10 +1422,418 @@ export function createIdealGraph(
|
|
|
1249
1422
|
}
|
|
1250
1423
|
}
|
|
1251
1424
|
|
|
1252
|
-
function
|
|
1425
|
+
function mergeBundles(
|
|
1426
|
+
bundleToKeepId: NodeId,
|
|
1427
|
+
bundleToRemoveId: NodeId,
|
|
1428
|
+
reason: string,
|
|
1429
|
+
) {
|
|
1430
|
+
stats.trackMerge(bundleToKeepId, bundleToRemoveId, reason);
|
|
1431
|
+
let bundleToKeep = isNonRootBundle(
|
|
1432
|
+
bundleGraph.getNode(bundleToKeepId),
|
|
1433
|
+
`Bundle ${bundleToKeepId} not found`,
|
|
1434
|
+
);
|
|
1435
|
+
let bundleToRemove = isNonRootBundle(
|
|
1436
|
+
bundleGraph.getNode(bundleToRemoveId),
|
|
1437
|
+
`Bundle ${bundleToRemoveId} not found`,
|
|
1438
|
+
);
|
|
1439
|
+
modifiedSourceBundles.add(bundleToKeep);
|
|
1440
|
+
for (let asset of bundleToRemove.assets) {
|
|
1441
|
+
bundleToKeep.assets.add(asset);
|
|
1442
|
+
bundleToKeep.size += asset.stats.size;
|
|
1443
|
+
|
|
1444
|
+
let newAssetReference = assetReference
|
|
1445
|
+
.get(asset)
|
|
1446
|
+
.map(([dep, bundle]: [any, any]) =>
|
|
1447
|
+
bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
|
|
1448
|
+
);
|
|
1449
|
+
|
|
1450
|
+
// @ts-expect-error TS2345
|
|
1451
|
+
assetReference.set(asset, newAssetReference);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// Merge any internalized assets
|
|
1455
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1456
|
+
if (bundleToKeep.internalizedAssets != null) {
|
|
1457
|
+
if (bundleToRemove.internalizedAssets != null) {
|
|
1458
|
+
bundleToKeep.internalizedAssets.intersect(
|
|
1459
|
+
bundleToRemove.internalizedAssets,
|
|
1460
|
+
);
|
|
1461
|
+
} else {
|
|
1462
|
+
bundleToKeep.internalizedAssets.clear();
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
} else {
|
|
1466
|
+
invariant(
|
|
1467
|
+
bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
|
|
1468
|
+
'All shared bundles should have internalized assets',
|
|
1469
|
+
);
|
|
1470
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Merge and clean up source bundles
|
|
1474
|
+
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1475
|
+
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if (sourceBundleId !== bundleToKeepId) {
|
|
1480
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
1481
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1486
|
+
bundleToKeep.sourceBundles.delete(bundleToRemoveId);
|
|
1487
|
+
|
|
1488
|
+
for (let bundle of bundleGraph.getNodeIdsConnectedFrom(
|
|
1489
|
+
bundleToRemoveId,
|
|
1490
|
+
)) {
|
|
1491
|
+
let bundleNode = nullthrows(bundleGraph.getNode(bundle));
|
|
1492
|
+
if (bundleNode === 'root') {
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// If the bundle is a source bundle, add it to the bundle to keep
|
|
1497
|
+
if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
|
|
1498
|
+
bundleNode.sourceBundles.delete(bundleToRemoveId);
|
|
1499
|
+
if (bundle !== bundleToKeepId) {
|
|
1500
|
+
bundleNode.sourceBundles.add(bundleToKeepId);
|
|
1501
|
+
bundleGraph.addEdge(bundleToKeepId, bundle);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// Merge bundle roots
|
|
1507
|
+
for (let bundleRoot of bundleToRemove.bundleRoots) {
|
|
1508
|
+
bundleToKeep.bundleRoots.add(bundleRoot);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
if (bundleToRemove.mainEntryAsset != null) {
|
|
1512
|
+
invariant(bundleToKeep.mainEntryAsset != null);
|
|
1513
|
+
|
|
1514
|
+
// Merge the bundles in bundle group
|
|
1515
|
+
let bundlesInRemoveBundleGroup =
|
|
1516
|
+
getBundlesForBundleGroup(bundleToRemoveId);
|
|
1517
|
+
|
|
1518
|
+
let removedBundleSharedBundles = new Set<NodeId>();
|
|
1519
|
+
for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
|
|
1520
|
+
if (bundleIdInGroup === bundleToRemoveId) {
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
|
|
1524
|
+
removedBundleSharedBundles.add(bundleIdInGroup);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
if (getFeatureFlag('removeRedundantSharedBundles')) {
|
|
1528
|
+
// Merge any shared bundles that now have the same source bundles due to
|
|
1529
|
+
// the current bundle merge
|
|
1530
|
+
let sharedBundles = new DefaultMap<string, Array<NodeId>>(() => []);
|
|
1531
|
+
for (let bundleId of removedBundleSharedBundles) {
|
|
1532
|
+
let bundleNode = nullthrows(bundleGraph.getNode(bundleId));
|
|
1533
|
+
invariant(bundleNode !== 'root');
|
|
1534
|
+
if (
|
|
1535
|
+
bundleNode.mainEntryAsset != null ||
|
|
1536
|
+
bundleNode.manualSharedBundle != null
|
|
1537
|
+
) {
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
let key =
|
|
1542
|
+
Array.from(bundleNode.sourceBundles)
|
|
1543
|
+
.filter((sourceBundle) => sourceBundle !== bundleToRemoveId)
|
|
1544
|
+
.sort()
|
|
1545
|
+
.join(',') +
|
|
1546
|
+
'.' +
|
|
1547
|
+
bundleNode.type;
|
|
1548
|
+
|
|
1549
|
+
sharedBundles.get(key).push(bundleId);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
for (let sharedBundlesToMerge of sharedBundles.values()) {
|
|
1553
|
+
if (sharedBundlesToMerge.length > 1) {
|
|
1554
|
+
let [firstBundleId, ...rest] = sharedBundlesToMerge;
|
|
1555
|
+
for (let bundleId of rest) {
|
|
1556
|
+
mergeBundles(firstBundleId, bundleId, 'redundant-shared');
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// Remove old bundle group
|
|
1563
|
+
bundleGroupBundleIds.delete(bundleToRemoveId);
|
|
1564
|
+
|
|
1565
|
+
// Clean up bundle roots
|
|
1566
|
+
let bundleRootToRemoveNodeId = nullthrows(
|
|
1567
|
+
assetToBundleRootNodeId.get(
|
|
1568
|
+
nullthrows(bundleToRemove.mainEntryAsset),
|
|
1569
|
+
),
|
|
1570
|
+
);
|
|
1571
|
+
let bundleRootToKeepNodeId = nullthrows(
|
|
1572
|
+
assetToBundleRootNodeId.get(nullthrows(bundleToKeep.mainEntryAsset)),
|
|
1573
|
+
);
|
|
1574
|
+
|
|
1575
|
+
for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(
|
|
1576
|
+
bundleRootToRemoveNodeId,
|
|
1577
|
+
)) {
|
|
1578
|
+
bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
|
|
1579
|
+
bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(
|
|
1583
|
+
bundleRootToRemoveNodeId,
|
|
1584
|
+
)) {
|
|
1585
|
+
bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
|
|
1586
|
+
bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
bundleRoots.set(nullthrows(bundleToRemove.mainEntryAsset), [
|
|
1590
|
+
bundleToKeepId,
|
|
1591
|
+
bundleToKeepId,
|
|
1592
|
+
]);
|
|
1593
|
+
|
|
1594
|
+
// Merge dependency bundle graph
|
|
1595
|
+
for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(
|
|
1596
|
+
dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)),
|
|
1597
|
+
ALL_EDGE_TYPES,
|
|
1598
|
+
)) {
|
|
1599
|
+
let dependencyNode = nullthrows(
|
|
1600
|
+
dependencyBundleGraph.getNode(dependencyNodeId),
|
|
1601
|
+
);
|
|
1602
|
+
invariant(dependencyNode.type === 'dependency');
|
|
1603
|
+
|
|
1604
|
+
// Add dependency to the bundle to keep
|
|
1605
|
+
dependencyBundleGraph.addEdge(
|
|
1606
|
+
dependencyNodeId,
|
|
1607
|
+
dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)),
|
|
1608
|
+
dependencyPriorityEdges[dependencyNode.value.priority],
|
|
1609
|
+
);
|
|
1610
|
+
// Remove dependency from the bundle to remove
|
|
1611
|
+
dependencyBundleGraph.removeEdge(
|
|
1612
|
+
dependencyNodeId,
|
|
1613
|
+
dependencyBundleGraph.getNodeIdByContentKey(
|
|
1614
|
+
String(bundleToRemoveId),
|
|
1615
|
+
),
|
|
1616
|
+
dependencyPriorityEdges[dependencyNode.value.priority],
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
bundleGraph.removeNode(bundleToRemoveId);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function mergeSharedBundles(mergeConfig: SharedBundleMergeCandidates) {
|
|
1626
|
+
// Find all shared bundles
|
|
1627
|
+
let sharedBundles = new Set<NodeId>();
|
|
1628
|
+
bundleGraph.traverse((nodeId) => {
|
|
1629
|
+
let bundle = bundleGraph.getNode(nodeId);
|
|
1630
|
+
|
|
1631
|
+
if (!bundle) {
|
|
1632
|
+
throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
if (bundle === 'root') {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// Only consider JS shared bundles and non-reused bundles.
|
|
1640
|
+
// These count potentially be considered for merging in future but they're
|
|
1641
|
+
// more complicated to merge
|
|
1642
|
+
if (
|
|
1643
|
+
bundle.sourceBundles.size > 0 &&
|
|
1644
|
+
bundle.manualSharedBundle == null &&
|
|
1645
|
+
!bundle.mainEntryAsset &&
|
|
1646
|
+
bundle.type === 'js'
|
|
1647
|
+
) {
|
|
1648
|
+
sharedBundles.add(nodeId);
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
let clusters = findMergeCandidates(
|
|
1653
|
+
bundleGraph,
|
|
1654
|
+
Array.from(sharedBundles),
|
|
1655
|
+
mergeConfig.map(
|
|
1656
|
+
(config): MergeGroup => ({
|
|
1657
|
+
...config,
|
|
1658
|
+
sourceBundles: config.sourceBundles?.map((assetMatch: string) => {
|
|
1659
|
+
let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
|
|
1660
|
+
|
|
1661
|
+
if (sourceBundleNodeId == null) {
|
|
1662
|
+
throw new Error(
|
|
1663
|
+
`Source bundle ${assetMatch} not found in merge source bundle lookup`,
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
return sourceBundleNodeId;
|
|
1668
|
+
}),
|
|
1669
|
+
}),
|
|
1670
|
+
),
|
|
1671
|
+
);
|
|
1672
|
+
|
|
1673
|
+
let mergedBundles = new Set();
|
|
1674
|
+
|
|
1675
|
+
for (let cluster of clusters) {
|
|
1676
|
+
let [mergeTarget, ...rest] = cluster;
|
|
1677
|
+
|
|
1678
|
+
for (let bundleIdToMerge of rest) {
|
|
1679
|
+
mergeBundles(mergeTarget, bundleIdToMerge, 'shared-merge');
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
mergedBundles.add(mergeTarget);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1686
|
+
return mergedBundles;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
function mergeAsyncBundles({
|
|
1691
|
+
bundleSize,
|
|
1692
|
+
maxOverfetchSize,
|
|
1693
|
+
ignore,
|
|
1694
|
+
}: AsyncBundleMerge) {
|
|
1695
|
+
let mergeCandidates = [];
|
|
1696
|
+
let ignoreRegexes = ignore?.map((glob) => globToRegex(glob)) ?? [];
|
|
1697
|
+
|
|
1698
|
+
let isIgnored = (bundle: Bundle) => {
|
|
1699
|
+
if (!bundle.mainEntryAsset) {
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
let mainEntryFilePath = path.relative(
|
|
1704
|
+
config.projectRoot,
|
|
1705
|
+
nullthrows(bundle.mainEntryAsset).filePath,
|
|
1706
|
+
);
|
|
1707
|
+
|
|
1708
|
+
return ignoreRegexes.some((regex) => regex.test(mainEntryFilePath));
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
for (let [_bundleRootAsset, [bundleRootBundleId]] of bundleRoots) {
|
|
1712
|
+
let bundleRootBundle = nullthrows(
|
|
1713
|
+
bundleGraph.getNode(bundleRootBundleId),
|
|
1714
|
+
);
|
|
1715
|
+
invariant(bundleRootBundle !== 'root');
|
|
1716
|
+
|
|
1717
|
+
if (
|
|
1718
|
+
bundleRootBundle.type === 'js' &&
|
|
1719
|
+
bundleRootBundle.bundleBehavior !== 'inline' &&
|
|
1720
|
+
bundleRootBundle.bundleBehavior !== 'inlineIsolated' &&
|
|
1721
|
+
bundleRootBundle.size <= bundleSize &&
|
|
1722
|
+
!isIgnored(bundleRootBundle)
|
|
1723
|
+
) {
|
|
1724
|
+
mergeCandidates.push(bundleRootBundleId);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
let candidates = [];
|
|
1729
|
+
for (let i = 0; i < mergeCandidates.length; i++) {
|
|
1730
|
+
for (let j = i + 1; j < mergeCandidates.length; j++) {
|
|
1731
|
+
let a = mergeCandidates[i];
|
|
1732
|
+
let b = mergeCandidates[j];
|
|
1733
|
+
if (a === b) continue; // Skip self-comparison
|
|
1734
|
+
|
|
1735
|
+
candidates.push(scoreAsyncMerge(a, b, maxOverfetchSize));
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
let sortByScore = (
|
|
1740
|
+
a: AsyncBundleMergeCandidate,
|
|
1741
|
+
b: AsyncBundleMergeCandidate,
|
|
1742
|
+
) => {
|
|
1743
|
+
let diff = a.score - b.score;
|
|
1744
|
+
if (diff > 0) {
|
|
1745
|
+
return 1;
|
|
1746
|
+
} else if (diff < 0) {
|
|
1747
|
+
return -1;
|
|
1748
|
+
}
|
|
1749
|
+
return 0;
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1752
|
+
candidates = candidates
|
|
1753
|
+
.filter(
|
|
1754
|
+
({overfetchSize, score}) =>
|
|
1755
|
+
overfetchSize <= maxOverfetchSize && score > 0,
|
|
1756
|
+
)
|
|
1757
|
+
.sort(sortByScore);
|
|
1758
|
+
|
|
1759
|
+
// Tracks the bundles that have been merged
|
|
1760
|
+
let merged = new Set<NodeId>();
|
|
1761
|
+
// Tracks the deleted bundles to the bundle they were merged into.
|
|
1762
|
+
let mergeRemap = new Map<NodeId, NodeId>();
|
|
1763
|
+
// Tracks the bundles that have been rescored and added back into the
|
|
1764
|
+
// candidates.
|
|
1765
|
+
let rescored = new DefaultMap<NodeId, Set<NodeId>>(() => new Set());
|
|
1766
|
+
|
|
1767
|
+
do {
|
|
1768
|
+
let [a, b] = nullthrows(candidates.pop()).bundleIds;
|
|
1769
|
+
|
|
1770
|
+
if (
|
|
1771
|
+
bundleGraph.hasNode(a) &&
|
|
1772
|
+
bundleGraph.hasNode(b) &&
|
|
1773
|
+
((!merged.has(a) && !merged.has(b)) || rescored.get(a).has(b))
|
|
1774
|
+
) {
|
|
1775
|
+
mergeRemap.set(b, a);
|
|
1776
|
+
merged.add(a);
|
|
1777
|
+
rescored.get(a).clear();
|
|
1778
|
+
|
|
1779
|
+
mergeBundles(a, b, 'async-merge');
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// One or both of the bundles have been previously merged, so we'll
|
|
1784
|
+
// rescore and add the result back into the list of candidates.
|
|
1785
|
+
let getMergedBundleId = (bundleId: NodeId): NodeId | undefined => {
|
|
1786
|
+
let seen = new Set<NodeId>();
|
|
1787
|
+
while (!bundleGraph.hasNode(bundleId) && !seen.has(bundleId)) {
|
|
1788
|
+
seen.add(bundleId);
|
|
1789
|
+
bundleId = nullthrows(mergeRemap.get(bundleId));
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if (!bundleGraph.hasNode(bundleId)) {
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
return bundleId;
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
// Map a and b to their merged bundle ids if they've already been merged
|
|
1800
|
+
let currentA = getMergedBundleId(a);
|
|
1801
|
+
let currentB = getMergedBundleId(b);
|
|
1802
|
+
|
|
1803
|
+
if (
|
|
1804
|
+
!currentA ||
|
|
1805
|
+
!currentB ||
|
|
1806
|
+
// Bundles are already merged
|
|
1807
|
+
currentA === currentB
|
|
1808
|
+
) {
|
|
1809
|
+
// This combiniation is not valid, so we skip it.
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
let candidate = scoreAsyncMerge(currentA, currentB, maxOverfetchSize);
|
|
1814
|
+
|
|
1815
|
+
if (candidate.overfetchSize <= maxOverfetchSize && candidate.score > 0) {
|
|
1816
|
+
sortedArray.add(candidates, candidate, sortByScore);
|
|
1817
|
+
rescored.get(currentA).add(currentB);
|
|
1818
|
+
}
|
|
1819
|
+
} while (candidates.length > 0);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
function getBundle(bundleId: NodeId): Bundle {
|
|
1823
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
1824
|
+
if (bundle === 'root') {
|
|
1825
|
+
throw new Error(`Cannot access root bundle`);
|
|
1826
|
+
}
|
|
1827
|
+
if (bundle == null) {
|
|
1828
|
+
throw new Error(`Bundle ${bundleId} not found in bundle graph`);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
return bundle;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function getBigIntFromContentKey(contentKey: string) {
|
|
1253
1835
|
let b = Buffer.alloc(64);
|
|
1254
1836
|
b.write(contentKey);
|
|
1255
|
-
// $FlowFixMe Flow doesn't have BigInt types in this version
|
|
1256
1837
|
return b.readBigInt64BE();
|
|
1257
1838
|
}
|
|
1258
1839
|
// Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
|
|
@@ -1279,14 +1860,86 @@ export function createIdealGraph(
|
|
|
1279
1860
|
bundleRootGraph.removeNode(bundleRootId);
|
|
1280
1861
|
}
|
|
1281
1862
|
}
|
|
1282
|
-
function getBundlesForBundleGroup(bundleGroupId) {
|
|
1283
|
-
let bundlesInABundleGroup = [];
|
|
1863
|
+
function getBundlesForBundleGroup(bundleGroupId: NodeId) {
|
|
1864
|
+
let bundlesInABundleGroup: Array<NodeId> = [];
|
|
1284
1865
|
bundleGraph.traverse((nodeId) => {
|
|
1285
1866
|
bundlesInABundleGroup.push(nodeId);
|
|
1286
1867
|
}, bundleGroupId);
|
|
1287
1868
|
return bundlesInABundleGroup;
|
|
1288
1869
|
}
|
|
1289
1870
|
|
|
1871
|
+
interface AsyncBundleMergeCandidate {
|
|
1872
|
+
overfetchSize: number;
|
|
1873
|
+
score: number;
|
|
1874
|
+
bundleIds: number[];
|
|
1875
|
+
}
|
|
1876
|
+
function scoreAsyncMerge(
|
|
1877
|
+
bundleAId: NodeId,
|
|
1878
|
+
bundleBId: NodeId,
|
|
1879
|
+
maxOverfetchSize: number,
|
|
1880
|
+
): AsyncBundleMergeCandidate {
|
|
1881
|
+
let bundleGroupA = new Set(getBundlesForBundleGroup(bundleAId));
|
|
1882
|
+
let bundleGroupB = new Set(getBundlesForBundleGroup(bundleBId));
|
|
1883
|
+
|
|
1884
|
+
let overlapSize = 0;
|
|
1885
|
+
let overfetchSize = 0;
|
|
1886
|
+
|
|
1887
|
+
for (let bundleId of new Set([...bundleGroupA, ...bundleGroupB])) {
|
|
1888
|
+
let bundle = getBundle(bundleId);
|
|
1889
|
+
|
|
1890
|
+
if (bundleGroupA.has(bundleId) && bundleGroupB.has(bundleId)) {
|
|
1891
|
+
overlapSize += bundle.size;
|
|
1892
|
+
} else {
|
|
1893
|
+
overfetchSize += bundle.size;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
let overlapPercent = overlapSize / (overfetchSize + overlapSize);
|
|
1898
|
+
|
|
1899
|
+
let bundleAParents = getBundleParents(bundleAId);
|
|
1900
|
+
let bundleBParents = getBundleParents(bundleBId);
|
|
1901
|
+
|
|
1902
|
+
let sharedParentOverlap = 0;
|
|
1903
|
+
let sharedParentMismatch = 0;
|
|
1904
|
+
|
|
1905
|
+
for (let bundleId of new Set([...bundleAParents, ...bundleBParents])) {
|
|
1906
|
+
if (bundleAParents.has(bundleId) && bundleBParents.has(bundleId)) {
|
|
1907
|
+
sharedParentOverlap++;
|
|
1908
|
+
} else {
|
|
1909
|
+
sharedParentMismatch++;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
let overfetchScore = overfetchSize / maxOverfetchSize;
|
|
1914
|
+
let sharedParentPercent =
|
|
1915
|
+
sharedParentOverlap / (sharedParentOverlap + sharedParentMismatch);
|
|
1916
|
+
let score = sharedParentPercent + overlapPercent + overfetchScore;
|
|
1917
|
+
|
|
1918
|
+
return {
|
|
1919
|
+
overfetchSize,
|
|
1920
|
+
score,
|
|
1921
|
+
bundleIds: [bundleAId, bundleBId],
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function getBundleParents(bundleId: NodeId): Set<NodeId> {
|
|
1926
|
+
let parents = new Set<NodeId>();
|
|
1927
|
+
let {bundleRoots} = getBundle(bundleId);
|
|
1928
|
+
|
|
1929
|
+
for (let bundleRoot of bundleRoots) {
|
|
1930
|
+
let bundleRootNodeId = nullthrows(
|
|
1931
|
+
assetToBundleRootNodeId.get(bundleRoot),
|
|
1932
|
+
);
|
|
1933
|
+
for (let parentId of bundleRootGraph.getNodeIdsConnectedTo(
|
|
1934
|
+
bundleRootNodeId,
|
|
1935
|
+
ALL_EDGE_TYPES,
|
|
1936
|
+
)) {
|
|
1937
|
+
parents.add(parentId);
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
return parents;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1290
1943
|
function getBundleFromBundleRoot(bundleRoot: BundleRoot): Bundle {
|
|
1291
1944
|
let bundle = bundleGraph.getNode(
|
|
1292
1945
|
nullthrows(bundleRoots.get(bundleRoot))[0],
|
|
@@ -1351,6 +2004,12 @@ export function createIdealGraph(
|
|
|
1351
2004
|
bundleGraph.removeNode(bundleId);
|
|
1352
2005
|
}
|
|
1353
2006
|
|
|
2007
|
+
stats.report((bundleId) => {
|
|
2008
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
2009
|
+
invariant(bundle !== 'root');
|
|
2010
|
+
return bundle;
|
|
2011
|
+
});
|
|
2012
|
+
|
|
1354
2013
|
return {
|
|
1355
2014
|
assets,
|
|
1356
2015
|
bundleGraph,
|
|
@@ -1361,23 +2020,24 @@ export function createIdealGraph(
|
|
|
1361
2020
|
};
|
|
1362
2021
|
}
|
|
1363
2022
|
|
|
1364
|
-
function createBundle(opts: {
|
|
1365
|
-
asset?: Asset
|
|
1366
|
-
bundleBehavior?:
|
|
1367
|
-
env?: Environment
|
|
1368
|
-
manualSharedBundle?:
|
|
1369
|
-
needsStableName?: boolean
|
|
1370
|
-
sourceBundles?: Set<NodeId
|
|
1371
|
-
target: Target
|
|
1372
|
-
type?: string
|
|
1373
|
-
uniqueKey?: string
|
|
1374
|
-
|
|
2023
|
+
function createBundle(opts: {
|
|
2024
|
+
asset?: Asset;
|
|
2025
|
+
bundleBehavior?: BundleBehavior | null | undefined;
|
|
2026
|
+
env?: Environment;
|
|
2027
|
+
manualSharedBundle?: string | null | undefined;
|
|
2028
|
+
needsStableName?: boolean;
|
|
2029
|
+
sourceBundles?: Set<NodeId>;
|
|
2030
|
+
target: Target;
|
|
2031
|
+
type?: string;
|
|
2032
|
+
uniqueKey?: string;
|
|
2033
|
+
}): Bundle {
|
|
1375
2034
|
if (opts.asset == null) {
|
|
1376
2035
|
return {
|
|
1377
2036
|
assets: new Set(),
|
|
1378
2037
|
bundleBehavior: opts.bundleBehavior,
|
|
1379
2038
|
env: nullthrows(opts.env),
|
|
1380
2039
|
mainEntryAsset: null,
|
|
2040
|
+
bundleRoots: new Set(),
|
|
1381
2041
|
manualSharedBundle: opts.manualSharedBundle,
|
|
1382
2042
|
needsStableName: Boolean(opts.needsStableName),
|
|
1383
2043
|
size: 0,
|
|
@@ -1394,6 +2054,7 @@ function createBundle(opts: {|
|
|
|
1394
2054
|
bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
|
|
1395
2055
|
env: opts.env ?? asset.env,
|
|
1396
2056
|
mainEntryAsset: asset,
|
|
2057
|
+
bundleRoots: new Set([asset]),
|
|
1397
2058
|
manualSharedBundle: opts.manualSharedBundle,
|
|
1398
2059
|
needsStableName: Boolean(opts.needsStableName),
|
|
1399
2060
|
size: asset.stats.size,
|