@atlaspack/bundler-default 2.14.5-canary.6 → 2.14.5-canary.8
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/lib/bundleMerge.js +91 -0
- package/lib/bundlerConfig.js +8 -2
- package/lib/idealGraph.js +63 -1
- package/package.json +8 -8
- package/src/bundleMerge.js +103 -0
- package/src/bundlerConfig.js +10 -0
- package/src/idealGraph.js +99 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.findMergeCandidates = findMergeCandidates;
|
|
7
|
+
function _assert() {
|
|
8
|
+
const data = _interopRequireDefault(require("assert"));
|
|
9
|
+
_assert = function () {
|
|
10
|
+
return data;
|
|
11
|
+
};
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
function _nullthrows() {
|
|
15
|
+
const data = _interopRequireDefault(require("nullthrows"));
|
|
16
|
+
_nullthrows = function () {
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
function _graph() {
|
|
22
|
+
const data = require("@atlaspack/graph");
|
|
23
|
+
_graph = function () {
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
+
// Returns a decimal showing the proportion source bundles are common to
|
|
30
|
+
// both bundles versus the total number of source bundles.
|
|
31
|
+
function scoreBundleMerge(bundleA, bundleB) {
|
|
32
|
+
let sharedSourceBundles = 0;
|
|
33
|
+
let allSourceBundles = new Set([...bundleA.sourceBundles, ...bundleB.sourceBundles]);
|
|
34
|
+
for (let bundle of bundleB.sourceBundles) {
|
|
35
|
+
if (bundleA.sourceBundles.has(bundle)) {
|
|
36
|
+
sharedSourceBundles++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return sharedSourceBundles / allSourceBundles.size;
|
|
40
|
+
}
|
|
41
|
+
function getMergeClusters(graph, candidates) {
|
|
42
|
+
let clusters = [];
|
|
43
|
+
for (let candidate of candidates) {
|
|
44
|
+
let cluster = [];
|
|
45
|
+
graph.traverse(nodeId => {
|
|
46
|
+
cluster.push((0, _nullthrows().default)(graph.getNode(nodeId)));
|
|
47
|
+
// Remove node from candidates as it has already been processed
|
|
48
|
+
candidates.delete(nodeId);
|
|
49
|
+
}, candidate);
|
|
50
|
+
clusters.push(cluster);
|
|
51
|
+
}
|
|
52
|
+
return clusters;
|
|
53
|
+
}
|
|
54
|
+
function findMergeCandidates(bundleGraph, bundles, threshold) {
|
|
55
|
+
let graph = new (_graph().ContentGraph)();
|
|
56
|
+
let seen = new Set();
|
|
57
|
+
let candidates = new Set();
|
|
58
|
+
|
|
59
|
+
// Build graph of clustered merge candidates
|
|
60
|
+
for (let bundleId of bundles) {
|
|
61
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
62
|
+
(0, _assert().default)(bundle && bundle !== 'root');
|
|
63
|
+
if (bundle.type !== 'js') {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
for (let otherBundleId of bundles) {
|
|
67
|
+
if (bundleId === otherBundleId) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let key = [bundleId, otherBundleId].sort().join(':');
|
|
71
|
+
if (seen.has(key)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
seen.add(key);
|
|
75
|
+
let otherBundle = bundleGraph.getNode(otherBundleId);
|
|
76
|
+
(0, _assert().default)(otherBundle && otherBundle !== 'root');
|
|
77
|
+
let score = scoreBundleMerge(bundle, otherBundle);
|
|
78
|
+
if (score >= threshold) {
|
|
79
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(bundleId.toString(), bundleId);
|
|
80
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(otherBundleId.toString(), otherBundleId);
|
|
81
|
+
|
|
82
|
+
// Add edge in both directions
|
|
83
|
+
graph.addEdge(bundleNode, otherBundleNode);
|
|
84
|
+
graph.addEdge(otherBundleNode, bundleNode);
|
|
85
|
+
candidates.add(bundleNode);
|
|
86
|
+
candidates.add(otherBundleNode);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return getMergeClusters(graph, candidates);
|
|
91
|
+
}
|
package/lib/bundlerConfig.js
CHANGED
|
@@ -53,14 +53,16 @@ const HTTP_OPTIONS = {
|
|
|
53
53
|
manualSharedBundles: [],
|
|
54
54
|
minBundleSize: 30000,
|
|
55
55
|
maxParallelRequests: 6,
|
|
56
|
-
disableSharedBundles: false
|
|
56
|
+
disableSharedBundles: false,
|
|
57
|
+
sharedBundleMergeThreshold: 1
|
|
57
58
|
},
|
|
58
59
|
'2': {
|
|
59
60
|
minBundles: 1,
|
|
60
61
|
manualSharedBundles: [],
|
|
61
62
|
minBundleSize: 20000,
|
|
62
63
|
maxParallelRequests: 25,
|
|
63
|
-
disableSharedBundles: false
|
|
64
|
+
disableSharedBundles: false,
|
|
65
|
+
sharedBundleMergeThreshold: 1
|
|
64
66
|
}
|
|
65
67
|
};
|
|
66
68
|
const CONFIG_SCHEMA = {
|
|
@@ -115,6 +117,9 @@ const CONFIG_SCHEMA = {
|
|
|
115
117
|
},
|
|
116
118
|
loadConditionalBundlesInParallel: {
|
|
117
119
|
type: 'boolean'
|
|
120
|
+
},
|
|
121
|
+
sharedBundleMergeThreshold: {
|
|
122
|
+
type: 'number'
|
|
118
123
|
}
|
|
119
124
|
},
|
|
120
125
|
additionalProperties: false
|
|
@@ -172,6 +177,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
172
177
|
return {
|
|
173
178
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
174
179
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
180
|
+
sharedBundleMergeThreshold: modeConfig.sharedBundleMergeThreshold ?? defaults.sharedBundleMergeThreshold,
|
|
175
181
|
maxParallelRequests: modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
176
182
|
projectRoot: options.projectRoot,
|
|
177
183
|
disableSharedBundles: modeConfig.disableSharedBundles ?? defaults.disableSharedBundles,
|
package/lib/idealGraph.js
CHANGED
|
@@ -47,6 +47,7 @@ function _nullthrows() {
|
|
|
47
47
|
};
|
|
48
48
|
return data;
|
|
49
49
|
}
|
|
50
|
+
var _bundleMerge = require("./bundleMerge");
|
|
50
51
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
51
52
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
52
53
|
const dependencyPriorityEdges = {
|
|
@@ -848,6 +849,12 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
848
849
|
}
|
|
849
850
|
}
|
|
850
851
|
|
|
852
|
+
// Step merge shared bundles that meet the overlap threshold
|
|
853
|
+
// This step is skipped by default as the threshold defaults to 1
|
|
854
|
+
if (config.sharedBundleMergeThreshold < 1) {
|
|
855
|
+
mergeOverlapBundles();
|
|
856
|
+
}
|
|
857
|
+
|
|
851
858
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
852
859
|
// their source bundles, and remove the bundle.
|
|
853
860
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
@@ -857,9 +864,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
857
864
|
removeBundle(bundleGraph, bundleNodeId, assetReference);
|
|
858
865
|
}
|
|
859
866
|
}
|
|
860
|
-
let modifiedSourceBundles = new Set();
|
|
861
867
|
|
|
862
868
|
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
869
|
+
let modifiedSourceBundles = new Set();
|
|
863
870
|
if (config.disableSharedBundles === false) {
|
|
864
871
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
865
872
|
// Find shared bundles in this bundle group.
|
|
@@ -944,6 +951,61 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
944
951
|
}
|
|
945
952
|
}
|
|
946
953
|
}
|
|
954
|
+
function mergeBundles(bundleGraph, bundleToKeepId, bundleToRemoveId, assetReference) {
|
|
955
|
+
let bundleToKeep = (0, _nullthrows().default)(bundleGraph.getNode(bundleToKeepId), '18');
|
|
956
|
+
let bundleToRemove = (0, _nullthrows().default)(bundleGraph.getNode(bundleToRemoveId), '19');
|
|
957
|
+
(0, _assert().default)(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
958
|
+
for (let asset of bundleToRemove.assets) {
|
|
959
|
+
bundleToKeep.assets.add(asset);
|
|
960
|
+
bundleToKeep.size += asset.stats.size;
|
|
961
|
+
let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
|
|
962
|
+
assetReference.set(asset, newAssetReference);
|
|
963
|
+
}
|
|
964
|
+
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
965
|
+
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
969
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Merge any internalized assets
|
|
973
|
+
if (bundleToRemove.internalizedAssets) {
|
|
974
|
+
if (bundleToKeep.internalizedAssets) {
|
|
975
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
976
|
+
} else {
|
|
977
|
+
bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
bundleGraph.removeNode(bundleToRemoveId);
|
|
981
|
+
}
|
|
982
|
+
function mergeOverlapBundles() {
|
|
983
|
+
// Find all shared bundles
|
|
984
|
+
let sharedBundles = new Set();
|
|
985
|
+
bundleGraph.traverse(nodeId => {
|
|
986
|
+
let bundle = bundleGraph.getNode(nodeId);
|
|
987
|
+
if (!bundle) {
|
|
988
|
+
throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
|
|
989
|
+
}
|
|
990
|
+
if (bundle === 'root') {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Only consider JS shared bundles and non-reused bundles.
|
|
995
|
+
// These count potentially be considered for merging in future but they're
|
|
996
|
+
// more complicated to merge
|
|
997
|
+
if (bundle.sourceBundles.size > 0 && bundle.manualSharedBundle == null && !bundle.mainEntryAsset && bundle.type === 'js') {
|
|
998
|
+
sharedBundles.add(nodeId);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), config.sharedBundleMergeThreshold);
|
|
1002
|
+
for (let cluster of clusters) {
|
|
1003
|
+
let [mergeTarget, ...rest] = cluster;
|
|
1004
|
+
for (let bundleIdToMerge of rest) {
|
|
1005
|
+
mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
947
1009
|
function getBigIntFromContentKey(contentKey) {
|
|
948
1010
|
let b = Buffer.alloc(64);
|
|
949
1011
|
b.write(contentKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/bundler-default",
|
|
3
|
-
"version": "2.14.5-canary.
|
|
3
|
+
"version": "2.14.5-canary.8+46a90dccd",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
"node": ">= 16.0.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@atlaspack/diagnostic": "2.14.1-canary.
|
|
20
|
-
"@atlaspack/feature-flags": "2.14.1-canary.
|
|
21
|
-
"@atlaspack/graph": "3.4.1-canary.
|
|
22
|
-
"@atlaspack/plugin": "2.14.5-canary.
|
|
23
|
-
"@atlaspack/rust": "3.2.1-canary.
|
|
24
|
-
"@atlaspack/utils": "2.14.5-canary.
|
|
19
|
+
"@atlaspack/diagnostic": "2.14.1-canary.76+46a90dccd",
|
|
20
|
+
"@atlaspack/feature-flags": "2.14.1-canary.76+46a90dccd",
|
|
21
|
+
"@atlaspack/graph": "3.4.1-canary.76+46a90dccd",
|
|
22
|
+
"@atlaspack/plugin": "2.14.5-canary.8+46a90dccd",
|
|
23
|
+
"@atlaspack/rust": "3.2.1-canary.8+46a90dccd",
|
|
24
|
+
"@atlaspack/utils": "2.14.5-canary.8+46a90dccd",
|
|
25
25
|
"nullthrows": "^1.1.1"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "46a90dccd019a26b222c878a92d23acc75dc67c5"
|
|
28
28
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
|
|
3
|
+
import invariant from 'assert';
|
|
4
|
+
import nullthrows from 'nullthrows';
|
|
5
|
+
import type {NodeId} from '@atlaspack/graph';
|
|
6
|
+
import type {Bundle, IdealBundleGraph} from './idealGraph';
|
|
7
|
+
import {ContentGraph} from '@atlaspack/graph';
|
|
8
|
+
|
|
9
|
+
// Returns a decimal showing the proportion source bundles are common to
|
|
10
|
+
// both bundles versus the total number of source bundles.
|
|
11
|
+
function scoreBundleMerge(bundleA: Bundle, bundleB: Bundle): number {
|
|
12
|
+
let sharedSourceBundles = 0;
|
|
13
|
+
let allSourceBundles = new Set([
|
|
14
|
+
...bundleA.sourceBundles,
|
|
15
|
+
...bundleB.sourceBundles,
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
for (let bundle of bundleB.sourceBundles) {
|
|
19
|
+
if (bundleA.sourceBundles.has(bundle)) {
|
|
20
|
+
sharedSourceBundles++;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return sharedSourceBundles / allSourceBundles.size;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getMergeClusters(
|
|
28
|
+
graph: ContentGraph<NodeId>,
|
|
29
|
+
candidates: Set<NodeId>,
|
|
30
|
+
): Array<Array<NodeId>> {
|
|
31
|
+
let clusters = [];
|
|
32
|
+
|
|
33
|
+
for (let candidate of candidates) {
|
|
34
|
+
let cluster: Array<NodeId> = [];
|
|
35
|
+
|
|
36
|
+
graph.traverse((nodeId) => {
|
|
37
|
+
cluster.push(nullthrows(graph.getNode(nodeId)));
|
|
38
|
+
// Remove node from candidates as it has already been processed
|
|
39
|
+
candidates.delete(nodeId);
|
|
40
|
+
}, candidate);
|
|
41
|
+
|
|
42
|
+
clusters.push(cluster);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return clusters;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function findMergeCandidates(
|
|
49
|
+
bundleGraph: IdealBundleGraph,
|
|
50
|
+
bundles: Array<NodeId>,
|
|
51
|
+
threshold: number,
|
|
52
|
+
): Array<Array<NodeId>> {
|
|
53
|
+
let graph = new ContentGraph<NodeId>();
|
|
54
|
+
let seen = new Set<string>();
|
|
55
|
+
let candidates = new Set<NodeId>();
|
|
56
|
+
|
|
57
|
+
// Build graph of clustered merge candidates
|
|
58
|
+
for (let bundleId of bundles) {
|
|
59
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
60
|
+
invariant(bundle && bundle !== 'root');
|
|
61
|
+
if (bundle.type !== 'js') {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (let otherBundleId of bundles) {
|
|
66
|
+
if (bundleId === otherBundleId) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let key = [bundleId, otherBundleId].sort().join(':');
|
|
71
|
+
|
|
72
|
+
if (seen.has(key)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
seen.add(key);
|
|
76
|
+
|
|
77
|
+
let otherBundle = bundleGraph.getNode(otherBundleId);
|
|
78
|
+
invariant(otherBundle && otherBundle !== 'root');
|
|
79
|
+
|
|
80
|
+
let score = scoreBundleMerge(bundle, otherBundle);
|
|
81
|
+
|
|
82
|
+
if (score >= threshold) {
|
|
83
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
84
|
+
bundleId.toString(),
|
|
85
|
+
bundleId,
|
|
86
|
+
);
|
|
87
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
88
|
+
otherBundleId.toString(),
|
|
89
|
+
otherBundleId,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Add edge in both directions
|
|
93
|
+
graph.addEdge(bundleNode, otherBundleNode);
|
|
94
|
+
graph.addEdge(otherBundleNode, bundleNode);
|
|
95
|
+
|
|
96
|
+
candidates.add(bundleNode);
|
|
97
|
+
candidates.add(otherBundleNode);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return getMergeClusters(graph, candidates);
|
|
103
|
+
}
|
package/src/bundlerConfig.js
CHANGED
|
@@ -28,6 +28,7 @@ type BaseBundlerConfig = {|
|
|
|
28
28
|
disableSharedBundles?: boolean,
|
|
29
29
|
manualSharedBundles?: ManualSharedBundles,
|
|
30
30
|
loadConditionalBundlesInParallel?: boolean,
|
|
31
|
+
sharedBundleMergeThreshold?: number,
|
|
31
32
|
|};
|
|
32
33
|
|
|
33
34
|
type BundlerConfig = {|
|
|
@@ -42,6 +43,7 @@ export type ResolvedBundlerConfig = {|
|
|
|
42
43
|
disableSharedBundles: boolean,
|
|
43
44
|
manualSharedBundles: ManualSharedBundles,
|
|
44
45
|
loadConditionalBundlesInParallel?: boolean,
|
|
46
|
+
sharedBundleMergeThreshold: number,
|
|
45
47
|
|};
|
|
46
48
|
|
|
47
49
|
function resolveModeConfig(
|
|
@@ -76,6 +78,7 @@ const HTTP_OPTIONS = {
|
|
|
76
78
|
minBundleSize: 30000,
|
|
77
79
|
maxParallelRequests: 6,
|
|
78
80
|
disableSharedBundles: false,
|
|
81
|
+
sharedBundleMergeThreshold: 1,
|
|
79
82
|
},
|
|
80
83
|
'2': {
|
|
81
84
|
minBundles: 1,
|
|
@@ -83,6 +86,7 @@ const HTTP_OPTIONS = {
|
|
|
83
86
|
minBundleSize: 20000,
|
|
84
87
|
maxParallelRequests: 25,
|
|
85
88
|
disableSharedBundles: false,
|
|
89
|
+
sharedBundleMergeThreshold: 1,
|
|
86
90
|
},
|
|
87
91
|
};
|
|
88
92
|
|
|
@@ -139,6 +143,9 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
139
143
|
loadConditionalBundlesInParallel: {
|
|
140
144
|
type: 'boolean',
|
|
141
145
|
},
|
|
146
|
+
sharedBundleMergeThreshold: {
|
|
147
|
+
type: 'number',
|
|
148
|
+
},
|
|
142
149
|
},
|
|
143
150
|
additionalProperties: false,
|
|
144
151
|
};
|
|
@@ -224,6 +231,9 @@ export async function loadBundlerConfig(
|
|
|
224
231
|
return {
|
|
225
232
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
226
233
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
234
|
+
sharedBundleMergeThreshold:
|
|
235
|
+
modeConfig.sharedBundleMergeThreshold ??
|
|
236
|
+
defaults.sharedBundleMergeThreshold,
|
|
227
237
|
maxParallelRequests:
|
|
228
238
|
modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
229
239
|
projectRoot: options.projectRoot,
|
package/src/idealGraph.js
CHANGED
|
@@ -23,6 +23,7 @@ import {DefaultMap, globToRegex} from '@atlaspack/utils';
|
|
|
23
23
|
import invariant from 'assert';
|
|
24
24
|
import nullthrows from 'nullthrows';
|
|
25
25
|
|
|
26
|
+
import {findMergeCandidates} from './bundleMerge';
|
|
26
27
|
import type {ResolvedBundlerConfig} from './bundlerConfig';
|
|
27
28
|
|
|
28
29
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
@@ -67,7 +68,7 @@ export const idealBundleGraphEdges = Object.freeze({
|
|
|
67
68
|
conditional: 2,
|
|
68
69
|
});
|
|
69
70
|
|
|
70
|
-
type IdealBundleGraph = Graph<
|
|
71
|
+
export type IdealBundleGraph = Graph<
|
|
71
72
|
Bundle | 'root',
|
|
72
73
|
$Values<typeof idealBundleGraphEdges>,
|
|
73
74
|
>;
|
|
@@ -1128,6 +1129,12 @@ export function createIdealGraph(
|
|
|
1128
1129
|
}
|
|
1129
1130
|
}
|
|
1130
1131
|
|
|
1132
|
+
// Step merge shared bundles that meet the overlap threshold
|
|
1133
|
+
// This step is skipped by default as the threshold defaults to 1
|
|
1134
|
+
if (config.sharedBundleMergeThreshold < 1) {
|
|
1135
|
+
mergeOverlapBundles();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1131
1138
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
1132
1139
|
// their source bundles, and remove the bundle.
|
|
1133
1140
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
@@ -1143,9 +1150,9 @@ export function createIdealGraph(
|
|
|
1143
1150
|
}
|
|
1144
1151
|
}
|
|
1145
1152
|
|
|
1153
|
+
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1146
1154
|
let modifiedSourceBundles = new Set();
|
|
1147
1155
|
|
|
1148
|
-
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1149
1156
|
if (config.disableSharedBundles === false) {
|
|
1150
1157
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
1151
1158
|
// Find shared bundles in this bundle group.
|
|
@@ -1249,6 +1256,96 @@ export function createIdealGraph(
|
|
|
1249
1256
|
}
|
|
1250
1257
|
}
|
|
1251
1258
|
|
|
1259
|
+
function mergeBundles(
|
|
1260
|
+
bundleGraph: IdealBundleGraph,
|
|
1261
|
+
bundleToKeepId: NodeId,
|
|
1262
|
+
bundleToRemoveId: NodeId,
|
|
1263
|
+
assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>,
|
|
1264
|
+
) {
|
|
1265
|
+
let bundleToKeep = nullthrows(bundleGraph.getNode(bundleToKeepId), '18');
|
|
1266
|
+
let bundleToRemove = nullthrows(
|
|
1267
|
+
bundleGraph.getNode(bundleToRemoveId),
|
|
1268
|
+
'19',
|
|
1269
|
+
);
|
|
1270
|
+
invariant(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
1271
|
+
for (let asset of bundleToRemove.assets) {
|
|
1272
|
+
bundleToKeep.assets.add(asset);
|
|
1273
|
+
bundleToKeep.size += asset.stats.size;
|
|
1274
|
+
|
|
1275
|
+
let newAssetReference = assetReference
|
|
1276
|
+
.get(asset)
|
|
1277
|
+
.map(([dep, bundle]) =>
|
|
1278
|
+
bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
|
|
1279
|
+
);
|
|
1280
|
+
|
|
1281
|
+
assetReference.set(asset, newAssetReference);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1285
|
+
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
1290
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Merge any internalized assets
|
|
1294
|
+
if (bundleToRemove.internalizedAssets) {
|
|
1295
|
+
if (bundleToKeep.internalizedAssets) {
|
|
1296
|
+
bundleToKeep.internalizedAssets.union(
|
|
1297
|
+
bundleToRemove.internalizedAssets,
|
|
1298
|
+
);
|
|
1299
|
+
} else {
|
|
1300
|
+
bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
bundleGraph.removeNode(bundleToRemoveId);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function mergeOverlapBundles() {
|
|
1308
|
+
// Find all shared bundles
|
|
1309
|
+
let sharedBundles = new Set<NodeId>();
|
|
1310
|
+
bundleGraph.traverse((nodeId) => {
|
|
1311
|
+
let bundle = bundleGraph.getNode(nodeId);
|
|
1312
|
+
|
|
1313
|
+
if (!bundle) {
|
|
1314
|
+
throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (bundle === 'root') {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// Only consider JS shared bundles and non-reused bundles.
|
|
1322
|
+
// These count potentially be considered for merging in future but they're
|
|
1323
|
+
// more complicated to merge
|
|
1324
|
+
if (
|
|
1325
|
+
bundle.sourceBundles.size > 0 &&
|
|
1326
|
+
bundle.manualSharedBundle == null &&
|
|
1327
|
+
!bundle.mainEntryAsset &&
|
|
1328
|
+
bundle.type === 'js'
|
|
1329
|
+
) {
|
|
1330
|
+
sharedBundles.add(nodeId);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
let clusters = findMergeCandidates(
|
|
1335
|
+
bundleGraph,
|
|
1336
|
+
Array.from(sharedBundles),
|
|
1337
|
+
config.sharedBundleMergeThreshold,
|
|
1338
|
+
);
|
|
1339
|
+
|
|
1340
|
+
for (let cluster of clusters) {
|
|
1341
|
+
let [mergeTarget, ...rest] = cluster;
|
|
1342
|
+
|
|
1343
|
+
for (let bundleIdToMerge of rest) {
|
|
1344
|
+
mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1252
1349
|
function getBigIntFromContentKey(contentKey) {
|
|
1253
1350
|
let b = Buffer.alloc(64);
|
|
1254
1351
|
b.write(contentKey);
|