@atlaspack/bundler-default 2.14.5-canary.20 → 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 +411 -0
- package/lib/DefaultBundler.js +6 -1
- package/lib/MonolithicBundler.js +11 -3
- package/lib/bundleMerge.js +106 -37
- package/lib/bundlerConfig.js +51 -5
- package/lib/decorateLegacyGraph.js +24 -3
- package/lib/idealGraph.js +426 -50
- 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} +105 -44
- package/src/{decorateLegacyGraph.js → decorateLegacyGraph.ts} +26 -7
- package/src/{idealGraph.js → idealGraph.ts} +669 -102
- package/src/memoize.ts +32 -0
- package/src/stats.ts +97 -0
- package/tsconfig.json +4 -0
- package/src/bundleMerge.js +0 -103
|
@@ -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,37 +22,43 @@ import {DefaultMap, globToRegex} from '@atlaspack/utils';
|
|
|
23
22
|
import invariant from 'assert';
|
|
24
23
|
import nullthrows from 'nullthrows';
|
|
25
24
|
|
|
26
|
-
import {findMergeCandidates} from './bundleMerge';
|
|
27
|
-
import type {
|
|
25
|
+
import {findMergeCandidates, MergeGroup} from './bundleMerge';
|
|
26
|
+
import type {
|
|
27
|
+
ResolvedBundlerConfig,
|
|
28
|
+
SharedBundleMergeCandidates,
|
|
29
|
+
AsyncBundleMerge,
|
|
30
|
+
} from './bundlerConfig';
|
|
31
|
+
import {Stats} from './stats';
|
|
28
32
|
|
|
29
33
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
30
34
|
type BundleRoot = Asset;
|
|
31
35
|
|
|
32
|
-
export type Bundle = {
|
|
33
|
-
uniqueKey:
|
|
34
|
-
assets: Set<Asset
|
|
35
|
-
internalizedAssets?: BitSet
|
|
36
|
-
bundleBehavior?:
|
|
37
|
-
needsStableName: boolean
|
|
38
|
-
mainEntryAsset:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
|
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
|
+
};
|
|
46
51
|
|
|
47
52
|
export type DependencyBundleGraph = ContentGraph<
|
|
48
|
-
| {
|
|
49
|
-
value: Bundle
|
|
50
|
-
type: 'bundle'
|
|
51
|
-
|
|
52
|
-
| {
|
|
53
|
-
value: Dependency
|
|
54
|
-
type: 'dependency'
|
|
55
|
-
|
|
56
|
-
number
|
|
53
|
+
| {
|
|
54
|
+
value: Bundle;
|
|
55
|
+
type: 'bundle';
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
value: Dependency;
|
|
59
|
+
type: 'dependency';
|
|
60
|
+
},
|
|
61
|
+
number
|
|
57
62
|
>;
|
|
58
63
|
|
|
59
64
|
const dependencyPriorityEdges = {
|
|
@@ -61,7 +66,7 @@ const dependencyPriorityEdges = {
|
|
|
61
66
|
parallel: 2,
|
|
62
67
|
lazy: 3,
|
|
63
68
|
conditional: 4,
|
|
64
|
-
};
|
|
69
|
+
} as const;
|
|
65
70
|
|
|
66
71
|
export const idealBundleGraphEdges = Object.freeze({
|
|
67
72
|
default: 1,
|
|
@@ -70,20 +75,30 @@ export const idealBundleGraphEdges = Object.freeze({
|
|
|
70
75
|
|
|
71
76
|
export type IdealBundleGraph = Graph<
|
|
72
77
|
Bundle | 'root',
|
|
73
|
-
|
|
78
|
+
(typeof idealBundleGraphEdges)[keyof typeof idealBundleGraphEdges]
|
|
74
79
|
>;
|
|
75
80
|
|
|
76
81
|
// IdealGraph is the structure we will pass to decorate,
|
|
77
82
|
// which mutates the assetGraph into the bundleGraph we would
|
|
78
83
|
// 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
|
-
|
|
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
|
+
}
|
|
87
102
|
|
|
88
103
|
export function createIdealGraph(
|
|
89
104
|
assetGraph: MutableBundleGraph,
|
|
@@ -97,8 +112,9 @@ export function createIdealGraph(
|
|
|
97
112
|
let dependencyBundleGraph: DependencyBundleGraph = new ContentGraph();
|
|
98
113
|
let assetReference: DefaultMap<
|
|
99
114
|
Asset,
|
|
100
|
-
Array<[Dependency, Bundle]
|
|
115
|
+
Array<[Dependency, Bundle]>
|
|
101
116
|
> = new DefaultMap(() => []);
|
|
117
|
+
let stats = new Stats(config.projectRoot);
|
|
102
118
|
|
|
103
119
|
// A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their
|
|
104
120
|
// referencing Bundle. There are no actual BundleGroup nodes, just bundles that take on that role.
|
|
@@ -111,8 +127,9 @@ export function createIdealGraph(
|
|
|
111
127
|
};
|
|
112
128
|
// Graph that models bundleRoots, with parallel & async deps only to inform reachability
|
|
113
129
|
let bundleRootGraph: Graph<
|
|
114
|
-
|
|
115
|
-
|
|
130
|
+
// asset index
|
|
131
|
+
number,
|
|
132
|
+
(typeof bundleRootEdgeTypes)[keyof typeof bundleRootEdgeTypes]
|
|
116
133
|
> = new Graph();
|
|
117
134
|
let assetToBundleRootNodeId = new Map<BundleRoot, number>();
|
|
118
135
|
|
|
@@ -146,7 +163,7 @@ export function createIdealGraph(
|
|
|
146
163
|
bundleGroupBundleIds.add(nodeId);
|
|
147
164
|
}
|
|
148
165
|
|
|
149
|
-
let assets = [];
|
|
166
|
+
let assets: Array<Asset> = [];
|
|
150
167
|
let assetToIndex = new Map<Asset, number>();
|
|
151
168
|
|
|
152
169
|
function makeManualAssetToConfigLookup() {
|
|
@@ -161,6 +178,7 @@ export function createIdealGraph(
|
|
|
161
178
|
|
|
162
179
|
for (let c of config.manualSharedBundles) {
|
|
163
180
|
if (c.root != null) {
|
|
181
|
+
// @ts-expect-error TS2345
|
|
164
182
|
parentsToConfig.get(path.join(config.projectRoot, c.root)).push(c);
|
|
165
183
|
}
|
|
166
184
|
}
|
|
@@ -204,17 +222,17 @@ export function createIdealGraph(
|
|
|
204
222
|
config.projectRoot,
|
|
205
223
|
node.value.filePath,
|
|
206
224
|
);
|
|
207
|
-
if (!assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
225
|
|
|
211
226
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
212
227
|
// and need to be assigned to all matching bundles
|
|
213
228
|
if (node.value.meta.isConstantModule === true) {
|
|
229
|
+
// @ts-expect-error TS2345
|
|
214
230
|
constantModuleToMSB.get(node.value).push(c);
|
|
215
231
|
}
|
|
216
|
-
|
|
217
|
-
|
|
232
|
+
|
|
233
|
+
if (assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
234
|
+
manualAssetToConfig.set(node.value, c);
|
|
235
|
+
}
|
|
218
236
|
}
|
|
219
237
|
|
|
220
238
|
if (
|
|
@@ -242,9 +260,19 @@ export function createIdealGraph(
|
|
|
242
260
|
makeManualAssetToConfigLookup();
|
|
243
261
|
let manualBundleToInternalizedAsset: DefaultMap<
|
|
244
262
|
NodeId,
|
|
245
|
-
Array<Asset
|
|
263
|
+
Array<Asset>
|
|
246
264
|
> = new DefaultMap(() => []);
|
|
247
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
|
+
|
|
248
276
|
/**
|
|
249
277
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
250
278
|
* for asset type changes, parallel, inline, and async or lazy dependencies,
|
|
@@ -252,7 +280,27 @@ export function createIdealGraph(
|
|
|
252
280
|
*/
|
|
253
281
|
assetGraph.traverse(
|
|
254
282
|
{
|
|
255
|
-
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
|
+
) {
|
|
256
304
|
if (node.type === 'asset') {
|
|
257
305
|
if (
|
|
258
306
|
context?.type === 'dependency' &&
|
|
@@ -312,7 +360,8 @@ export function createIdealGraph(
|
|
|
312
360
|
dependency.priority === 'lazy' ||
|
|
313
361
|
(getFeatureFlag('conditionalBundlingApi') &&
|
|
314
362
|
node.value.priority === 'conditional') ||
|
|
315
|
-
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'
|
|
316
365
|
) {
|
|
317
366
|
if (bundleId == null) {
|
|
318
367
|
let firstBundleGroup = nullthrows(
|
|
@@ -325,7 +374,9 @@ export function createIdealGraph(
|
|
|
325
374
|
dependency.bundleBehavior ?? childAsset.bundleBehavior,
|
|
326
375
|
needsStableName:
|
|
327
376
|
dependency.bundleBehavior === 'inline' ||
|
|
328
|
-
childAsset.bundleBehavior === 'inline'
|
|
377
|
+
childAsset.bundleBehavior === 'inline' ||
|
|
378
|
+
dependency.bundleBehavior === 'inlineIsolated' ||
|
|
379
|
+
childAsset.bundleBehavior === 'inlineIsolated'
|
|
329
380
|
? false
|
|
330
381
|
: dependency.isEntry || dependency.needsStableName,
|
|
331
382
|
target: firstBundleGroup.target,
|
|
@@ -335,6 +386,14 @@ export function createIdealGraph(
|
|
|
335
386
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
336
387
|
bundleGroupBundleIds.add(bundleId);
|
|
337
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
|
+
}
|
|
338
397
|
if (manualSharedObject) {
|
|
339
398
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
340
399
|
// since MSBs should not have main entry assets
|
|
@@ -349,7 +408,8 @@ export function createIdealGraph(
|
|
|
349
408
|
if (
|
|
350
409
|
// If this dependency requests isolated, but the bundle is not,
|
|
351
410
|
// make the bundle isolated for all uses.
|
|
352
|
-
dependency.bundleBehavior === 'isolated'
|
|
411
|
+
(dependency.bundleBehavior === 'isolated' ||
|
|
412
|
+
dependency.bundleBehavior === 'inlineIsolated') &&
|
|
353
413
|
bundle.bundleBehavior == null
|
|
354
414
|
) {
|
|
355
415
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
@@ -371,6 +431,7 @@ export function createIdealGraph(
|
|
|
371
431
|
type: 'bundle',
|
|
372
432
|
},
|
|
373
433
|
),
|
|
434
|
+
// @ts-expect-error TS7053
|
|
374
435
|
dependencyPriorityEdges[dependency.priority],
|
|
375
436
|
);
|
|
376
437
|
|
|
@@ -432,6 +493,7 @@ export function createIdealGraph(
|
|
|
432
493
|
needsStableName:
|
|
433
494
|
childAsset.bundleBehavior === 'inline' ||
|
|
434
495
|
dependency.bundleBehavior === 'inline' ||
|
|
496
|
+
dependency.bundleBehavior === 'inlineIsolated' ||
|
|
435
497
|
(dependency.priority === 'parallel' &&
|
|
436
498
|
!dependency.needsStableName)
|
|
437
499
|
? false
|
|
@@ -445,7 +507,8 @@ export function createIdealGraph(
|
|
|
445
507
|
if (
|
|
446
508
|
// If this dependency requests isolated, but the bundle is not,
|
|
447
509
|
// make the bundle isolated for all uses.
|
|
448
|
-
dependency.bundleBehavior === 'isolated'
|
|
510
|
+
(dependency.bundleBehavior === 'isolated' ||
|
|
511
|
+
dependency.bundleBehavior === 'inlineIsolated') &&
|
|
449
512
|
bundle.bundleBehavior == null
|
|
450
513
|
) {
|
|
451
514
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
@@ -482,6 +545,7 @@ export function createIdealGraph(
|
|
|
482
545
|
|
|
483
546
|
assetReference.get(childAsset).push([dependency, bundle]);
|
|
484
547
|
} else {
|
|
548
|
+
// @ts-expect-error TS2322
|
|
485
549
|
bundleId = null;
|
|
486
550
|
}
|
|
487
551
|
if (manualSharedObject && bundleId != null) {
|
|
@@ -489,6 +553,7 @@ export function createIdealGraph(
|
|
|
489
553
|
// add the asset if it doesn't already have it and set key
|
|
490
554
|
|
|
491
555
|
invariant(
|
|
556
|
+
// @ts-expect-error TS2367
|
|
492
557
|
bundle !== 'root' && bundle != null && bundleId != null,
|
|
493
558
|
);
|
|
494
559
|
|
|
@@ -515,7 +580,8 @@ export function createIdealGraph(
|
|
|
515
580
|
}
|
|
516
581
|
return node;
|
|
517
582
|
},
|
|
518
|
-
|
|
583
|
+
// @ts-expect-error TS2322
|
|
584
|
+
exit(node: BundleGraphTraversable) {
|
|
519
585
|
if (stack[stack.length - 1]?.[0] === node.value) {
|
|
520
586
|
stack.pop();
|
|
521
587
|
}
|
|
@@ -564,20 +630,20 @@ export function createIdealGraph(
|
|
|
564
630
|
|
|
565
631
|
// reachableRoots is an array of bit sets for each asset. Each bit set
|
|
566
632
|
// indicates which bundle roots are reachable from that asset synchronously.
|
|
567
|
-
let reachableRoots = [];
|
|
633
|
+
let reachableRoots: Array<BitSet> = [];
|
|
568
634
|
for (let i = 0; i < assets.length; i++) {
|
|
569
635
|
reachableRoots.push(new BitSet(bundleRootGraph.nodes.length));
|
|
570
636
|
}
|
|
571
637
|
|
|
572
638
|
// reachableAssets is the inverse mapping of reachableRoots. For each bundle root,
|
|
573
639
|
// it contains a bit set that indicates which assets are reachable from it.
|
|
574
|
-
let reachableAssets = [];
|
|
640
|
+
let reachableAssets: Array<BitSet> = [];
|
|
575
641
|
|
|
576
642
|
// ancestorAssets maps bundle roots to the set of all assets available to it at runtime,
|
|
577
643
|
// including in earlier parallel bundles. These are intersected through all paths to
|
|
578
644
|
// the bundle to ensure that the available assets are always present no matter in which
|
|
579
645
|
// order the bundles are loaded.
|
|
580
|
-
let ancestorAssets = [];
|
|
646
|
+
let ancestorAssets: Array<null | BitSet> = [];
|
|
581
647
|
|
|
582
648
|
let inlineConstantDeps = new DefaultMap(() => new Set());
|
|
583
649
|
|
|
@@ -688,7 +754,10 @@ export function createIdealGraph(
|
|
|
688
754
|
// not true that a bundle's available assets = all assets of all the bundleGroups
|
|
689
755
|
// it belongs to. It's the intersection of those sets.
|
|
690
756
|
let available;
|
|
691
|
-
if (
|
|
757
|
+
if (
|
|
758
|
+
bundleRoot.bundleBehavior === 'isolated' ||
|
|
759
|
+
bundleRoot.bundleBehavior === 'inlineIsolated'
|
|
760
|
+
) {
|
|
692
761
|
available = new BitSet(assets.length);
|
|
693
762
|
} else {
|
|
694
763
|
available = nullthrows(ancestorAssets[nodeId]).clone();
|
|
@@ -772,7 +841,8 @@ export function createIdealGraph(
|
|
|
772
841
|
|
|
773
842
|
let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, ALL_EDGE_TYPES);
|
|
774
843
|
let canDelete =
|
|
775
|
-
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated'
|
|
844
|
+
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated' &&
|
|
845
|
+
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'inlineIsolated';
|
|
776
846
|
if (parentRoots.length === 0) continue;
|
|
777
847
|
for (let parentId of parentRoots) {
|
|
778
848
|
if (parentId === rootNodeId) {
|
|
@@ -806,8 +876,11 @@ export function createIdealGraph(
|
|
|
806
876
|
|
|
807
877
|
function assignInlineConstants(parentAsset: Asset, bundle: Bundle) {
|
|
808
878
|
for (let inlineConstant of inlineConstantDeps.get(parentAsset)) {
|
|
879
|
+
// @ts-expect-error TS2345
|
|
809
880
|
if (!bundle.assets.has(inlineConstant)) {
|
|
881
|
+
// @ts-expect-error TS2345
|
|
810
882
|
bundle.assets.add(inlineConstant);
|
|
883
|
+
// @ts-expect-error TS18046
|
|
811
884
|
bundle.size += inlineConstant.stats.size;
|
|
812
885
|
}
|
|
813
886
|
}
|
|
@@ -849,7 +922,8 @@ export function createIdealGraph(
|
|
|
849
922
|
!a.isBundleSplittable ||
|
|
850
923
|
(bundleRoots.get(a) &&
|
|
851
924
|
(getBundleFromBundleRoot(a).needsStableName ||
|
|
852
|
-
getBundleFromBundleRoot(a).bundleBehavior === 'isolated'
|
|
925
|
+
getBundleFromBundleRoot(a).bundleBehavior === 'isolated' ||
|
|
926
|
+
getBundleFromBundleRoot(a).bundleBehavior === 'inlineIsolated'))
|
|
853
927
|
) {
|
|
854
928
|
// Add asset to non-splittable bundles.
|
|
855
929
|
addAssetToBundleRoot(asset, a);
|
|
@@ -867,7 +941,7 @@ export function createIdealGraph(
|
|
|
867
941
|
let bundle;
|
|
868
942
|
let bundleId;
|
|
869
943
|
let manualSharedBundleKey = manualSharedObject.name + ',' + asset.type;
|
|
870
|
-
let sourceBundles = [];
|
|
944
|
+
let sourceBundles: Array<NodeId> = [];
|
|
871
945
|
reachable.forEach((id) => {
|
|
872
946
|
sourceBundles.push(nullthrows(bundleRoots.get(assets[id]))[0]);
|
|
873
947
|
});
|
|
@@ -982,7 +1056,7 @@ export function createIdealGraph(
|
|
|
982
1056
|
});
|
|
983
1057
|
}
|
|
984
1058
|
|
|
985
|
-
let reachableArray = [];
|
|
1059
|
+
let reachableArray: Array<Asset> = [];
|
|
986
1060
|
reachable.forEach((id) => {
|
|
987
1061
|
reachableArray.push(assets[id]);
|
|
988
1062
|
});
|
|
@@ -1057,6 +1131,8 @@ export function createIdealGraph(
|
|
|
1057
1131
|
}
|
|
1058
1132
|
|
|
1059
1133
|
let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
|
|
1134
|
+
let modifiedSourceBundles = new Set<Bundle>();
|
|
1135
|
+
|
|
1060
1136
|
// Step split manual shared bundles for those that have the "split" property set
|
|
1061
1137
|
let remainderMap = new DefaultMap(() => []);
|
|
1062
1138
|
for (let id of manualSharedMap.values()) {
|
|
@@ -1075,9 +1151,9 @@ export function createIdealGraph(
|
|
|
1075
1151
|
if (modNum != null) {
|
|
1076
1152
|
for (let a of [...manualBundle.assets]) {
|
|
1077
1153
|
let numRep = getBigIntFromContentKey(a.id);
|
|
1078
|
-
// $FlowFixMe Flow doesn't know about BigInt
|
|
1079
1154
|
let r = Number(numRep % BigInt(modNum));
|
|
1080
1155
|
|
|
1156
|
+
// @ts-expect-error TS2345
|
|
1081
1157
|
remainderMap.get(r).push(a);
|
|
1082
1158
|
}
|
|
1083
1159
|
|
|
@@ -1100,8 +1176,10 @@ export function createIdealGraph(
|
|
|
1100
1176
|
}
|
|
1101
1177
|
for (let sp of remainderMap.get(i)) {
|
|
1102
1178
|
bundle.assets.add(sp);
|
|
1179
|
+
// @ts-expect-error TS2339
|
|
1103
1180
|
bundle.size += sp.stats.size;
|
|
1104
1181
|
manualBundle.assets.delete(sp);
|
|
1182
|
+
// @ts-expect-error TS2339
|
|
1105
1183
|
manualBundle.size -= sp.stats.size;
|
|
1106
1184
|
}
|
|
1107
1185
|
}
|
|
@@ -1114,6 +1192,7 @@ export function createIdealGraph(
|
|
|
1114
1192
|
// match multiple MSB's
|
|
1115
1193
|
for (let [asset, msbs] of constantModuleToMSB.entries()) {
|
|
1116
1194
|
for (let manualSharedObject of msbs) {
|
|
1195
|
+
// @ts-expect-error TS2339
|
|
1117
1196
|
let bundleId = manualSharedMap.get(manualSharedObject.name + ',js');
|
|
1118
1197
|
if (bundleId == null) continue;
|
|
1119
1198
|
let bundle = nullthrows(bundleGraph.getNode(bundleId));
|
|
@@ -1122,17 +1201,97 @@ export function createIdealGraph(
|
|
|
1122
1201
|
'We tried to use the root incorrectly',
|
|
1123
1202
|
);
|
|
1124
1203
|
|
|
1204
|
+
// @ts-expect-error TS2345
|
|
1125
1205
|
if (!bundle.assets.has(asset)) {
|
|
1206
|
+
// @ts-expect-error TS2345
|
|
1126
1207
|
bundle.assets.add(asset);
|
|
1208
|
+
// @ts-expect-error TS18046
|
|
1127
1209
|
bundle.size += asset.stats.size;
|
|
1128
1210
|
}
|
|
1129
1211
|
}
|
|
1130
1212
|
}
|
|
1131
1213
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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);
|
|
1136
1295
|
}
|
|
1137
1296
|
|
|
1138
1297
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
@@ -1151,7 +1310,6 @@ export function createIdealGraph(
|
|
|
1151
1310
|
}
|
|
1152
1311
|
|
|
1153
1312
|
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1154
|
-
let modifiedSourceBundles = new Set();
|
|
1155
1313
|
|
|
1156
1314
|
if (config.disableSharedBundles === false) {
|
|
1157
1315
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
@@ -1165,7 +1323,13 @@ export function createIdealGraph(
|
|
|
1165
1323
|
let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
|
|
1166
1324
|
let bundle = nullthrows(bundleGraph.getNode(b));
|
|
1167
1325
|
invariant(bundle !== 'root');
|
|
1168
|
-
return
|
|
1326
|
+
return (
|
|
1327
|
+
count +
|
|
1328
|
+
Number(
|
|
1329
|
+
bundle.bundleBehavior !== 'inline' &&
|
|
1330
|
+
bundle.bundleBehavior !== 'inlineIsolated',
|
|
1331
|
+
)
|
|
1332
|
+
);
|
|
1169
1333
|
}, 0);
|
|
1170
1334
|
|
|
1171
1335
|
if (numBundlesContributingToPRL > config.maxParallelRequests) {
|
|
@@ -1200,7 +1364,9 @@ export function createIdealGraph(
|
|
|
1200
1364
|
numBundlesContributingToPRL > config.maxParallelRequests
|
|
1201
1365
|
) {
|
|
1202
1366
|
let bundleTuple = sharedBundlesInGroup.pop();
|
|
1367
|
+
// @ts-expect-error TS18048
|
|
1203
1368
|
let bundleToRemove = bundleTuple.bundle;
|
|
1369
|
+
// @ts-expect-error TS18048
|
|
1204
1370
|
let bundleIdToRemove = bundleTuple.id;
|
|
1205
1371
|
//TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
|
|
1206
1372
|
// but total # bundles still exceeds limit due to non shared bundles
|
|
@@ -1257,51 +1423,206 @@ export function createIdealGraph(
|
|
|
1257
1423
|
}
|
|
1258
1424
|
|
|
1259
1425
|
function mergeBundles(
|
|
1260
|
-
bundleGraph: IdealBundleGraph,
|
|
1261
1426
|
bundleToKeepId: NodeId,
|
|
1262
1427
|
bundleToRemoveId: NodeId,
|
|
1263
|
-
|
|
1428
|
+
reason: string,
|
|
1264
1429
|
) {
|
|
1265
|
-
|
|
1266
|
-
let
|
|
1267
|
-
|
|
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);
|
|
1268
1440
|
for (let asset of bundleToRemove.assets) {
|
|
1269
1441
|
bundleToKeep.assets.add(asset);
|
|
1270
1442
|
bundleToKeep.size += asset.stats.size;
|
|
1271
1443
|
|
|
1272
1444
|
let newAssetReference = assetReference
|
|
1273
1445
|
.get(asset)
|
|
1274
|
-
.map(([dep, bundle]) =>
|
|
1446
|
+
.map(([dep, bundle]: [any, any]) =>
|
|
1275
1447
|
bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
|
|
1276
1448
|
);
|
|
1277
1449
|
|
|
1450
|
+
// @ts-expect-error TS2345
|
|
1278
1451
|
assetReference.set(asset, newAssetReference);
|
|
1279
1452
|
}
|
|
1280
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
|
|
1281
1474
|
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1282
1475
|
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1283
1476
|
continue;
|
|
1284
1477
|
}
|
|
1285
1478
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1479
|
+
if (sourceBundleId !== bundleToKeepId) {
|
|
1480
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
1481
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1482
|
+
}
|
|
1288
1483
|
}
|
|
1289
1484
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
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
|
+
),
|
|
1295
1570
|
);
|
|
1296
|
-
|
|
1297
|
-
|
|
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
|
+
}
|
|
1298
1619
|
}
|
|
1299
1620
|
}
|
|
1300
1621
|
|
|
1301
1622
|
bundleGraph.removeNode(bundleToRemoveId);
|
|
1302
1623
|
}
|
|
1303
1624
|
|
|
1304
|
-
function
|
|
1625
|
+
function mergeSharedBundles(mergeConfig: SharedBundleMergeCandidates) {
|
|
1305
1626
|
// Find all shared bundles
|
|
1306
1627
|
let sharedBundles = new Set<NodeId>();
|
|
1307
1628
|
bundleGraph.traverse((nodeId) => {
|
|
@@ -1331,22 +1652,188 @@ export function createIdealGraph(
|
|
|
1331
1652
|
let clusters = findMergeCandidates(
|
|
1332
1653
|
bundleGraph,
|
|
1333
1654
|
Array.from(sharedBundles),
|
|
1334
|
-
|
|
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
|
+
),
|
|
1335
1671
|
);
|
|
1336
1672
|
|
|
1673
|
+
let mergedBundles = new Set();
|
|
1674
|
+
|
|
1337
1675
|
for (let cluster of clusters) {
|
|
1338
1676
|
let [mergeTarget, ...rest] = cluster;
|
|
1339
1677
|
|
|
1340
1678
|
for (let bundleIdToMerge of rest) {
|
|
1341
|
-
mergeBundles(
|
|
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;
|
|
1342
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`);
|
|
1343
1829
|
}
|
|
1830
|
+
|
|
1831
|
+
return bundle;
|
|
1344
1832
|
}
|
|
1345
1833
|
|
|
1346
|
-
function getBigIntFromContentKey(contentKey) {
|
|
1834
|
+
function getBigIntFromContentKey(contentKey: string) {
|
|
1347
1835
|
let b = Buffer.alloc(64);
|
|
1348
1836
|
b.write(contentKey);
|
|
1349
|
-
// $FlowFixMe Flow doesn't have BigInt types in this version
|
|
1350
1837
|
return b.readBigInt64BE();
|
|
1351
1838
|
}
|
|
1352
1839
|
// Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
|
|
@@ -1373,14 +1860,86 @@ export function createIdealGraph(
|
|
|
1373
1860
|
bundleRootGraph.removeNode(bundleRootId);
|
|
1374
1861
|
}
|
|
1375
1862
|
}
|
|
1376
|
-
function getBundlesForBundleGroup(bundleGroupId) {
|
|
1377
|
-
let bundlesInABundleGroup = [];
|
|
1863
|
+
function getBundlesForBundleGroup(bundleGroupId: NodeId) {
|
|
1864
|
+
let bundlesInABundleGroup: Array<NodeId> = [];
|
|
1378
1865
|
bundleGraph.traverse((nodeId) => {
|
|
1379
1866
|
bundlesInABundleGroup.push(nodeId);
|
|
1380
1867
|
}, bundleGroupId);
|
|
1381
1868
|
return bundlesInABundleGroup;
|
|
1382
1869
|
}
|
|
1383
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
|
+
|
|
1384
1943
|
function getBundleFromBundleRoot(bundleRoot: BundleRoot): Bundle {
|
|
1385
1944
|
let bundle = bundleGraph.getNode(
|
|
1386
1945
|
nullthrows(bundleRoots.get(bundleRoot))[0],
|
|
@@ -1445,6 +2004,12 @@ export function createIdealGraph(
|
|
|
1445
2004
|
bundleGraph.removeNode(bundleId);
|
|
1446
2005
|
}
|
|
1447
2006
|
|
|
2007
|
+
stats.report((bundleId) => {
|
|
2008
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
2009
|
+
invariant(bundle !== 'root');
|
|
2010
|
+
return bundle;
|
|
2011
|
+
});
|
|
2012
|
+
|
|
1448
2013
|
return {
|
|
1449
2014
|
assets,
|
|
1450
2015
|
bundleGraph,
|
|
@@ -1455,23 +2020,24 @@ export function createIdealGraph(
|
|
|
1455
2020
|
};
|
|
1456
2021
|
}
|
|
1457
2022
|
|
|
1458
|
-
function createBundle(opts: {
|
|
1459
|
-
asset?: Asset
|
|
1460
|
-
bundleBehavior?:
|
|
1461
|
-
env?: Environment
|
|
1462
|
-
manualSharedBundle?:
|
|
1463
|
-
needsStableName?: boolean
|
|
1464
|
-
sourceBundles?: Set<NodeId
|
|
1465
|
-
target: Target
|
|
1466
|
-
type?: string
|
|
1467
|
-
uniqueKey?: string
|
|
1468
|
-
|
|
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 {
|
|
1469
2034
|
if (opts.asset == null) {
|
|
1470
2035
|
return {
|
|
1471
2036
|
assets: new Set(),
|
|
1472
2037
|
bundleBehavior: opts.bundleBehavior,
|
|
1473
2038
|
env: nullthrows(opts.env),
|
|
1474
2039
|
mainEntryAsset: null,
|
|
2040
|
+
bundleRoots: new Set(),
|
|
1475
2041
|
manualSharedBundle: opts.manualSharedBundle,
|
|
1476
2042
|
needsStableName: Boolean(opts.needsStableName),
|
|
1477
2043
|
size: 0,
|
|
@@ -1488,6 +2054,7 @@ function createBundle(opts: {|
|
|
|
1488
2054
|
bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
|
|
1489
2055
|
env: opts.env ?? asset.env,
|
|
1490
2056
|
mainEntryAsset: asset,
|
|
2057
|
+
bundleRoots: new Set([asset]),
|
|
1491
2058
|
manualSharedBundle: opts.manualSharedBundle,
|
|
1492
2059
|
needsStableName: Boolean(opts.needsStableName),
|
|
1493
2060
|
size: asset.stats.size,
|