@atlaspack/bundler-default 2.14.5-canary.36 → 2.14.5-canary.361
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 +613 -0
- package/dist/DefaultBundler.js +84 -0
- package/dist/MonolithicBundler.js +68 -0
- package/dist/bundleMerge.js +137 -0
- package/dist/bundlerConfig.js +223 -0
- package/dist/decorateLegacyGraph.js +189 -0
- package/dist/idealGraph.js +1471 -0
- package/dist/memoize.js +31 -0
- package/dist/stats.js +69 -0
- package/lib/DefaultBundler.js +6 -1
- package/lib/MonolithicBundler.js +11 -3
- package/lib/bundleMerge.js +106 -37
- package/lib/bundlerConfig.js +52 -6
- package/lib/decorateLegacyGraph.js +24 -3
- package/lib/idealGraph.js +410 -55
- 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 +20 -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} +106 -45
- package/src/{decorateLegacyGraph.js → decorateLegacyGraph.ts} +26 -7
- package/src/{idealGraph.js → idealGraph.ts} +729 -137
- package/src/memoize.ts +32 -0
- package/src/stats.ts +97 -0
- package/tsconfig.json +30 -0
- package/tsconfig.tsbuildinfo +1 -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,
|
|
@@ -18,42 +17,50 @@ import type {
|
|
|
18
17
|
MutableBundleGraph,
|
|
19
18
|
Target,
|
|
20
19
|
PluginLogger,
|
|
20
|
+
BundleGraphTraversable,
|
|
21
|
+
TraversalActions,
|
|
21
22
|
} from '@atlaspack/types';
|
|
22
23
|
import {DefaultMap, globToRegex} from '@atlaspack/utils';
|
|
23
24
|
import invariant from 'assert';
|
|
24
25
|
import nullthrows from 'nullthrows';
|
|
25
26
|
|
|
26
|
-
import {findMergeCandidates} from './bundleMerge';
|
|
27
|
-
import type {
|
|
27
|
+
import {findMergeCandidates, MergeGroup} from './bundleMerge';
|
|
28
|
+
import type {
|
|
29
|
+
ResolvedBundlerConfig,
|
|
30
|
+
SharedBundleMergeCandidates,
|
|
31
|
+
AsyncBundleMerge,
|
|
32
|
+
} from './bundlerConfig';
|
|
33
|
+
import {Stats} from './stats';
|
|
28
34
|
|
|
29
35
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
30
36
|
type BundleRoot = Asset;
|
|
31
37
|
|
|
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
|
-
|
|
|
38
|
+
export type Bundle = {
|
|
39
|
+
uniqueKey: string | null | undefined;
|
|
40
|
+
assets: Set<Asset>;
|
|
41
|
+
internalizedAssets?: BitSet;
|
|
42
|
+
bundleBehavior?: BundleBehavior | null | undefined;
|
|
43
|
+
needsStableName: boolean;
|
|
44
|
+
mainEntryAsset: Asset | null | undefined;
|
|
45
|
+
bundleRoots: Set<Asset>;
|
|
46
|
+
size: number;
|
|
47
|
+
sourceBundles: Set<NodeId>;
|
|
48
|
+
target: Target;
|
|
49
|
+
env: Environment;
|
|
50
|
+
type: string;
|
|
51
|
+
manualSharedBundle: string | null | undefined; // for naming purposes;
|
|
52
|
+
};
|
|
46
53
|
|
|
47
54
|
export type DependencyBundleGraph = ContentGraph<
|
|
48
|
-
| {
|
|
49
|
-
value: Bundle
|
|
50
|
-
type: 'bundle'
|
|
51
|
-
|
|
52
|
-
| {
|
|
53
|
-
value: Dependency
|
|
54
|
-
type: 'dependency'
|
|
55
|
-
|
|
56
|
-
number
|
|
55
|
+
| {
|
|
56
|
+
value: Bundle;
|
|
57
|
+
type: 'bundle';
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
value: Dependency;
|
|
61
|
+
type: 'dependency';
|
|
62
|
+
},
|
|
63
|
+
number
|
|
57
64
|
>;
|
|
58
65
|
|
|
59
66
|
const dependencyPriorityEdges = {
|
|
@@ -61,7 +68,7 @@ const dependencyPriorityEdges = {
|
|
|
61
68
|
parallel: 2,
|
|
62
69
|
lazy: 3,
|
|
63
70
|
conditional: 4,
|
|
64
|
-
};
|
|
71
|
+
} as const;
|
|
65
72
|
|
|
66
73
|
export const idealBundleGraphEdges = Object.freeze({
|
|
67
74
|
default: 1,
|
|
@@ -70,20 +77,30 @@ export const idealBundleGraphEdges = Object.freeze({
|
|
|
70
77
|
|
|
71
78
|
export type IdealBundleGraph = Graph<
|
|
72
79
|
Bundle | 'root',
|
|
73
|
-
|
|
80
|
+
(typeof idealBundleGraphEdges)[keyof typeof idealBundleGraphEdges]
|
|
74
81
|
>;
|
|
75
82
|
|
|
76
83
|
// IdealGraph is the structure we will pass to decorate,
|
|
77
84
|
// which mutates the assetGraph into the bundleGraph we would
|
|
78
85
|
// 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
|
-
|
|
86
|
+
export type IdealGraph = {
|
|
87
|
+
assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>;
|
|
88
|
+
assets: Array<Asset>;
|
|
89
|
+
bundleGraph: IdealBundleGraph;
|
|
90
|
+
bundleGroupBundleIds: Set<NodeId>;
|
|
91
|
+
dependencyBundleGraph: DependencyBundleGraph;
|
|
92
|
+
manualAssetToBundle: Map<Asset, NodeId>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function isNonRootBundle(
|
|
96
|
+
bundle?: Bundle | 'root' | null,
|
|
97
|
+
message?: string,
|
|
98
|
+
): Bundle {
|
|
99
|
+
let existingBundle = nullthrows(bundle, message);
|
|
100
|
+
invariant(existingBundle !== 'root', "Bundle cannot be 'root'");
|
|
101
|
+
|
|
102
|
+
return existingBundle;
|
|
103
|
+
}
|
|
87
104
|
|
|
88
105
|
export function createIdealGraph(
|
|
89
106
|
assetGraph: MutableBundleGraph,
|
|
@@ -97,8 +114,9 @@ export function createIdealGraph(
|
|
|
97
114
|
let dependencyBundleGraph: DependencyBundleGraph = new ContentGraph();
|
|
98
115
|
let assetReference: DefaultMap<
|
|
99
116
|
Asset,
|
|
100
|
-
Array<[Dependency, Bundle]
|
|
117
|
+
Array<[Dependency, Bundle]>
|
|
101
118
|
> = new DefaultMap(() => []);
|
|
119
|
+
let stats = new Stats(config.projectRoot);
|
|
102
120
|
|
|
103
121
|
// A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their
|
|
104
122
|
// referencing Bundle. There are no actual BundleGroup nodes, just bundles that take on that role.
|
|
@@ -111,8 +129,9 @@ export function createIdealGraph(
|
|
|
111
129
|
};
|
|
112
130
|
// Graph that models bundleRoots, with parallel & async deps only to inform reachability
|
|
113
131
|
let bundleRootGraph: Graph<
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
// asset index
|
|
133
|
+
number,
|
|
134
|
+
(typeof bundleRootEdgeTypes)[keyof typeof bundleRootEdgeTypes]
|
|
116
135
|
> = new Graph();
|
|
117
136
|
let assetToBundleRootNodeId = new Map<BundleRoot, number>();
|
|
118
137
|
|
|
@@ -146,26 +165,47 @@ export function createIdealGraph(
|
|
|
146
165
|
bundleGroupBundleIds.add(nodeId);
|
|
147
166
|
}
|
|
148
167
|
|
|
149
|
-
let assets = [];
|
|
168
|
+
let assets: Array<Asset> = [];
|
|
150
169
|
let assetToIndex = new Map<Asset, number>();
|
|
151
170
|
|
|
152
|
-
function makeManualAssetToConfigLookup() {
|
|
153
|
-
|
|
154
|
-
|
|
171
|
+
function makeManualAssetToConfigLookup(): {
|
|
172
|
+
manualAssetToConfig: Map<
|
|
173
|
+
Asset,
|
|
174
|
+
ResolvedBundlerConfig['manualSharedBundles'][number]
|
|
175
|
+
>;
|
|
176
|
+
constantModuleToMSB: DefaultMap<
|
|
177
|
+
Asset,
|
|
178
|
+
Array<ResolvedBundlerConfig['manualSharedBundles'][number]>
|
|
179
|
+
>;
|
|
180
|
+
} {
|
|
181
|
+
let manualAssetToConfig = new Map<
|
|
182
|
+
Asset,
|
|
183
|
+
ResolvedBundlerConfig['manualSharedBundles'][number]
|
|
184
|
+
>();
|
|
185
|
+
let constantModuleToMSB = new DefaultMap<
|
|
186
|
+
Asset,
|
|
187
|
+
Array<ResolvedBundlerConfig['manualSharedBundles'][number]>
|
|
188
|
+
>(() => []);
|
|
155
189
|
|
|
156
190
|
if (config.manualSharedBundles.length === 0) {
|
|
157
191
|
return {manualAssetToConfig, constantModuleToMSB};
|
|
158
192
|
}
|
|
159
193
|
|
|
160
|
-
let parentsToConfig = new DefaultMap
|
|
194
|
+
let parentsToConfig = new DefaultMap<
|
|
195
|
+
string,
|
|
196
|
+
Array<ResolvedBundlerConfig['manualSharedBundles'][number]>
|
|
197
|
+
>(() => []);
|
|
161
198
|
|
|
162
199
|
for (let c of config.manualSharedBundles) {
|
|
163
200
|
if (c.root != null) {
|
|
164
201
|
parentsToConfig.get(path.join(config.projectRoot, c.root)).push(c);
|
|
165
202
|
}
|
|
166
203
|
}
|
|
167
|
-
let numParentsToFind = parentsToConfig.size;
|
|
168
|
-
let configToParentAsset = new Map
|
|
204
|
+
let numParentsToFind: number = parentsToConfig.size;
|
|
205
|
+
let configToParentAsset = new Map<
|
|
206
|
+
ResolvedBundlerConfig['manualSharedBundles'][number],
|
|
207
|
+
Asset
|
|
208
|
+
>();
|
|
169
209
|
|
|
170
210
|
assetGraph.traverse((node, _, actions) => {
|
|
171
211
|
if (node.type === 'asset' && parentsToConfig.has(node.value.filePath)) {
|
|
@@ -192,8 +232,10 @@ export function createIdealGraph(
|
|
|
192
232
|
continue;
|
|
193
233
|
}
|
|
194
234
|
|
|
195
|
-
let parentAsset = configToParentAsset.get(c);
|
|
196
|
-
let assetRegexes = c.assets.map((glob) =>
|
|
235
|
+
let parentAsset: Asset | undefined = configToParentAsset.get(c);
|
|
236
|
+
let assetRegexes: Array<RegExp> = c.assets.map((glob) =>
|
|
237
|
+
globToRegex(glob),
|
|
238
|
+
);
|
|
197
239
|
|
|
198
240
|
assetGraph.traverse((node, _, actions) => {
|
|
199
241
|
if (
|
|
@@ -204,17 +246,16 @@ export function createIdealGraph(
|
|
|
204
246
|
config.projectRoot,
|
|
205
247
|
node.value.filePath,
|
|
206
248
|
);
|
|
207
|
-
if (!assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
249
|
|
|
211
250
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
212
251
|
// and need to be assigned to all matching bundles
|
|
213
252
|
if (node.value.meta.isConstantModule === true) {
|
|
214
253
|
constantModuleToMSB.get(node.value).push(c);
|
|
215
254
|
}
|
|
216
|
-
|
|
217
|
-
|
|
255
|
+
|
|
256
|
+
if (assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
257
|
+
manualAssetToConfig.set(node.value, c);
|
|
258
|
+
}
|
|
218
259
|
}
|
|
219
260
|
|
|
220
261
|
if (
|
|
@@ -242,9 +283,19 @@ export function createIdealGraph(
|
|
|
242
283
|
makeManualAssetToConfigLookup();
|
|
243
284
|
let manualBundleToInternalizedAsset: DefaultMap<
|
|
244
285
|
NodeId,
|
|
245
|
-
Array<Asset
|
|
286
|
+
Array<Asset>
|
|
246
287
|
> = new DefaultMap(() => []);
|
|
247
288
|
|
|
289
|
+
let mergeSourceBundleLookup = new Map<string, NodeId>();
|
|
290
|
+
let mergeSourceBundleAssets: Set<string> = new Set(
|
|
291
|
+
config.sharedBundleMerge?.flatMap(
|
|
292
|
+
(c) =>
|
|
293
|
+
c.sourceBundles?.map((assetMatch: string) =>
|
|
294
|
+
path.join(config.projectRoot, assetMatch),
|
|
295
|
+
) ?? [],
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
|
|
248
299
|
/**
|
|
249
300
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
250
301
|
* for asset type changes, parallel, inline, and async or lazy dependencies,
|
|
@@ -252,7 +303,26 @@ export function createIdealGraph(
|
|
|
252
303
|
*/
|
|
253
304
|
assetGraph.traverse(
|
|
254
305
|
{
|
|
255
|
-
enter(
|
|
306
|
+
enter(
|
|
307
|
+
node:
|
|
308
|
+
| BundleGraphTraversable
|
|
309
|
+
| {
|
|
310
|
+
readonly type: 'dependency';
|
|
311
|
+
value: Dependency;
|
|
312
|
+
},
|
|
313
|
+
context:
|
|
314
|
+
| {
|
|
315
|
+
readonly type: 'asset';
|
|
316
|
+
value: Asset;
|
|
317
|
+
}
|
|
318
|
+
| null
|
|
319
|
+
| undefined
|
|
320
|
+
| {
|
|
321
|
+
readonly type: 'dependency';
|
|
322
|
+
value: Dependency;
|
|
323
|
+
},
|
|
324
|
+
actions: TraversalActions,
|
|
325
|
+
) {
|
|
256
326
|
if (node.type === 'asset') {
|
|
257
327
|
if (
|
|
258
328
|
context?.type === 'dependency' &&
|
|
@@ -288,12 +358,16 @@ export function createIdealGraph(
|
|
|
288
358
|
}
|
|
289
359
|
|
|
290
360
|
for (let childAsset of assets) {
|
|
291
|
-
let bundleId = bundles.get(
|
|
292
|
-
|
|
361
|
+
let bundleId: number | undefined | null = bundles.get(
|
|
362
|
+
childAsset.id,
|
|
363
|
+
);
|
|
364
|
+
let bundle: Bundle | 'root' | undefined;
|
|
293
365
|
|
|
294
366
|
// MSB Step 1: Match glob on filepath and type for any asset
|
|
295
|
-
let manualSharedBundleKey;
|
|
296
|
-
let manualSharedObject
|
|
367
|
+
let manualSharedBundleKey: string | undefined;
|
|
368
|
+
let manualSharedObject:
|
|
369
|
+
| ResolvedBundlerConfig['manualSharedBundles'][number]
|
|
370
|
+
| undefined = manualAssetToConfig.get(childAsset);
|
|
297
371
|
|
|
298
372
|
if (manualSharedObject) {
|
|
299
373
|
// MSB Step 2: Generate a key for which to look up this manual bundle with
|
|
@@ -312,7 +386,8 @@ export function createIdealGraph(
|
|
|
312
386
|
dependency.priority === 'lazy' ||
|
|
313
387
|
(getFeatureFlag('conditionalBundlingApi') &&
|
|
314
388
|
node.value.priority === 'conditional') ||
|
|
315
|
-
childAsset.bundleBehavior === 'isolated' // An isolated Dependency, or Bundle must contain all assets it needs to load.
|
|
389
|
+
childAsset.bundleBehavior === 'isolated' || // An isolated Dependency, or Bundle must contain all assets it needs to load.
|
|
390
|
+
childAsset.bundleBehavior === 'inlineIsolated'
|
|
316
391
|
) {
|
|
317
392
|
if (bundleId == null) {
|
|
318
393
|
let firstBundleGroup = nullthrows(
|
|
@@ -325,7 +400,9 @@ export function createIdealGraph(
|
|
|
325
400
|
dependency.bundleBehavior ?? childAsset.bundleBehavior,
|
|
326
401
|
needsStableName:
|
|
327
402
|
dependency.bundleBehavior === 'inline' ||
|
|
328
|
-
childAsset.bundleBehavior === 'inline'
|
|
403
|
+
childAsset.bundleBehavior === 'inline' ||
|
|
404
|
+
dependency.bundleBehavior === 'inlineIsolated' ||
|
|
405
|
+
childAsset.bundleBehavior === 'inlineIsolated'
|
|
329
406
|
? false
|
|
330
407
|
: dependency.isEntry || dependency.needsStableName,
|
|
331
408
|
target: firstBundleGroup.target,
|
|
@@ -335,6 +412,14 @@ export function createIdealGraph(
|
|
|
335
412
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
336
413
|
bundleGroupBundleIds.add(bundleId);
|
|
337
414
|
bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
|
|
415
|
+
// If this asset is relevant for merging then track it's source
|
|
416
|
+
// bundle id for later
|
|
417
|
+
if (mergeSourceBundleAssets.has(childAsset.filePath)) {
|
|
418
|
+
mergeSourceBundleLookup.set(
|
|
419
|
+
path.relative(config.projectRoot, childAsset.filePath),
|
|
420
|
+
bundleId,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
338
423
|
if (manualSharedObject) {
|
|
339
424
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
340
425
|
// since MSBs should not have main entry assets
|
|
@@ -349,7 +434,8 @@ export function createIdealGraph(
|
|
|
349
434
|
if (
|
|
350
435
|
// If this dependency requests isolated, but the bundle is not,
|
|
351
436
|
// make the bundle isolated for all uses.
|
|
352
|
-
dependency.bundleBehavior === 'isolated'
|
|
437
|
+
(dependency.bundleBehavior === 'isolated' ||
|
|
438
|
+
dependency.bundleBehavior === 'inlineIsolated') &&
|
|
353
439
|
bundle.bundleBehavior == null
|
|
354
440
|
) {
|
|
355
441
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
@@ -371,7 +457,9 @@ export function createIdealGraph(
|
|
|
371
457
|
type: 'bundle',
|
|
372
458
|
},
|
|
373
459
|
),
|
|
374
|
-
dependencyPriorityEdges[
|
|
460
|
+
dependencyPriorityEdges[
|
|
461
|
+
dependency.priority as keyof typeof dependencyPriorityEdges
|
|
462
|
+
],
|
|
375
463
|
);
|
|
376
464
|
|
|
377
465
|
if (
|
|
@@ -432,6 +520,7 @@ export function createIdealGraph(
|
|
|
432
520
|
needsStableName:
|
|
433
521
|
childAsset.bundleBehavior === 'inline' ||
|
|
434
522
|
dependency.bundleBehavior === 'inline' ||
|
|
523
|
+
dependency.bundleBehavior === 'inlineIsolated' ||
|
|
435
524
|
(dependency.priority === 'parallel' &&
|
|
436
525
|
!dependency.needsStableName)
|
|
437
526
|
? false
|
|
@@ -439,13 +528,15 @@ export function createIdealGraph(
|
|
|
439
528
|
});
|
|
440
529
|
bundleId = bundleGraph.addNode(bundle);
|
|
441
530
|
} else {
|
|
442
|
-
|
|
443
|
-
invariant(
|
|
531
|
+
let bundleNode = bundleGraph.getNode(bundleId);
|
|
532
|
+
invariant(bundleNode != null && bundleNode !== 'root');
|
|
444
533
|
|
|
534
|
+
bundle = bundleNode;
|
|
445
535
|
if (
|
|
446
536
|
// If this dependency requests isolated, but the bundle is not,
|
|
447
537
|
// make the bundle isolated for all uses.
|
|
448
|
-
dependency.bundleBehavior === 'isolated'
|
|
538
|
+
(dependency.bundleBehavior === 'isolated' ||
|
|
539
|
+
dependency.bundleBehavior === 'inlineIsolated') &&
|
|
449
540
|
bundle.bundleBehavior == null
|
|
450
541
|
) {
|
|
451
542
|
bundle.bundleBehavior = dependency.bundleBehavior;
|
|
@@ -515,7 +606,7 @@ export function createIdealGraph(
|
|
|
515
606
|
}
|
|
516
607
|
return node;
|
|
517
608
|
},
|
|
518
|
-
exit(node) {
|
|
609
|
+
exit(node: BundleGraphTraversable): undefined {
|
|
519
610
|
if (stack[stack.length - 1]?.[0] === node.value) {
|
|
520
611
|
stack.pop();
|
|
521
612
|
}
|
|
@@ -564,22 +655,22 @@ export function createIdealGraph(
|
|
|
564
655
|
|
|
565
656
|
// reachableRoots is an array of bit sets for each asset. Each bit set
|
|
566
657
|
// indicates which bundle roots are reachable from that asset synchronously.
|
|
567
|
-
let reachableRoots = [];
|
|
658
|
+
let reachableRoots: Array<BitSet> = [];
|
|
568
659
|
for (let i = 0; i < assets.length; i++) {
|
|
569
660
|
reachableRoots.push(new BitSet(bundleRootGraph.nodes.length));
|
|
570
661
|
}
|
|
571
662
|
|
|
572
663
|
// reachableAssets is the inverse mapping of reachableRoots. For each bundle root,
|
|
573
664
|
// it contains a bit set that indicates which assets are reachable from it.
|
|
574
|
-
let reachableAssets = [];
|
|
665
|
+
let reachableAssets: Array<BitSet> = [];
|
|
575
666
|
|
|
576
667
|
// ancestorAssets maps bundle roots to the set of all assets available to it at runtime,
|
|
577
668
|
// including in earlier parallel bundles. These are intersected through all paths to
|
|
578
669
|
// the bundle to ensure that the available assets are always present no matter in which
|
|
579
670
|
// order the bundles are loaded.
|
|
580
|
-
let ancestorAssets = [];
|
|
671
|
+
let ancestorAssets: Array<null | BitSet> = [];
|
|
581
672
|
|
|
582
|
-
let inlineConstantDeps = new DefaultMap(() => new Set());
|
|
673
|
+
let inlineConstantDeps = new DefaultMap<Asset, Set<Asset>>(() => new Set());
|
|
583
674
|
|
|
584
675
|
for (let [bundleRootId, assetId] of bundleRootGraph.nodes.entries()) {
|
|
585
676
|
let reachable = new BitSet(assets.length);
|
|
@@ -688,7 +779,10 @@ export function createIdealGraph(
|
|
|
688
779
|
// not true that a bundle's available assets = all assets of all the bundleGroups
|
|
689
780
|
// it belongs to. It's the intersection of those sets.
|
|
690
781
|
let available;
|
|
691
|
-
if (
|
|
782
|
+
if (
|
|
783
|
+
bundleRoot.bundleBehavior === 'isolated' ||
|
|
784
|
+
bundleRoot.bundleBehavior === 'inlineIsolated'
|
|
785
|
+
) {
|
|
692
786
|
available = new BitSet(assets.length);
|
|
693
787
|
} else {
|
|
694
788
|
available = nullthrows(ancestorAssets[nodeId]).clone();
|
|
@@ -772,7 +866,8 @@ export function createIdealGraph(
|
|
|
772
866
|
|
|
773
867
|
let parentRoots = bundleRootGraph.getNodeIdsConnectedTo(id, ALL_EDGE_TYPES);
|
|
774
868
|
let canDelete =
|
|
775
|
-
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated'
|
|
869
|
+
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'isolated' &&
|
|
870
|
+
getBundleFromBundleRoot(bundleRoot).bundleBehavior !== 'inlineIsolated';
|
|
776
871
|
if (parentRoots.length === 0) continue;
|
|
777
872
|
for (let parentId of parentRoots) {
|
|
778
873
|
if (parentId === rootNodeId) {
|
|
@@ -820,8 +915,10 @@ export function createIdealGraph(
|
|
|
820
915
|
let reachableNonEntries = new BitSet(assets.length);
|
|
821
916
|
let reachableIntersection = new BitSet(assets.length);
|
|
822
917
|
for (let i = 0; i < assets.length; i++) {
|
|
823
|
-
let asset = assets[i];
|
|
824
|
-
let manualSharedObject
|
|
918
|
+
let asset: Asset = assets[i];
|
|
919
|
+
let manualSharedObject:
|
|
920
|
+
| ResolvedBundlerConfig['manualSharedBundles'][number]
|
|
921
|
+
| undefined = manualAssetToConfig.get(asset);
|
|
825
922
|
|
|
826
923
|
if (bundleRoots.has(asset) && inlineConstantDeps.get(asset).size > 0) {
|
|
827
924
|
let entryBundleId = nullthrows(bundleRoots.get(asset))[0];
|
|
@@ -849,7 +946,8 @@ export function createIdealGraph(
|
|
|
849
946
|
!a.isBundleSplittable ||
|
|
850
947
|
(bundleRoots.get(a) &&
|
|
851
948
|
(getBundleFromBundleRoot(a).needsStableName ||
|
|
852
|
-
getBundleFromBundleRoot(a).bundleBehavior === 'isolated'
|
|
949
|
+
getBundleFromBundleRoot(a).bundleBehavior === 'isolated' ||
|
|
950
|
+
getBundleFromBundleRoot(a).bundleBehavior === 'inlineIsolated'))
|
|
853
951
|
) {
|
|
854
952
|
// Add asset to non-splittable bundles.
|
|
855
953
|
addAssetToBundleRoot(asset, a);
|
|
@@ -864,10 +962,11 @@ export function createIdealGraph(
|
|
|
864
962
|
|
|
865
963
|
// If we encounter a "manual" asset, draw an edge from reachable to its MSB
|
|
866
964
|
if (manualSharedObject && !reachable.empty()) {
|
|
867
|
-
let bundle;
|
|
868
|
-
let bundleId;
|
|
869
|
-
let manualSharedBundleKey =
|
|
870
|
-
|
|
965
|
+
let bundle: Bundle | undefined;
|
|
966
|
+
let bundleId: NodeId | undefined;
|
|
967
|
+
let manualSharedBundleKey: string =
|
|
968
|
+
manualSharedObject.name + ',' + asset.type;
|
|
969
|
+
let sourceBundles: Array<NodeId> = [];
|
|
871
970
|
reachable.forEach((id) => {
|
|
872
971
|
sourceBundles.push(nullthrows(bundleRoots.get(assets[id]))[0]);
|
|
873
972
|
});
|
|
@@ -891,12 +990,14 @@ export function createIdealGraph(
|
|
|
891
990
|
manualSharedMap.set(manualSharedBundleKey, bundleId);
|
|
892
991
|
} else {
|
|
893
992
|
bundleId = nullthrows(manualSharedMap.get(manualSharedBundleKey));
|
|
894
|
-
|
|
993
|
+
let bundleNode = nullthrows(bundleGraph.getNode(bundleId));
|
|
895
994
|
invariant(
|
|
896
|
-
|
|
995
|
+
bundleNode != null && bundleNode !== 'root',
|
|
897
996
|
'We tried to use the root incorrectly',
|
|
898
997
|
);
|
|
899
998
|
|
|
999
|
+
bundle = bundleNode;
|
|
1000
|
+
|
|
900
1001
|
if (!bundle.assets.has(asset)) {
|
|
901
1002
|
bundle.assets.add(asset);
|
|
902
1003
|
bundle.size += asset.stats.size;
|
|
@@ -982,7 +1083,7 @@ export function createIdealGraph(
|
|
|
982
1083
|
});
|
|
983
1084
|
}
|
|
984
1085
|
|
|
985
|
-
let reachableArray = [];
|
|
1086
|
+
let reachableArray: Array<Asset> = [];
|
|
986
1087
|
reachable.forEach((id) => {
|
|
987
1088
|
reachableArray.push(assets[id]);
|
|
988
1089
|
});
|
|
@@ -992,12 +1093,13 @@ export function createIdealGraph(
|
|
|
992
1093
|
config.disableSharedBundles === false &&
|
|
993
1094
|
reachableArray.length > config.minBundles
|
|
994
1095
|
) {
|
|
995
|
-
let sourceBundles = reachableArray.map(
|
|
1096
|
+
let sourceBundles: Array<NodeId> = reachableArray.map(
|
|
996
1097
|
(a) => nullthrows(bundleRoots.get(a))[0],
|
|
997
1098
|
);
|
|
998
|
-
let key =
|
|
999
|
-
|
|
1000
|
-
let
|
|
1099
|
+
let key: string =
|
|
1100
|
+
reachableArray.map((a) => a.id).join(',') + '.' + asset.type;
|
|
1101
|
+
let bundleId: NodeId | undefined = bundles.get(key);
|
|
1102
|
+
let bundle: Bundle | undefined;
|
|
1001
1103
|
if (bundleId == null) {
|
|
1002
1104
|
let firstSourceBundle = nullthrows(
|
|
1003
1105
|
bundleGraph.getNode(sourceBundles[0]),
|
|
@@ -1028,8 +1130,9 @@ export function createIdealGraph(
|
|
|
1028
1130
|
bundleId = bundleGraph.addNode(bundle);
|
|
1029
1131
|
bundles.set(key, bundleId);
|
|
1030
1132
|
} else {
|
|
1031
|
-
|
|
1032
|
-
invariant(
|
|
1133
|
+
let bundleNode = nullthrows(bundleGraph.getNode(bundleId));
|
|
1134
|
+
invariant(bundleNode !== 'root');
|
|
1135
|
+
bundle = bundleNode;
|
|
1033
1136
|
}
|
|
1034
1137
|
bundle.assets.add(asset);
|
|
1035
1138
|
bundle.size += asset.stats.size;
|
|
@@ -1057,25 +1160,31 @@ export function createIdealGraph(
|
|
|
1057
1160
|
}
|
|
1058
1161
|
|
|
1059
1162
|
let manualSharedBundleIds = new Set([...manualSharedMap.values()]);
|
|
1163
|
+
let modifiedSourceBundles = new Set<Bundle>();
|
|
1164
|
+
|
|
1060
1165
|
// Step split manual shared bundles for those that have the "split" property set
|
|
1061
|
-
let remainderMap = new DefaultMap(() => []);
|
|
1166
|
+
let remainderMap = new DefaultMap<number, Array<Asset>>(() => []);
|
|
1062
1167
|
for (let id of manualSharedMap.values()) {
|
|
1063
1168
|
let manualBundle = bundleGraph.getNode(id);
|
|
1064
1169
|
invariant(manualBundle !== 'root' && manualBundle != null);
|
|
1065
1170
|
|
|
1066
1171
|
if (manualBundle.sourceBundles.size > 0) {
|
|
1067
|
-
let
|
|
1172
|
+
let firstSourceBundleNode: Bundle | 'root' = nullthrows(
|
|
1068
1173
|
bundleGraph.getNode([...manualBundle.sourceBundles][0]),
|
|
1069
1174
|
);
|
|
1070
|
-
invariant(
|
|
1071
|
-
let
|
|
1072
|
-
|
|
1175
|
+
invariant(firstSourceBundleNode !== 'root');
|
|
1176
|
+
let firstSourceBundle = firstSourceBundleNode;
|
|
1177
|
+
|
|
1178
|
+
let firstAsset: Asset = [...manualBundle.assets][0];
|
|
1179
|
+
let manualSharedObject:
|
|
1180
|
+
| ResolvedBundlerConfig['manualSharedBundles'][number]
|
|
1181
|
+
| undefined = manualAssetToConfig.get(firstAsset);
|
|
1073
1182
|
invariant(manualSharedObject != null);
|
|
1074
|
-
let modNum =
|
|
1183
|
+
let modNum: number | undefined =
|
|
1184
|
+
manualAssetToConfig.get(firstAsset)?.split;
|
|
1075
1185
|
if (modNum != null) {
|
|
1076
1186
|
for (let a of [...manualBundle.assets]) {
|
|
1077
1187
|
let numRep = getBigIntFromContentKey(a.id);
|
|
1078
|
-
// $FlowFixMe Flow doesn't know about BigInt
|
|
1079
1188
|
let r = Number(numRep % BigInt(modNum));
|
|
1080
1189
|
|
|
1081
1190
|
remainderMap.get(r).push(a);
|
|
@@ -1129,10 +1238,87 @@ export function createIdealGraph(
|
|
|
1129
1238
|
}
|
|
1130
1239
|
}
|
|
1131
1240
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1241
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1242
|
+
// Merge webpack chunk name bundles
|
|
1243
|
+
let chunkNameBundles = new DefaultMap<string, Set<NodeId>>(() => new Set());
|
|
1244
|
+
for (let [nodeId, node] of dependencyBundleGraph.nodes.entries()) {
|
|
1245
|
+
// meta.chunkName is set by the Rust transformer, so we just need to find
|
|
1246
|
+
// bundles that have a chunkName set.
|
|
1247
|
+
if (
|
|
1248
|
+
!node ||
|
|
1249
|
+
node.type !== 'dependency' ||
|
|
1250
|
+
typeof node.value.meta.chunkName !== 'string'
|
|
1251
|
+
) {
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
let connectedBundles = dependencyBundleGraph.getNodeIdsConnectedFrom(
|
|
1256
|
+
nodeId,
|
|
1257
|
+
dependencyPriorityEdges[node.value.priority],
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
if (connectedBundles.length === 0) {
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
invariant(
|
|
1265
|
+
connectedBundles.length === 1,
|
|
1266
|
+
'Expected webpackChunkName dependency to be connected to no more than one bundle',
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
let bundleId = connectedBundles[0];
|
|
1270
|
+
let bundleNode = dependencyBundleGraph.getNode(bundleId);
|
|
1271
|
+
invariant(bundleNode != null && bundleNode.type === 'bundle');
|
|
1272
|
+
|
|
1273
|
+
// If a bundle does not have a main entry asset, it's somehow just a
|
|
1274
|
+
// shared bundle, and will be merged/deleted by other means.
|
|
1275
|
+
if (bundleNode.value.mainEntryAsset == null) {
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
let bundleNodeId = null;
|
|
1280
|
+
let mainEntryAssetId = bundleNode.value.mainEntryAsset?.id;
|
|
1281
|
+
|
|
1282
|
+
if (mainEntryAssetId != null) {
|
|
1283
|
+
bundleNodeId = bundles.get(mainEntryAssetId);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
if (bundleNodeId == null) {
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
chunkNameBundles
|
|
1291
|
+
.get(node.value.meta.chunkName)
|
|
1292
|
+
// DependencyBundleGraph uses content keys as node ids, so we can use that
|
|
1293
|
+
// to get the bundle id.
|
|
1294
|
+
.add(bundleNodeId);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
for (let [chunkName, bundleIds] of chunkNameBundles.entries()) {
|
|
1298
|
+
// The `[request]` placeholder is not yet supported
|
|
1299
|
+
if (
|
|
1300
|
+
bundleIds.size <= 1 ||
|
|
1301
|
+
(typeof chunkName === 'string' && chunkName.includes('[request]'))
|
|
1302
|
+
) {
|
|
1303
|
+
continue; // Nothing to merge
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// Merge all bundles with the same chunk name into the first one.
|
|
1307
|
+
let [firstBundleId, ...rest] = Array.from(bundleIds);
|
|
1308
|
+
for (let bundleId of rest) {
|
|
1309
|
+
mergeBundles(firstBundleId, bundleId, 'webpack-chunk-name');
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// Step merge async bundles that meet the configured params
|
|
1315
|
+
if (config.asyncBundleMerge) {
|
|
1316
|
+
mergeAsyncBundles(config.asyncBundleMerge);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Step merge shared bundles that meet the configured params
|
|
1320
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
1321
|
+
mergeSharedBundles(config.sharedBundleMerge);
|
|
1136
1322
|
}
|
|
1137
1323
|
|
|
1138
1324
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
@@ -1151,7 +1337,6 @@ export function createIdealGraph(
|
|
|
1151
1337
|
}
|
|
1152
1338
|
|
|
1153
1339
|
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1154
|
-
let modifiedSourceBundles = new Set();
|
|
1155
1340
|
|
|
1156
1341
|
if (config.disableSharedBundles === false) {
|
|
1157
1342
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
@@ -1165,7 +1350,13 @@ export function createIdealGraph(
|
|
|
1165
1350
|
let numBundlesContributingToPRL = bundleIdsInGroup.reduce((count, b) => {
|
|
1166
1351
|
let bundle = nullthrows(bundleGraph.getNode(b));
|
|
1167
1352
|
invariant(bundle !== 'root');
|
|
1168
|
-
return
|
|
1353
|
+
return (
|
|
1354
|
+
count +
|
|
1355
|
+
Number(
|
|
1356
|
+
bundle.bundleBehavior !== 'inline' &&
|
|
1357
|
+
bundle.bundleBehavior !== 'inlineIsolated',
|
|
1358
|
+
)
|
|
1359
|
+
);
|
|
1169
1360
|
}, 0);
|
|
1170
1361
|
|
|
1171
1362
|
if (numBundlesContributingToPRL > config.maxParallelRequests) {
|
|
@@ -1200,6 +1391,7 @@ export function createIdealGraph(
|
|
|
1200
1391
|
numBundlesContributingToPRL > config.maxParallelRequests
|
|
1201
1392
|
) {
|
|
1202
1393
|
let bundleTuple = sharedBundlesInGroup.pop();
|
|
1394
|
+
if (!bundleTuple) break;
|
|
1203
1395
|
let bundleToRemove = bundleTuple.bundle;
|
|
1204
1396
|
let bundleIdToRemove = bundleTuple.id;
|
|
1205
1397
|
//TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles
|
|
@@ -1257,51 +1449,205 @@ export function createIdealGraph(
|
|
|
1257
1449
|
}
|
|
1258
1450
|
|
|
1259
1451
|
function mergeBundles(
|
|
1260
|
-
bundleGraph: IdealBundleGraph,
|
|
1261
1452
|
bundleToKeepId: NodeId,
|
|
1262
1453
|
bundleToRemoveId: NodeId,
|
|
1263
|
-
|
|
1454
|
+
reason: string,
|
|
1264
1455
|
) {
|
|
1265
|
-
|
|
1266
|
-
let
|
|
1267
|
-
|
|
1456
|
+
stats.trackMerge(bundleToKeepId, bundleToRemoveId, reason);
|
|
1457
|
+
let bundleToKeep = isNonRootBundle(
|
|
1458
|
+
bundleGraph.getNode(bundleToKeepId),
|
|
1459
|
+
`Bundle ${bundleToKeepId} not found`,
|
|
1460
|
+
);
|
|
1461
|
+
let bundleToRemove = isNonRootBundle(
|
|
1462
|
+
bundleGraph.getNode(bundleToRemoveId),
|
|
1463
|
+
`Bundle ${bundleToRemoveId} not found`,
|
|
1464
|
+
);
|
|
1465
|
+
modifiedSourceBundles.add(bundleToKeep);
|
|
1268
1466
|
for (let asset of bundleToRemove.assets) {
|
|
1269
1467
|
bundleToKeep.assets.add(asset);
|
|
1270
1468
|
bundleToKeep.size += asset.stats.size;
|
|
1271
1469
|
|
|
1272
1470
|
let newAssetReference = assetReference
|
|
1273
1471
|
.get(asset)
|
|
1274
|
-
.map(([dep, bundle]) =>
|
|
1472
|
+
.map(([dep, bundle]): [Dependency, Bundle] =>
|
|
1275
1473
|
bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
|
|
1276
1474
|
);
|
|
1277
1475
|
|
|
1278
1476
|
assetReference.set(asset, newAssetReference);
|
|
1279
1477
|
}
|
|
1280
1478
|
|
|
1479
|
+
// Merge any internalized assets
|
|
1480
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1481
|
+
if (bundleToKeep.internalizedAssets != null) {
|
|
1482
|
+
if (bundleToRemove.internalizedAssets != null) {
|
|
1483
|
+
bundleToKeep.internalizedAssets.intersect(
|
|
1484
|
+
bundleToRemove.internalizedAssets,
|
|
1485
|
+
);
|
|
1486
|
+
} else {
|
|
1487
|
+
bundleToKeep.internalizedAssets.clear();
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
} else {
|
|
1491
|
+
invariant(
|
|
1492
|
+
bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
|
|
1493
|
+
'All shared bundles should have internalized assets',
|
|
1494
|
+
);
|
|
1495
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Merge and clean up source bundles
|
|
1281
1499
|
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1282
1500
|
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1283
1501
|
continue;
|
|
1284
1502
|
}
|
|
1285
1503
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1504
|
+
if (sourceBundleId !== bundleToKeepId) {
|
|
1505
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
1506
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1507
|
+
}
|
|
1288
1508
|
}
|
|
1289
1509
|
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1510
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1511
|
+
bundleToKeep.sourceBundles.delete(bundleToRemoveId);
|
|
1512
|
+
|
|
1513
|
+
for (let bundle of bundleGraph.getNodeIdsConnectedFrom(
|
|
1514
|
+
bundleToRemoveId,
|
|
1515
|
+
)) {
|
|
1516
|
+
let bundleNode = nullthrows(bundleGraph.getNode(bundle));
|
|
1517
|
+
if (bundleNode === 'root') {
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// If the bundle is a source bundle, add it to the bundle to keep
|
|
1522
|
+
if (bundleNode.sourceBundles.has(bundleToRemoveId)) {
|
|
1523
|
+
bundleNode.sourceBundles.delete(bundleToRemoveId);
|
|
1524
|
+
if (bundle !== bundleToKeepId) {
|
|
1525
|
+
bundleNode.sourceBundles.add(bundleToKeepId);
|
|
1526
|
+
bundleGraph.addEdge(bundleToKeepId, bundle);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// Merge bundle roots
|
|
1532
|
+
for (let bundleRoot of bundleToRemove.bundleRoots) {
|
|
1533
|
+
bundleToKeep.bundleRoots.add(bundleRoot);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
if (bundleToRemove.mainEntryAsset != null) {
|
|
1537
|
+
invariant(bundleToKeep.mainEntryAsset != null);
|
|
1538
|
+
|
|
1539
|
+
// Merge the bundles in bundle group
|
|
1540
|
+
let bundlesInRemoveBundleGroup =
|
|
1541
|
+
getBundlesForBundleGroup(bundleToRemoveId);
|
|
1542
|
+
|
|
1543
|
+
let removedBundleSharedBundles = new Set<NodeId>();
|
|
1544
|
+
for (let bundleIdInGroup of bundlesInRemoveBundleGroup) {
|
|
1545
|
+
if (bundleIdInGroup === bundleToRemoveId) {
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
bundleGraph.addEdge(bundleToKeepId, bundleIdInGroup);
|
|
1549
|
+
removedBundleSharedBundles.add(bundleIdInGroup);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (getFeatureFlag('removeRedundantSharedBundles')) {
|
|
1553
|
+
// Merge any shared bundles that now have the same source bundles due to
|
|
1554
|
+
// the current bundle merge
|
|
1555
|
+
let sharedBundles = new DefaultMap<string, Array<NodeId>>(() => []);
|
|
1556
|
+
for (let bundleId of removedBundleSharedBundles) {
|
|
1557
|
+
let bundleNode = nullthrows(bundleGraph.getNode(bundleId));
|
|
1558
|
+
invariant(bundleNode !== 'root');
|
|
1559
|
+
if (
|
|
1560
|
+
bundleNode.mainEntryAsset != null ||
|
|
1561
|
+
bundleNode.manualSharedBundle != null
|
|
1562
|
+
) {
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
let key =
|
|
1567
|
+
Array.from(bundleNode.sourceBundles)
|
|
1568
|
+
.filter((sourceBundle) => sourceBundle !== bundleToRemoveId)
|
|
1569
|
+
.sort()
|
|
1570
|
+
.join(',') +
|
|
1571
|
+
'.' +
|
|
1572
|
+
bundleNode.type;
|
|
1573
|
+
|
|
1574
|
+
sharedBundles.get(key).push(bundleId);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
for (let sharedBundlesToMerge of sharedBundles.values()) {
|
|
1578
|
+
if (sharedBundlesToMerge.length > 1) {
|
|
1579
|
+
let [firstBundleId, ...rest] = sharedBundlesToMerge;
|
|
1580
|
+
for (let bundleId of rest) {
|
|
1581
|
+
mergeBundles(firstBundleId, bundleId, 'redundant-shared');
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Remove old bundle group
|
|
1588
|
+
bundleGroupBundleIds.delete(bundleToRemoveId);
|
|
1589
|
+
|
|
1590
|
+
// Clean up bundle roots
|
|
1591
|
+
let bundleRootToRemoveNodeId = nullthrows(
|
|
1592
|
+
assetToBundleRootNodeId.get(
|
|
1593
|
+
nullthrows(bundleToRemove.mainEntryAsset),
|
|
1594
|
+
),
|
|
1295
1595
|
);
|
|
1296
|
-
|
|
1297
|
-
|
|
1596
|
+
let bundleRootToKeepNodeId = nullthrows(
|
|
1597
|
+
assetToBundleRootNodeId.get(nullthrows(bundleToKeep.mainEntryAsset)),
|
|
1598
|
+
);
|
|
1599
|
+
|
|
1600
|
+
for (let nodeId of bundleRootGraph.getNodeIdsConnectedTo(
|
|
1601
|
+
bundleRootToRemoveNodeId,
|
|
1602
|
+
)) {
|
|
1603
|
+
bundleRootGraph.addEdge(nodeId, bundleRootToKeepNodeId);
|
|
1604
|
+
bundleRootGraph.removeEdge(nodeId, bundleRootToRemoveNodeId);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
for (let nodeId of bundleRootGraph.getNodeIdsConnectedFrom(
|
|
1608
|
+
bundleRootToRemoveNodeId,
|
|
1609
|
+
)) {
|
|
1610
|
+
bundleRootGraph.addEdge(bundleRootToKeepNodeId, nodeId);
|
|
1611
|
+
bundleRootGraph.removeEdge(bundleRootToRemoveNodeId, nodeId);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
bundleRoots.set(nullthrows(bundleToRemove.mainEntryAsset), [
|
|
1615
|
+
bundleToKeepId,
|
|
1616
|
+
bundleToKeepId,
|
|
1617
|
+
]);
|
|
1618
|
+
|
|
1619
|
+
// Merge dependency bundle graph
|
|
1620
|
+
for (let dependencyNodeId of dependencyBundleGraph.getNodeIdsConnectedTo(
|
|
1621
|
+
dependencyBundleGraph.getNodeIdByContentKey(String(bundleToRemoveId)),
|
|
1622
|
+
ALL_EDGE_TYPES,
|
|
1623
|
+
)) {
|
|
1624
|
+
let dependencyNode = nullthrows(
|
|
1625
|
+
dependencyBundleGraph.getNode(dependencyNodeId),
|
|
1626
|
+
);
|
|
1627
|
+
invariant(dependencyNode.type === 'dependency');
|
|
1628
|
+
|
|
1629
|
+
// Add dependency to the bundle to keep
|
|
1630
|
+
dependencyBundleGraph.addEdge(
|
|
1631
|
+
dependencyNodeId,
|
|
1632
|
+
dependencyBundleGraph.getNodeIdByContentKey(String(bundleToKeepId)),
|
|
1633
|
+
dependencyPriorityEdges[dependencyNode.value.priority],
|
|
1634
|
+
);
|
|
1635
|
+
// Remove dependency from the bundle to remove
|
|
1636
|
+
dependencyBundleGraph.removeEdge(
|
|
1637
|
+
dependencyNodeId,
|
|
1638
|
+
dependencyBundleGraph.getNodeIdByContentKey(
|
|
1639
|
+
String(bundleToRemoveId),
|
|
1640
|
+
),
|
|
1641
|
+
dependencyPriorityEdges[dependencyNode.value.priority],
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1298
1644
|
}
|
|
1299
1645
|
}
|
|
1300
1646
|
|
|
1301
1647
|
bundleGraph.removeNode(bundleToRemoveId);
|
|
1302
1648
|
}
|
|
1303
1649
|
|
|
1304
|
-
function
|
|
1650
|
+
function mergeSharedBundles(mergeConfig: SharedBundleMergeCandidates) {
|
|
1305
1651
|
// Find all shared bundles
|
|
1306
1652
|
let sharedBundles = new Set<NodeId>();
|
|
1307
1653
|
bundleGraph.traverse((nodeId) => {
|
|
@@ -1331,22 +1677,188 @@ export function createIdealGraph(
|
|
|
1331
1677
|
let clusters = findMergeCandidates(
|
|
1332
1678
|
bundleGraph,
|
|
1333
1679
|
Array.from(sharedBundles),
|
|
1334
|
-
|
|
1680
|
+
mergeConfig.map(
|
|
1681
|
+
(config): MergeGroup => ({
|
|
1682
|
+
...config,
|
|
1683
|
+
sourceBundles: config.sourceBundles?.map((assetMatch: string) => {
|
|
1684
|
+
let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
|
|
1685
|
+
|
|
1686
|
+
if (sourceBundleNodeId == null) {
|
|
1687
|
+
throw new Error(
|
|
1688
|
+
`Source bundle ${assetMatch} not found in merge source bundle lookup`,
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
return sourceBundleNodeId;
|
|
1693
|
+
}),
|
|
1694
|
+
}),
|
|
1695
|
+
),
|
|
1335
1696
|
);
|
|
1336
1697
|
|
|
1698
|
+
let mergedBundles = new Set();
|
|
1699
|
+
|
|
1337
1700
|
for (let cluster of clusters) {
|
|
1338
1701
|
let [mergeTarget, ...rest] = cluster;
|
|
1339
1702
|
|
|
1340
1703
|
for (let bundleIdToMerge of rest) {
|
|
1341
|
-
mergeBundles(
|
|
1704
|
+
mergeBundles(mergeTarget, bundleIdToMerge, 'shared-merge');
|
|
1342
1705
|
}
|
|
1706
|
+
|
|
1707
|
+
mergedBundles.add(mergeTarget);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
if (getFeatureFlag('supportWebpackChunkName')) {
|
|
1711
|
+
return mergedBundles;
|
|
1343
1712
|
}
|
|
1344
1713
|
}
|
|
1345
1714
|
|
|
1346
|
-
function
|
|
1715
|
+
function mergeAsyncBundles({
|
|
1716
|
+
bundleSize,
|
|
1717
|
+
maxOverfetchSize,
|
|
1718
|
+
ignore,
|
|
1719
|
+
}: AsyncBundleMerge) {
|
|
1720
|
+
let mergeCandidates = [];
|
|
1721
|
+
let ignoreRegexes = ignore?.map((glob) => globToRegex(glob)) ?? [];
|
|
1722
|
+
|
|
1723
|
+
let isIgnored = (bundle: Bundle) => {
|
|
1724
|
+
if (!bundle.mainEntryAsset) {
|
|
1725
|
+
return false;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
let mainEntryFilePath = path.relative(
|
|
1729
|
+
config.projectRoot,
|
|
1730
|
+
nullthrows(bundle.mainEntryAsset).filePath,
|
|
1731
|
+
);
|
|
1732
|
+
|
|
1733
|
+
return ignoreRegexes.some((regex) => regex.test(mainEntryFilePath));
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1736
|
+
for (let [_bundleRootAsset, [bundleRootBundleId]] of bundleRoots) {
|
|
1737
|
+
let bundleRootBundle = nullthrows(
|
|
1738
|
+
bundleGraph.getNode(bundleRootBundleId),
|
|
1739
|
+
);
|
|
1740
|
+
invariant(bundleRootBundle !== 'root');
|
|
1741
|
+
|
|
1742
|
+
if (
|
|
1743
|
+
bundleRootBundle.type === 'js' &&
|
|
1744
|
+
bundleRootBundle.bundleBehavior !== 'inline' &&
|
|
1745
|
+
bundleRootBundle.bundleBehavior !== 'inlineIsolated' &&
|
|
1746
|
+
bundleRootBundle.size <= bundleSize &&
|
|
1747
|
+
!isIgnored(bundleRootBundle)
|
|
1748
|
+
) {
|
|
1749
|
+
mergeCandidates.push(bundleRootBundleId);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
let candidates = [];
|
|
1754
|
+
for (let i = 0; i < mergeCandidates.length; i++) {
|
|
1755
|
+
for (let j = i + 1; j < mergeCandidates.length; j++) {
|
|
1756
|
+
let a = mergeCandidates[i];
|
|
1757
|
+
let b = mergeCandidates[j];
|
|
1758
|
+
if (a === b) continue; // Skip self-comparison
|
|
1759
|
+
|
|
1760
|
+
candidates.push(scoreAsyncMerge(a, b, maxOverfetchSize));
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
let sortByScore = (
|
|
1765
|
+
a: AsyncBundleMergeCandidate,
|
|
1766
|
+
b: AsyncBundleMergeCandidate,
|
|
1767
|
+
) => {
|
|
1768
|
+
let diff = a.score - b.score;
|
|
1769
|
+
if (diff > 0) {
|
|
1770
|
+
return 1;
|
|
1771
|
+
} else if (diff < 0) {
|
|
1772
|
+
return -1;
|
|
1773
|
+
}
|
|
1774
|
+
return 0;
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
candidates = candidates
|
|
1778
|
+
.filter(
|
|
1779
|
+
({overfetchSize, score}) =>
|
|
1780
|
+
overfetchSize <= maxOverfetchSize && score > 0,
|
|
1781
|
+
)
|
|
1782
|
+
.sort(sortByScore);
|
|
1783
|
+
|
|
1784
|
+
// Tracks the bundles that have been merged
|
|
1785
|
+
let merged = new Set<NodeId>();
|
|
1786
|
+
// Tracks the deleted bundles to the bundle they were merged into.
|
|
1787
|
+
let mergeRemap = new Map<NodeId, NodeId>();
|
|
1788
|
+
// Tracks the bundles that have been rescored and added back into the
|
|
1789
|
+
// candidates.
|
|
1790
|
+
let rescored = new DefaultMap<NodeId, Set<NodeId>>(() => new Set());
|
|
1791
|
+
|
|
1792
|
+
do {
|
|
1793
|
+
let [a, b] = nullthrows(candidates.pop()).bundleIds;
|
|
1794
|
+
|
|
1795
|
+
if (
|
|
1796
|
+
bundleGraph.hasNode(a) &&
|
|
1797
|
+
bundleGraph.hasNode(b) &&
|
|
1798
|
+
((!merged.has(a) && !merged.has(b)) || rescored.get(a).has(b))
|
|
1799
|
+
) {
|
|
1800
|
+
mergeRemap.set(b, a);
|
|
1801
|
+
merged.add(a);
|
|
1802
|
+
rescored.get(a).clear();
|
|
1803
|
+
|
|
1804
|
+
mergeBundles(a, b, 'async-merge');
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// One or both of the bundles have been previously merged, so we'll
|
|
1809
|
+
// rescore and add the result back into the list of candidates.
|
|
1810
|
+
let getMergedBundleId = (bundleId: NodeId): NodeId | undefined => {
|
|
1811
|
+
let seen = new Set<NodeId>();
|
|
1812
|
+
while (!bundleGraph.hasNode(bundleId) && !seen.has(bundleId)) {
|
|
1813
|
+
seen.add(bundleId);
|
|
1814
|
+
bundleId = nullthrows(mergeRemap.get(bundleId));
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
if (!bundleGraph.hasNode(bundleId)) {
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
return bundleId;
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
// Map a and b to their merged bundle ids if they've already been merged
|
|
1825
|
+
let currentA = getMergedBundleId(a);
|
|
1826
|
+
let currentB = getMergedBundleId(b);
|
|
1827
|
+
|
|
1828
|
+
if (
|
|
1829
|
+
!currentA ||
|
|
1830
|
+
!currentB ||
|
|
1831
|
+
// Bundles are already merged
|
|
1832
|
+
currentA === currentB
|
|
1833
|
+
) {
|
|
1834
|
+
// This combiniation is not valid, so we skip it.
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
let candidate = scoreAsyncMerge(currentA, currentB, maxOverfetchSize);
|
|
1839
|
+
|
|
1840
|
+
if (candidate.overfetchSize <= maxOverfetchSize && candidate.score > 0) {
|
|
1841
|
+
sortedArray.add(candidates, candidate, sortByScore);
|
|
1842
|
+
rescored.get(currentA).add(currentB);
|
|
1843
|
+
}
|
|
1844
|
+
} while (candidates.length > 0);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function getBundle(bundleId: NodeId): Bundle {
|
|
1848
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
1849
|
+
if (bundle === 'root') {
|
|
1850
|
+
throw new Error(`Cannot access root bundle`);
|
|
1851
|
+
}
|
|
1852
|
+
if (bundle == null) {
|
|
1853
|
+
throw new Error(`Bundle ${bundleId} not found in bundle graph`);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
return bundle;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function getBigIntFromContentKey(contentKey: string) {
|
|
1347
1860
|
let b = Buffer.alloc(64);
|
|
1348
1861
|
b.write(contentKey);
|
|
1349
|
-
// $FlowFixMe Flow doesn't have BigInt types in this version
|
|
1350
1862
|
return b.readBigInt64BE();
|
|
1351
1863
|
}
|
|
1352
1864
|
// Fix asset order in source bundles as they are likely now incorrect after shared bundle deletion
|
|
@@ -1373,14 +1885,86 @@ export function createIdealGraph(
|
|
|
1373
1885
|
bundleRootGraph.removeNode(bundleRootId);
|
|
1374
1886
|
}
|
|
1375
1887
|
}
|
|
1376
|
-
function getBundlesForBundleGroup(bundleGroupId) {
|
|
1377
|
-
let bundlesInABundleGroup = [];
|
|
1888
|
+
function getBundlesForBundleGroup(bundleGroupId: NodeId) {
|
|
1889
|
+
let bundlesInABundleGroup: Array<NodeId> = [];
|
|
1378
1890
|
bundleGraph.traverse((nodeId) => {
|
|
1379
1891
|
bundlesInABundleGroup.push(nodeId);
|
|
1380
1892
|
}, bundleGroupId);
|
|
1381
1893
|
return bundlesInABundleGroup;
|
|
1382
1894
|
}
|
|
1383
1895
|
|
|
1896
|
+
interface AsyncBundleMergeCandidate {
|
|
1897
|
+
overfetchSize: number;
|
|
1898
|
+
score: number;
|
|
1899
|
+
bundleIds: number[];
|
|
1900
|
+
}
|
|
1901
|
+
function scoreAsyncMerge(
|
|
1902
|
+
bundleAId: NodeId,
|
|
1903
|
+
bundleBId: NodeId,
|
|
1904
|
+
maxOverfetchSize: number,
|
|
1905
|
+
): AsyncBundleMergeCandidate {
|
|
1906
|
+
let bundleGroupA = new Set(getBundlesForBundleGroup(bundleAId));
|
|
1907
|
+
let bundleGroupB = new Set(getBundlesForBundleGroup(bundleBId));
|
|
1908
|
+
|
|
1909
|
+
let overlapSize = 0;
|
|
1910
|
+
let overfetchSize = 0;
|
|
1911
|
+
|
|
1912
|
+
for (let bundleId of new Set([...bundleGroupA, ...bundleGroupB])) {
|
|
1913
|
+
let bundle = getBundle(bundleId);
|
|
1914
|
+
|
|
1915
|
+
if (bundleGroupA.has(bundleId) && bundleGroupB.has(bundleId)) {
|
|
1916
|
+
overlapSize += bundle.size;
|
|
1917
|
+
} else {
|
|
1918
|
+
overfetchSize += bundle.size;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
let overlapPercent = overlapSize / (overfetchSize + overlapSize);
|
|
1923
|
+
|
|
1924
|
+
let bundleAParents = getBundleParents(bundleAId);
|
|
1925
|
+
let bundleBParents = getBundleParents(bundleBId);
|
|
1926
|
+
|
|
1927
|
+
let sharedParentOverlap = 0;
|
|
1928
|
+
let sharedParentMismatch = 0;
|
|
1929
|
+
|
|
1930
|
+
for (let bundleId of new Set([...bundleAParents, ...bundleBParents])) {
|
|
1931
|
+
if (bundleAParents.has(bundleId) && bundleBParents.has(bundleId)) {
|
|
1932
|
+
sharedParentOverlap++;
|
|
1933
|
+
} else {
|
|
1934
|
+
sharedParentMismatch++;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
let overfetchScore = overfetchSize / maxOverfetchSize;
|
|
1939
|
+
let sharedParentPercent =
|
|
1940
|
+
sharedParentOverlap / (sharedParentOverlap + sharedParentMismatch);
|
|
1941
|
+
let score = sharedParentPercent + overlapPercent + overfetchScore;
|
|
1942
|
+
|
|
1943
|
+
return {
|
|
1944
|
+
overfetchSize,
|
|
1945
|
+
score,
|
|
1946
|
+
bundleIds: [bundleAId, bundleBId],
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
function getBundleParents(bundleId: NodeId): Set<NodeId> {
|
|
1951
|
+
let parents = new Set<NodeId>();
|
|
1952
|
+
let {bundleRoots} = getBundle(bundleId);
|
|
1953
|
+
|
|
1954
|
+
for (let bundleRoot of bundleRoots) {
|
|
1955
|
+
let bundleRootNodeId = nullthrows(
|
|
1956
|
+
assetToBundleRootNodeId.get(bundleRoot),
|
|
1957
|
+
);
|
|
1958
|
+
for (let parentId of bundleRootGraph.getNodeIdsConnectedTo(
|
|
1959
|
+
bundleRootNodeId,
|
|
1960
|
+
ALL_EDGE_TYPES,
|
|
1961
|
+
)) {
|
|
1962
|
+
parents.add(parentId);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return parents;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1384
1968
|
function getBundleFromBundleRoot(bundleRoot: BundleRoot): Bundle {
|
|
1385
1969
|
let bundle = bundleGraph.getNode(
|
|
1386
1970
|
nullthrows(bundleRoots.get(bundleRoot))[0],
|
|
@@ -1445,6 +2029,12 @@ export function createIdealGraph(
|
|
|
1445
2029
|
bundleGraph.removeNode(bundleId);
|
|
1446
2030
|
}
|
|
1447
2031
|
|
|
2032
|
+
stats.report((bundleId) => {
|
|
2033
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
2034
|
+
invariant(bundle !== 'root');
|
|
2035
|
+
return bundle;
|
|
2036
|
+
});
|
|
2037
|
+
|
|
1448
2038
|
return {
|
|
1449
2039
|
assets,
|
|
1450
2040
|
bundleGraph,
|
|
@@ -1455,23 +2045,24 @@ export function createIdealGraph(
|
|
|
1455
2045
|
};
|
|
1456
2046
|
}
|
|
1457
2047
|
|
|
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
|
-
|
|
2048
|
+
function createBundle(opts: {
|
|
2049
|
+
asset?: Asset;
|
|
2050
|
+
bundleBehavior?: BundleBehavior | null | undefined;
|
|
2051
|
+
env?: Environment;
|
|
2052
|
+
manualSharedBundle?: string | null | undefined;
|
|
2053
|
+
needsStableName?: boolean;
|
|
2054
|
+
sourceBundles?: Set<NodeId>;
|
|
2055
|
+
target: Target;
|
|
2056
|
+
type?: string;
|
|
2057
|
+
uniqueKey?: string;
|
|
2058
|
+
}): Bundle {
|
|
1469
2059
|
if (opts.asset == null) {
|
|
1470
2060
|
return {
|
|
1471
2061
|
assets: new Set(),
|
|
1472
2062
|
bundleBehavior: opts.bundleBehavior,
|
|
1473
2063
|
env: nullthrows(opts.env),
|
|
1474
2064
|
mainEntryAsset: null,
|
|
2065
|
+
bundleRoots: new Set(),
|
|
1475
2066
|
manualSharedBundle: opts.manualSharedBundle,
|
|
1476
2067
|
needsStableName: Boolean(opts.needsStableName),
|
|
1477
2068
|
size: 0,
|
|
@@ -1488,6 +2079,7 @@ function createBundle(opts: {|
|
|
|
1488
2079
|
bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior,
|
|
1489
2080
|
env: opts.env ?? asset.env,
|
|
1490
2081
|
mainEntryAsset: asset,
|
|
2082
|
+
bundleRoots: new Set([asset]),
|
|
1491
2083
|
manualSharedBundle: opts.manualSharedBundle,
|
|
1492
2084
|
needsStableName: Boolean(opts.needsStableName),
|
|
1493
2085
|
size: asset.stats.size,
|