@atlaspack/bundler-default 2.16.2 → 3.0.0
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 +113 -0
- package/lib/bundleMerge.js +104 -37
- package/lib/bundlerConfig.js +27 -3
- package/lib/idealGraph.js +31 -13
- package/lib/memoize.js +40 -0
- package/package.json +7 -6
- package/src/bundleMerge.js +210 -59
- package/src/bundlerConfig.js +37 -7
- package/src/idealGraph.js +44 -17
- package/src/memoize.js +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,118 @@
|
|
|
1
1
|
# @atlaspack/bundler-default
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#600](https://github.com/atlassian-labs/atlaspack/pull/600) [`1b52b99`](https://github.com/atlassian-labs/atlaspack/commit/1b52b99db4298b04c1a6eb0f97994d75a2d436f9) Thanks [@mattcompiles](https://github.com/mattcompiles)! - ### Breaking change
|
|
8
|
+
|
|
9
|
+
This new config replaces the previously released `sharedBundleMergeThreshold`.
|
|
10
|
+
|
|
11
|
+
The following options are available for each merge group.
|
|
12
|
+
|
|
13
|
+
### Options
|
|
14
|
+
|
|
15
|
+
#### overlapThreshold
|
|
16
|
+
|
|
17
|
+
> The same as `sharedBundleMergeThreshold` from #535
|
|
18
|
+
|
|
19
|
+
Merge bundles share a percentage of source bundles
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
"@atlaspack/bundler-default": {
|
|
23
|
+
"sharedBundleMerge": [{
|
|
24
|
+
"overlapThreshold": 0.75
|
|
25
|
+
}]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### maxBundleSize
|
|
30
|
+
|
|
31
|
+
Merge bundles that are smaller than a configured amount of bytes.
|
|
32
|
+
|
|
33
|
+
> Keep in mind these bytes are pre-optimisation
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
"@atlaspack/bundler-default": {
|
|
37
|
+
"sharedBundleMerge": [{
|
|
38
|
+
"maxBundleSize": 20000
|
|
39
|
+
}]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### sourceBundles
|
|
44
|
+
|
|
45
|
+
Merge bundles that share a set of source bundles. The matching is relative to the project root, like how manual shared bundle roots work.
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
"@atlaspack/bundler-default": {
|
|
49
|
+
"sharedBundleMerge": [{
|
|
50
|
+
"sourceBundles": ["src/important-route", "src/important-route-2"]
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### minBundlesInGroup
|
|
56
|
+
|
|
57
|
+
Merge bundles that belong to a bundle group that's larger than a set amount. This is useful for targetting bundles that would be deleted by the `maxParallelRequests` option.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
"@atlaspack/bundler-default": {
|
|
61
|
+
"maxParallelRequests": 30,
|
|
62
|
+
"sharedBundleMerge": [{
|
|
63
|
+
"minBundlesInGroup": 30
|
|
64
|
+
}]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Combining options
|
|
69
|
+
|
|
70
|
+
When multiple options are provided, all must be true for a merge to be relevant.
|
|
71
|
+
|
|
72
|
+
For example, merge bundles that are smaller than 20kb and share at least 50% of the same source bundles.
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
"@atlaspack/bundler-default": {
|
|
76
|
+
"sharedBundleMerge": [{
|
|
77
|
+
"overlapThreshold": 0.5,
|
|
78
|
+
"maxBundleSize": 20000
|
|
79
|
+
}]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Multiple merges
|
|
84
|
+
|
|
85
|
+
You can also have multiple merge configs.
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
"@atlaspack/bundler-default": {
|
|
89
|
+
"sharedBundleMerge": [
|
|
90
|
+
{
|
|
91
|
+
"overlapThreshold": 0.75,
|
|
92
|
+
"maxBundleSize": 20000
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"minBundlesInGroup": 30
|
|
96
|
+
"sourceBundles": ["src/important-route", "src/important-route-2"]
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Patch Changes
|
|
103
|
+
|
|
104
|
+
- Updated dependencies [[`1b52b99`](https://github.com/atlassian-labs/atlaspack/commit/1b52b99db4298b04c1a6eb0f97994d75a2d436f9)]:
|
|
105
|
+
- @atlaspack/graph@3.5.0
|
|
106
|
+
|
|
107
|
+
## 2.16.3
|
|
108
|
+
|
|
109
|
+
### Patch Changes
|
|
110
|
+
|
|
111
|
+
- Updated dependencies [[`35fdd4b`](https://github.com/atlassian-labs/atlaspack/commit/35fdd4b52da0af20f74667f7b8adfb2f90279b7c), [`6dd4ccb`](https://github.com/atlassian-labs/atlaspack/commit/6dd4ccb753541de32322d881f973d571dd57e4ca)]:
|
|
112
|
+
- @atlaspack/rust@3.3.5
|
|
113
|
+
- @atlaspack/plugin@2.14.10
|
|
114
|
+
- @atlaspack/utils@2.14.10
|
|
115
|
+
|
|
3
116
|
## 2.16.2
|
|
4
117
|
|
|
5
118
|
### Patch Changes
|
package/lib/bundleMerge.js
CHANGED
|
@@ -25,67 +25,134 @@ function _graph() {
|
|
|
25
25
|
};
|
|
26
26
|
return data;
|
|
27
27
|
}
|
|
28
|
+
var _memoize = require("./memoize");
|
|
28
29
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
30
|
+
function getBundlesForBundleGroup(bundleGraph, bundleGroupId) {
|
|
31
|
+
let count = 0;
|
|
32
|
+
bundleGraph.traverse(nodeId => {
|
|
33
|
+
var _bundleGraph$getNode;
|
|
34
|
+
if (((_bundleGraph$getNode = bundleGraph.getNode(nodeId)) === null || _bundleGraph$getNode === void 0 ? void 0 : _bundleGraph$getNode.bundleBehavior) !== 'inline') {
|
|
35
|
+
count++;
|
|
36
|
+
}
|
|
37
|
+
}, bundleGroupId);
|
|
38
|
+
return count;
|
|
39
|
+
}
|
|
40
|
+
let getBundleOverlapBitSet = (sourceBundlesA, sourceBundlesB) => {
|
|
41
|
+
let allSourceBundles = _graph().BitSet.union(sourceBundlesA, sourceBundlesB);
|
|
42
|
+
let sharedSourceBundles = _graph().BitSet.intersect(sourceBundlesA, sourceBundlesB);
|
|
43
|
+
return sharedSourceBundles.size() / allSourceBundles.size();
|
|
44
|
+
};
|
|
45
|
+
|
|
29
46
|
// Returns a decimal showing the proportion source bundles are common to
|
|
30
47
|
// both bundles versus the total number of source bundles.
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
function checkBundleThreshold(bundleA, bundleB, threshold) {
|
|
49
|
+
return getBundleOverlapBitSet(bundleA.sourceBundleBitSet, bundleB.sourceBundleBitSet) >= threshold;
|
|
50
|
+
}
|
|
51
|
+
let checkSharedSourceBundles = (0, _memoize.memoize)((bundle, importantAncestorBundles) => {
|
|
52
|
+
return importantAncestorBundles.every(ancestorId => bundle.sourceBundles.has(ancestorId));
|
|
53
|
+
});
|
|
54
|
+
let hasSuitableBundleGroup = (0, _memoize.memoize)((bundleGraph, bundle, minBundlesInGroup) => {
|
|
55
|
+
for (let sourceBundle of bundle.sourceBundles) {
|
|
56
|
+
let bundlesInGroup = getBundlesForBundleGroup(bundleGraph, sourceBundle);
|
|
57
|
+
if (bundlesInGroup >= minBundlesInGroup) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
});
|
|
63
|
+
function validMerge(bundleGraph, config, bundleA, bundleB) {
|
|
64
|
+
if (config.maxBundleSize != null) {
|
|
65
|
+
if (bundleA.bundle.size > config.maxBundleSize || bundleB.bundle.size > config.maxBundleSize) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (config.overlapThreshold != null) {
|
|
70
|
+
if (!checkBundleThreshold(bundleA, bundleB, config.overlapThreshold)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (config.sourceBundles != null) {
|
|
75
|
+
if (!checkSharedSourceBundles(bundleA.bundle, config.sourceBundles) || !checkSharedSourceBundles(bundleB.bundle, config.sourceBundles)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (config.minBundlesInGroup != null) {
|
|
80
|
+
if (!hasSuitableBundleGroup(bundleGraph, bundleA.bundle, config.minBundlesInGroup) || !hasSuitableBundleGroup(bundleGraph, bundleB.bundle, config.minBundlesInGroup)) {
|
|
81
|
+
return false;
|
|
37
82
|
}
|
|
38
83
|
}
|
|
39
|
-
return
|
|
84
|
+
return true;
|
|
40
85
|
}
|
|
41
86
|
function getMergeClusters(graph, candidates) {
|
|
42
87
|
let clusters = [];
|
|
43
|
-
for (let candidate of candidates) {
|
|
88
|
+
for (let [candidate, edgeType] of candidates.entries()) {
|
|
44
89
|
let cluster = [];
|
|
45
90
|
graph.traverse(nodeId => {
|
|
46
91
|
cluster.push((0, _nullthrows().default)(graph.getNode(nodeId)));
|
|
47
92
|
// Remove node from candidates as it has already been processed
|
|
48
93
|
candidates.delete(nodeId);
|
|
49
|
-
}, candidate);
|
|
94
|
+
}, candidate, edgeType);
|
|
50
95
|
clusters.push(cluster);
|
|
51
96
|
}
|
|
52
97
|
return clusters;
|
|
53
98
|
}
|
|
54
|
-
function
|
|
55
|
-
let
|
|
56
|
-
let seen = new Set();
|
|
57
|
-
let candidates = new Set();
|
|
58
|
-
|
|
59
|
-
// Build graph of clustered merge candidates
|
|
60
|
-
for (let bundleId of bundles) {
|
|
99
|
+
function getPossibleMergeCandidates(bundleGraph, bundles) {
|
|
100
|
+
let mergeCandidates = bundles.map(bundleId => {
|
|
61
101
|
let bundle = bundleGraph.getNode(bundleId);
|
|
62
|
-
(0, _assert().default)(bundle && bundle !== 'root');
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
(0, _assert().default)(bundle && bundle !== 'root', 'Bundle should exist');
|
|
103
|
+
let sourceBundleBitSet = new (_graph().BitSet)(bundleGraph.nodes.length);
|
|
104
|
+
for (let sourceBundle of bundle.sourceBundles) {
|
|
105
|
+
sourceBundleBitSet.add(sourceBundle);
|
|
65
106
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
107
|
+
return {
|
|
108
|
+
id: bundleId,
|
|
109
|
+
bundle,
|
|
110
|
+
sourceBundleBitSet,
|
|
111
|
+
contentKey: bundleId.toString()
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
const uniquePairs = [];
|
|
115
|
+
for (let i = 0; i < mergeCandidates.length; i++) {
|
|
116
|
+
for (let j = i + 1; j < mergeCandidates.length; j++) {
|
|
117
|
+
let a = mergeCandidates[i];
|
|
118
|
+
let b = mergeCandidates[j];
|
|
119
|
+
if (
|
|
120
|
+
// $FlowFixMe both bundles will always have internalizedAssets
|
|
121
|
+
a.bundle.internalizedAssets.equals(b.bundle.internalizedAssets)) {
|
|
122
|
+
uniquePairs.push([a, b]);
|
|
69
123
|
}
|
|
70
|
-
|
|
71
|
-
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return uniquePairs;
|
|
127
|
+
}
|
|
128
|
+
function findMergeCandidates(bundleGraph, bundles, config) {
|
|
129
|
+
let graph = new (_graph().ContentGraph)();
|
|
130
|
+
let candidates = new Map();
|
|
131
|
+
let allPossibleMergeCandidates = getPossibleMergeCandidates(bundleGraph, bundles);
|
|
132
|
+
|
|
133
|
+
// Build graph of clustered merge candidates
|
|
134
|
+
for (let i = 0; i < config.length; i++) {
|
|
135
|
+
// Ensure edge type coresponds to config index
|
|
136
|
+
let edgeType = i + 1;
|
|
137
|
+
for (let group of allPossibleMergeCandidates) {
|
|
138
|
+
let candidateA = group[0];
|
|
139
|
+
let candidateB = group[1];
|
|
140
|
+
if (!validMerge(bundleGraph, config[i], candidateA, candidateB)) {
|
|
72
141
|
continue;
|
|
73
142
|
}
|
|
74
|
-
|
|
75
|
-
let
|
|
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);
|
|
143
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(candidateA.contentKey, candidateA.id);
|
|
144
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(candidateB.contentKey, candidateB.id);
|
|
81
145
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
146
|
+
// Add edge in both directions
|
|
147
|
+
graph.addEdge(bundleNode, otherBundleNode, edgeType);
|
|
148
|
+
graph.addEdge(otherBundleNode, bundleNode, edgeType);
|
|
149
|
+
candidates.set(bundleNode, edgeType);
|
|
150
|
+
candidates.set(otherBundleNode, edgeType);
|
|
88
151
|
}
|
|
152
|
+
|
|
153
|
+
// Remove bundles that have been allocated to a higher priority merge
|
|
154
|
+
allPossibleMergeCandidates = allPossibleMergeCandidates.filter(group => !graph.hasContentKey(group[0].contentKey) && !graph.hasContentKey(group[1].contentKey));
|
|
89
155
|
}
|
|
156
|
+
(0, _memoize.clearCaches)();
|
|
90
157
|
return getMergeClusters(graph, candidates);
|
|
91
158
|
}
|
package/lib/bundlerConfig.js
CHANGED
|
@@ -61,7 +61,7 @@ const HTTP_OPTIONS = {
|
|
|
61
61
|
minBundleSize: 30000,
|
|
62
62
|
maxParallelRequests: 6,
|
|
63
63
|
disableSharedBundles: false,
|
|
64
|
-
|
|
64
|
+
sharedBundleMerge: []
|
|
65
65
|
},
|
|
66
66
|
'2': {
|
|
67
67
|
minBundles: 1,
|
|
@@ -69,7 +69,7 @@ const HTTP_OPTIONS = {
|
|
|
69
69
|
minBundleSize: 20000,
|
|
70
70
|
maxParallelRequests: 25,
|
|
71
71
|
disableSharedBundles: false,
|
|
72
|
-
|
|
72
|
+
sharedBundleMerge: []
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
const CONFIG_SCHEMA = {
|
|
@@ -110,6 +110,30 @@ const CONFIG_SCHEMA = {
|
|
|
110
110
|
additionalProperties: false
|
|
111
111
|
}
|
|
112
112
|
},
|
|
113
|
+
sharedBundleMerge: {
|
|
114
|
+
type: 'array',
|
|
115
|
+
items: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: {
|
|
118
|
+
overlapThreshold: {
|
|
119
|
+
type: 'number'
|
|
120
|
+
},
|
|
121
|
+
maxBundleSize: {
|
|
122
|
+
type: 'number'
|
|
123
|
+
},
|
|
124
|
+
sourceBundles: {
|
|
125
|
+
type: 'array',
|
|
126
|
+
items: {
|
|
127
|
+
type: 'string'
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
minBundlesInGroup: {
|
|
131
|
+
type: 'number'
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
additionalProperties: false
|
|
135
|
+
}
|
|
136
|
+
},
|
|
113
137
|
minBundles: {
|
|
114
138
|
type: 'number'
|
|
115
139
|
},
|
|
@@ -192,7 +216,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
192
216
|
return {
|
|
193
217
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
194
218
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
195
|
-
|
|
219
|
+
sharedBundleMerge: modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
|
|
196
220
|
maxParallelRequests: modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
197
221
|
projectRoot: options.projectRoot,
|
|
198
222
|
disableSharedBundles: modeConfig.disableSharedBundles ?? defaults.disableSharedBundles,
|
package/lib/idealGraph.js
CHANGED
|
@@ -66,6 +66,7 @@ const idealBundleGraphEdges = exports.idealBundleGraphEdges = Object.freeze({
|
|
|
66
66
|
// expect from default bundler
|
|
67
67
|
|
|
68
68
|
function createIdealGraph(assetGraph, config, entries, logger) {
|
|
69
|
+
var _config$sharedBundleM;
|
|
69
70
|
// Asset to the bundle and group it's an entry of
|
|
70
71
|
let bundleRoots = new Map();
|
|
71
72
|
let bundles = new Map();
|
|
@@ -184,6 +185,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
184
185
|
};
|
|
185
186
|
}();
|
|
186
187
|
let manualBundleToInternalizedAsset = new (_utils().DefaultMap)(() => []);
|
|
188
|
+
let mergeSourceBundleLookup = new Map();
|
|
189
|
+
let mergeSourceBundleAssets = new Set((_config$sharedBundleM = config.sharedBundleMerge) === null || _config$sharedBundleM === void 0 ? void 0 : _config$sharedBundleM.flatMap(c => {
|
|
190
|
+
var _c$sourceBundles;
|
|
191
|
+
return ((_c$sourceBundles = c.sourceBundles) === null || _c$sourceBundles === void 0 ? void 0 : _c$sourceBundles.map(assetMatch => _path().default.join(config.projectRoot, assetMatch))) ?? [];
|
|
192
|
+
}));
|
|
187
193
|
|
|
188
194
|
/**
|
|
189
195
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
@@ -250,6 +256,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
250
256
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
251
257
|
bundleGroupBundleIds.add(bundleId);
|
|
252
258
|
bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
|
|
259
|
+
// If this asset is relevant for merging then track it's source
|
|
260
|
+
// bundle id for later
|
|
261
|
+
if (mergeSourceBundleAssets.has(childAsset.filePath)) {
|
|
262
|
+
mergeSourceBundleLookup.set(_path().default.relative(config.projectRoot, childAsset.filePath), bundleId);
|
|
263
|
+
}
|
|
253
264
|
if (manualSharedObject) {
|
|
254
265
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
255
266
|
// since MSBs should not have main entry assets
|
|
@@ -851,8 +862,8 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
851
862
|
|
|
852
863
|
// Step merge shared bundles that meet the overlap threshold
|
|
853
864
|
// This step is skipped by default as the threshold defaults to 1
|
|
854
|
-
if (config.
|
|
855
|
-
mergeOverlapBundles();
|
|
865
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
866
|
+
mergeOverlapBundles(config.sharedBundleMerge);
|
|
856
867
|
}
|
|
857
868
|
|
|
858
869
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
@@ -961,6 +972,10 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
961
972
|
let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
|
|
962
973
|
assetReference.set(asset, newAssetReference);
|
|
963
974
|
}
|
|
975
|
+
|
|
976
|
+
// Merge any internalized assets
|
|
977
|
+
(0, _assert().default)(bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets, 'All shared bundles should have internalized assets');
|
|
978
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
964
979
|
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
965
980
|
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
966
981
|
continue;
|
|
@@ -968,18 +983,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
968
983
|
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
969
984
|
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
970
985
|
}
|
|
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
986
|
bundleGraph.removeNode(bundleToRemoveId);
|
|
981
987
|
}
|
|
982
|
-
function mergeOverlapBundles() {
|
|
988
|
+
function mergeOverlapBundles(mergeConfig) {
|
|
983
989
|
// Find all shared bundles
|
|
984
990
|
let sharedBundles = new Set();
|
|
985
991
|
bundleGraph.traverse(nodeId => {
|
|
@@ -998,7 +1004,19 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
998
1004
|
sharedBundles.add(nodeId);
|
|
999
1005
|
}
|
|
1000
1006
|
});
|
|
1001
|
-
let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), config
|
|
1007
|
+
let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), mergeConfig.map(config => {
|
|
1008
|
+
var _config$sourceBundles;
|
|
1009
|
+
return {
|
|
1010
|
+
...config,
|
|
1011
|
+
sourceBundles: (_config$sourceBundles = config.sourceBundles) === null || _config$sourceBundles === void 0 ? void 0 : _config$sourceBundles.map(assetMatch => {
|
|
1012
|
+
let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
|
|
1013
|
+
if (sourceBundleNodeId == null) {
|
|
1014
|
+
throw new Error(`Source bundle ${assetMatch} not found in merge source bundle lookup`);
|
|
1015
|
+
}
|
|
1016
|
+
return sourceBundleNodeId;
|
|
1017
|
+
})
|
|
1018
|
+
};
|
|
1019
|
+
}));
|
|
1002
1020
|
for (let cluster of clusters) {
|
|
1003
1021
|
let [mergeTarget, ...rest] = cluster;
|
|
1004
1022
|
for (let bundleIdToMerge of rest) {
|
package/lib/memoize.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.clearCaches = clearCaches;
|
|
7
|
+
exports.memoize = memoize;
|
|
8
|
+
function _manyKeysMap() {
|
|
9
|
+
const data = _interopRequireDefault(require("many-keys-map"));
|
|
10
|
+
_manyKeysMap = function () {
|
|
11
|
+
return data;
|
|
12
|
+
};
|
|
13
|
+
return data;
|
|
14
|
+
}
|
|
15
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
|
+
// $FlowFixMe
|
|
17
|
+
let caches = [];
|
|
18
|
+
function clearCaches() {
|
|
19
|
+
for (let cache of caches) {
|
|
20
|
+
cache.clear();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function memoize(fn) {
|
|
24
|
+
let cache = new (_manyKeysMap().default)();
|
|
25
|
+
caches.push(cache);
|
|
26
|
+
return function (...args) {
|
|
27
|
+
// Navigate through the cache hierarchy
|
|
28
|
+
let cached = cache.get(args);
|
|
29
|
+
if (cached !== undefined) {
|
|
30
|
+
// If the result is cached, return it
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Calculate the result and cache it
|
|
35
|
+
const result = fn.apply(this, args);
|
|
36
|
+
// $FlowFixMe
|
|
37
|
+
cache.set(args, result);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/bundler-default",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@atlaspack/diagnostic": "2.14.1",
|
|
20
20
|
"@atlaspack/feature-flags": "2.16.0",
|
|
21
|
-
"@atlaspack/graph": "3.
|
|
22
|
-
"@atlaspack/plugin": "2.14.
|
|
23
|
-
"@atlaspack/rust": "3.3.
|
|
24
|
-
"@atlaspack/utils": "2.14.
|
|
25
|
-
"nullthrows": "^1.1.1"
|
|
21
|
+
"@atlaspack/graph": "3.5.0",
|
|
22
|
+
"@atlaspack/plugin": "2.14.10",
|
|
23
|
+
"@atlaspack/rust": "3.3.5",
|
|
24
|
+
"@atlaspack/utils": "2.14.10",
|
|
25
|
+
"nullthrows": "^1.1.1",
|
|
26
|
+
"many-keys-map": "^2.0.1"
|
|
26
27
|
}
|
|
27
28
|
}
|
package/src/bundleMerge.js
CHANGED
|
@@ -2,102 +2,253 @@
|
|
|
2
2
|
|
|
3
3
|
import invariant from 'assert';
|
|
4
4
|
import nullthrows from 'nullthrows';
|
|
5
|
+
import {ContentGraph, BitSet} from '@atlaspack/graph';
|
|
5
6
|
import type {NodeId} from '@atlaspack/graph';
|
|
6
7
|
import type {Bundle, IdealBundleGraph} from './idealGraph';
|
|
7
|
-
import {
|
|
8
|
+
import {memoize, clearCaches} from './memoize';
|
|
9
|
+
|
|
10
|
+
function getBundlesForBundleGroup(
|
|
11
|
+
bundleGraph: IdealBundleGraph,
|
|
12
|
+
bundleGroupId: NodeId,
|
|
13
|
+
): number {
|
|
14
|
+
let count = 0;
|
|
15
|
+
bundleGraph.traverse((nodeId) => {
|
|
16
|
+
if (bundleGraph.getNode(nodeId)?.bundleBehavior !== 'inline') {
|
|
17
|
+
count++;
|
|
18
|
+
}
|
|
19
|
+
}, bundleGroupId);
|
|
20
|
+
return count;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let getBundleOverlapBitSet = (
|
|
24
|
+
sourceBundlesA: BitSet,
|
|
25
|
+
sourceBundlesB: BitSet,
|
|
26
|
+
): number => {
|
|
27
|
+
let allSourceBundles = BitSet.union(sourceBundlesA, sourceBundlesB);
|
|
28
|
+
let sharedSourceBundles = BitSet.intersect(sourceBundlesA, sourceBundlesB);
|
|
29
|
+
|
|
30
|
+
return sharedSourceBundles.size() / allSourceBundles.size();
|
|
31
|
+
};
|
|
8
32
|
|
|
9
33
|
// Returns a decimal showing the proportion source bundles are common to
|
|
10
34
|
// both bundles versus the total number of source bundles.
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
function checkBundleThreshold(
|
|
36
|
+
bundleA: MergeCandidate,
|
|
37
|
+
bundleB: MergeCandidate,
|
|
38
|
+
threshold: number,
|
|
39
|
+
): boolean {
|
|
40
|
+
return (
|
|
41
|
+
getBundleOverlapBitSet(
|
|
42
|
+
bundleA.sourceBundleBitSet,
|
|
43
|
+
bundleB.sourceBundleBitSet,
|
|
44
|
+
) >= threshold
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let checkSharedSourceBundles = memoize(
|
|
49
|
+
(bundle: Bundle, importantAncestorBundles: Array<NodeId>): boolean => {
|
|
50
|
+
return importantAncestorBundles.every((ancestorId) =>
|
|
51
|
+
bundle.sourceBundles.has(ancestorId),
|
|
52
|
+
);
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
let hasSuitableBundleGroup = memoize(
|
|
57
|
+
(
|
|
58
|
+
bundleGraph: IdealBundleGraph,
|
|
59
|
+
bundle: Bundle,
|
|
60
|
+
minBundlesInGroup: number,
|
|
61
|
+
): boolean => {
|
|
62
|
+
for (let sourceBundle of bundle.sourceBundles) {
|
|
63
|
+
let bundlesInGroup = getBundlesForBundleGroup(bundleGraph, sourceBundle);
|
|
64
|
+
|
|
65
|
+
if (bundlesInGroup >= minBundlesInGroup) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
function validMerge(
|
|
74
|
+
bundleGraph: IdealBundleGraph,
|
|
75
|
+
config: MergeGroup,
|
|
76
|
+
bundleA: MergeCandidate,
|
|
77
|
+
bundleB: MergeCandidate,
|
|
78
|
+
): boolean {
|
|
79
|
+
if (config.maxBundleSize != null) {
|
|
80
|
+
if (
|
|
81
|
+
bundleA.bundle.size > config.maxBundleSize ||
|
|
82
|
+
bundleB.bundle.size > config.maxBundleSize
|
|
83
|
+
) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (config.overlapThreshold != null) {
|
|
89
|
+
if (!checkBundleThreshold(bundleA, bundleB, config.overlapThreshold)) {
|
|
90
|
+
return false;
|
|
21
91
|
}
|
|
22
92
|
}
|
|
23
93
|
|
|
24
|
-
|
|
94
|
+
if (config.sourceBundles != null) {
|
|
95
|
+
if (
|
|
96
|
+
!checkSharedSourceBundles(bundleA.bundle, config.sourceBundles) ||
|
|
97
|
+
!checkSharedSourceBundles(bundleB.bundle, config.sourceBundles)
|
|
98
|
+
) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (config.minBundlesInGroup != null) {
|
|
104
|
+
if (
|
|
105
|
+
!hasSuitableBundleGroup(
|
|
106
|
+
bundleGraph,
|
|
107
|
+
bundleA.bundle,
|
|
108
|
+
config.minBundlesInGroup,
|
|
109
|
+
) ||
|
|
110
|
+
!hasSuitableBundleGroup(
|
|
111
|
+
bundleGraph,
|
|
112
|
+
bundleB.bundle,
|
|
113
|
+
config.minBundlesInGroup,
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return true;
|
|
25
121
|
}
|
|
26
122
|
|
|
27
123
|
function getMergeClusters(
|
|
28
|
-
graph: ContentGraph<NodeId>,
|
|
29
|
-
candidates:
|
|
124
|
+
graph: ContentGraph<NodeId, EdgeType>,
|
|
125
|
+
candidates: Map<NodeId, EdgeType>,
|
|
30
126
|
): Array<Array<NodeId>> {
|
|
31
127
|
let clusters = [];
|
|
32
128
|
|
|
33
|
-
for (let candidate of candidates) {
|
|
129
|
+
for (let [candidate, edgeType] of candidates.entries()) {
|
|
34
130
|
let cluster: Array<NodeId> = [];
|
|
35
131
|
|
|
36
|
-
graph.traverse(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
132
|
+
graph.traverse(
|
|
133
|
+
(nodeId) => {
|
|
134
|
+
cluster.push(nullthrows(graph.getNode(nodeId)));
|
|
135
|
+
// Remove node from candidates as it has already been processed
|
|
136
|
+
candidates.delete(nodeId);
|
|
137
|
+
},
|
|
138
|
+
candidate,
|
|
139
|
+
edgeType,
|
|
140
|
+
);
|
|
42
141
|
clusters.push(cluster);
|
|
43
142
|
}
|
|
44
143
|
|
|
45
144
|
return clusters;
|
|
46
145
|
}
|
|
47
146
|
|
|
48
|
-
|
|
147
|
+
type MergeCandidate = {|
|
|
148
|
+
bundle: Bundle,
|
|
149
|
+
id: NodeId,
|
|
150
|
+
sourceBundleBitSet: BitSet,
|
|
151
|
+
contentKey: string,
|
|
152
|
+
|};
|
|
153
|
+
function getPossibleMergeCandidates(
|
|
49
154
|
bundleGraph: IdealBundleGraph,
|
|
50
155
|
bundles: Array<NodeId>,
|
|
51
|
-
|
|
52
|
-
)
|
|
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) {
|
|
156
|
+
): Array<[MergeCandidate, MergeCandidate]> {
|
|
157
|
+
let mergeCandidates = bundles.map((bundleId) => {
|
|
59
158
|
let bundle = bundleGraph.getNode(bundleId);
|
|
60
|
-
invariant(bundle && bundle !== 'root');
|
|
61
|
-
|
|
62
|
-
|
|
159
|
+
invariant(bundle && bundle !== 'root', 'Bundle should exist');
|
|
160
|
+
|
|
161
|
+
let sourceBundleBitSet = new BitSet(bundleGraph.nodes.length);
|
|
162
|
+
for (let sourceBundle of bundle.sourceBundles) {
|
|
163
|
+
sourceBundleBitSet.add(sourceBundle);
|
|
63
164
|
}
|
|
64
165
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
166
|
+
return {
|
|
167
|
+
id: bundleId,
|
|
168
|
+
bundle,
|
|
169
|
+
sourceBundleBitSet,
|
|
170
|
+
contentKey: bundleId.toString(),
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const uniquePairs = [];
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < mergeCandidates.length; i++) {
|
|
177
|
+
for (let j = i + 1; j < mergeCandidates.length; j++) {
|
|
178
|
+
let a = mergeCandidates[i];
|
|
179
|
+
let b = mergeCandidates[j];
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
// $FlowFixMe both bundles will always have internalizedAssets
|
|
183
|
+
a.bundle.internalizedAssets.equals(b.bundle.internalizedAssets)
|
|
184
|
+
) {
|
|
185
|
+
uniquePairs.push([a, b]);
|
|
68
186
|
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return uniquePairs;
|
|
190
|
+
}
|
|
69
191
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
192
|
+
export type MergeGroup = {|
|
|
193
|
+
overlapThreshold?: number,
|
|
194
|
+
maxBundleSize?: number,
|
|
195
|
+
sourceBundles?: Array<NodeId>,
|
|
196
|
+
minBundlesInGroup?: number,
|
|
197
|
+
|};
|
|
198
|
+
type EdgeType = number;
|
|
76
199
|
|
|
77
|
-
|
|
78
|
-
|
|
200
|
+
export function findMergeCandidates(
|
|
201
|
+
bundleGraph: IdealBundleGraph,
|
|
202
|
+
bundles: Array<NodeId>,
|
|
203
|
+
config: Array<MergeGroup>,
|
|
204
|
+
): Array<Array<NodeId>> {
|
|
205
|
+
let graph = new ContentGraph<NodeId, EdgeType>();
|
|
206
|
+
let candidates = new Map<NodeId, EdgeType>();
|
|
79
207
|
|
|
80
|
-
|
|
208
|
+
let allPossibleMergeCandidates = getPossibleMergeCandidates(
|
|
209
|
+
bundleGraph,
|
|
210
|
+
bundles,
|
|
211
|
+
);
|
|
81
212
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
);
|
|
87
|
-
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
88
|
-
otherBundleId.toString(),
|
|
89
|
-
otherBundleId,
|
|
90
|
-
);
|
|
213
|
+
// Build graph of clustered merge candidates
|
|
214
|
+
for (let i = 0; i < config.length; i++) {
|
|
215
|
+
// Ensure edge type coresponds to config index
|
|
216
|
+
let edgeType = i + 1;
|
|
91
217
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
218
|
+
for (let group of allPossibleMergeCandidates) {
|
|
219
|
+
let candidateA = group[0];
|
|
220
|
+
let candidateB = group[1];
|
|
95
221
|
|
|
96
|
-
|
|
97
|
-
|
|
222
|
+
if (!validMerge(bundleGraph, config[i], candidateA, candidateB)) {
|
|
223
|
+
continue;
|
|
98
224
|
}
|
|
225
|
+
|
|
226
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
227
|
+
candidateA.contentKey,
|
|
228
|
+
candidateA.id,
|
|
229
|
+
);
|
|
230
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
231
|
+
candidateB.contentKey,
|
|
232
|
+
candidateB.id,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Add edge in both directions
|
|
236
|
+
graph.addEdge(bundleNode, otherBundleNode, edgeType);
|
|
237
|
+
graph.addEdge(otherBundleNode, bundleNode, edgeType);
|
|
238
|
+
|
|
239
|
+
candidates.set(bundleNode, edgeType);
|
|
240
|
+
candidates.set(otherBundleNode, edgeType);
|
|
99
241
|
}
|
|
242
|
+
|
|
243
|
+
// Remove bundles that have been allocated to a higher priority merge
|
|
244
|
+
allPossibleMergeCandidates = allPossibleMergeCandidates.filter(
|
|
245
|
+
(group) =>
|
|
246
|
+
!graph.hasContentKey(group[0].contentKey) &&
|
|
247
|
+
!graph.hasContentKey(group[1].contentKey),
|
|
248
|
+
);
|
|
100
249
|
}
|
|
101
250
|
|
|
251
|
+
clearCaches();
|
|
252
|
+
|
|
102
253
|
return getMergeClusters(graph, candidates);
|
|
103
254
|
}
|
package/src/bundlerConfig.js
CHANGED
|
@@ -21,6 +21,13 @@ type ManualSharedBundles = Array<{|
|
|
|
21
21
|
split?: number,
|
|
22
22
|
|}>;
|
|
23
23
|
|
|
24
|
+
export type MergeCandidates = Array<{|
|
|
25
|
+
overlapThreshold?: number,
|
|
26
|
+
maxBundleSize?: number,
|
|
27
|
+
sourceBundles?: Array<string>,
|
|
28
|
+
minBundlesInGroup?: number,
|
|
29
|
+
|}>;
|
|
30
|
+
|
|
24
31
|
type BaseBundlerConfig = {|
|
|
25
32
|
http?: number,
|
|
26
33
|
minBundles?: number,
|
|
@@ -29,7 +36,7 @@ type BaseBundlerConfig = {|
|
|
|
29
36
|
disableSharedBundles?: boolean,
|
|
30
37
|
manualSharedBundles?: ManualSharedBundles,
|
|
31
38
|
loadConditionalBundlesInParallel?: boolean,
|
|
32
|
-
|
|
39
|
+
sharedBundleMerge?: MergeCandidates,
|
|
33
40
|
|};
|
|
34
41
|
|
|
35
42
|
type BundlerConfig = {|
|
|
@@ -44,7 +51,7 @@ export type ResolvedBundlerConfig = {|
|
|
|
44
51
|
disableSharedBundles: boolean,
|
|
45
52
|
manualSharedBundles: ManualSharedBundles,
|
|
46
53
|
loadConditionalBundlesInParallel?: boolean,
|
|
47
|
-
|
|
54
|
+
sharedBundleMerge?: MergeCandidates,
|
|
48
55
|
|};
|
|
49
56
|
|
|
50
57
|
function resolveModeConfig(
|
|
@@ -79,7 +86,7 @@ const HTTP_OPTIONS = {
|
|
|
79
86
|
minBundleSize: 30000,
|
|
80
87
|
maxParallelRequests: 6,
|
|
81
88
|
disableSharedBundles: false,
|
|
82
|
-
|
|
89
|
+
sharedBundleMerge: [],
|
|
83
90
|
},
|
|
84
91
|
'2': {
|
|
85
92
|
minBundles: 1,
|
|
@@ -87,7 +94,7 @@ const HTTP_OPTIONS = {
|
|
|
87
94
|
minBundleSize: 20000,
|
|
88
95
|
maxParallelRequests: 25,
|
|
89
96
|
disableSharedBundles: false,
|
|
90
|
-
|
|
97
|
+
sharedBundleMerge: [],
|
|
91
98
|
},
|
|
92
99
|
};
|
|
93
100
|
|
|
@@ -129,6 +136,30 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
129
136
|
additionalProperties: false,
|
|
130
137
|
},
|
|
131
138
|
},
|
|
139
|
+
sharedBundleMerge: {
|
|
140
|
+
type: 'array',
|
|
141
|
+
items: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
overlapThreshold: {
|
|
145
|
+
type: 'number',
|
|
146
|
+
},
|
|
147
|
+
maxBundleSize: {
|
|
148
|
+
type: 'number',
|
|
149
|
+
},
|
|
150
|
+
sourceBundles: {
|
|
151
|
+
type: 'array',
|
|
152
|
+
items: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
minBundlesInGroup: {
|
|
157
|
+
type: 'number',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
additionalProperties: false,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
132
163
|
minBundles: {
|
|
133
164
|
type: 'number',
|
|
134
165
|
},
|
|
@@ -240,9 +271,8 @@ export async function loadBundlerConfig(
|
|
|
240
271
|
return {
|
|
241
272
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
242
273
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
243
|
-
|
|
244
|
-
modeConfig.
|
|
245
|
-
defaults.sharedBundleMergeThreshold,
|
|
274
|
+
sharedBundleMerge:
|
|
275
|
+
modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
|
|
246
276
|
maxParallelRequests:
|
|
247
277
|
modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
248
278
|
projectRoot: options.projectRoot,
|
package/src/idealGraph.js
CHANGED
|
@@ -23,8 +23,8 @@ 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';
|
|
27
|
-
import type {ResolvedBundlerConfig} from './bundlerConfig';
|
|
26
|
+
import {findMergeCandidates, type MergeGroup} from './bundleMerge';
|
|
27
|
+
import type {ResolvedBundlerConfig, MergeCandidates} from './bundlerConfig';
|
|
28
28
|
|
|
29
29
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
30
30
|
type BundleRoot = Asset;
|
|
@@ -245,6 +245,16 @@ export function createIdealGraph(
|
|
|
245
245
|
Array<Asset>,
|
|
246
246
|
> = new DefaultMap(() => []);
|
|
247
247
|
|
|
248
|
+
let mergeSourceBundleLookup = new Map<string, NodeId>();
|
|
249
|
+
let mergeSourceBundleAssets = new Set(
|
|
250
|
+
config.sharedBundleMerge?.flatMap(
|
|
251
|
+
(c) =>
|
|
252
|
+
c.sourceBundles?.map((assetMatch) =>
|
|
253
|
+
path.join(config.projectRoot, assetMatch),
|
|
254
|
+
) ?? [],
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
|
|
248
258
|
/**
|
|
249
259
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
250
260
|
* for asset type changes, parallel, inline, and async or lazy dependencies,
|
|
@@ -335,6 +345,14 @@ export function createIdealGraph(
|
|
|
335
345
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
336
346
|
bundleGroupBundleIds.add(bundleId);
|
|
337
347
|
bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
|
|
348
|
+
// If this asset is relevant for merging then track it's source
|
|
349
|
+
// bundle id for later
|
|
350
|
+
if (mergeSourceBundleAssets.has(childAsset.filePath)) {
|
|
351
|
+
mergeSourceBundleLookup.set(
|
|
352
|
+
path.relative(config.projectRoot, childAsset.filePath),
|
|
353
|
+
bundleId,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
338
356
|
if (manualSharedObject) {
|
|
339
357
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
340
358
|
// since MSBs should not have main entry assets
|
|
@@ -1131,8 +1149,8 @@ export function createIdealGraph(
|
|
|
1131
1149
|
|
|
1132
1150
|
// Step merge shared bundles that meet the overlap threshold
|
|
1133
1151
|
// This step is skipped by default as the threshold defaults to 1
|
|
1134
|
-
if (config.
|
|
1135
|
-
mergeOverlapBundles();
|
|
1152
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
1153
|
+
mergeOverlapBundles(config.sharedBundleMerge);
|
|
1136
1154
|
}
|
|
1137
1155
|
|
|
1138
1156
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
@@ -1278,6 +1296,13 @@ export function createIdealGraph(
|
|
|
1278
1296
|
assetReference.set(asset, newAssetReference);
|
|
1279
1297
|
}
|
|
1280
1298
|
|
|
1299
|
+
// Merge any internalized assets
|
|
1300
|
+
invariant(
|
|
1301
|
+
bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
|
|
1302
|
+
'All shared bundles should have internalized assets',
|
|
1303
|
+
);
|
|
1304
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
1305
|
+
|
|
1281
1306
|
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1282
1307
|
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1283
1308
|
continue;
|
|
@@ -1287,21 +1312,10 @@ export function createIdealGraph(
|
|
|
1287
1312
|
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1288
1313
|
}
|
|
1289
1314
|
|
|
1290
|
-
// Merge any internalized assets
|
|
1291
|
-
if (bundleToRemove.internalizedAssets) {
|
|
1292
|
-
if (bundleToKeep.internalizedAssets) {
|
|
1293
|
-
bundleToKeep.internalizedAssets.union(
|
|
1294
|
-
bundleToRemove.internalizedAssets,
|
|
1295
|
-
);
|
|
1296
|
-
} else {
|
|
1297
|
-
bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
1315
|
bundleGraph.removeNode(bundleToRemoveId);
|
|
1302
1316
|
}
|
|
1303
1317
|
|
|
1304
|
-
function mergeOverlapBundles() {
|
|
1318
|
+
function mergeOverlapBundles(mergeConfig: MergeCandidates) {
|
|
1305
1319
|
// Find all shared bundles
|
|
1306
1320
|
let sharedBundles = new Set<NodeId>();
|
|
1307
1321
|
bundleGraph.traverse((nodeId) => {
|
|
@@ -1331,7 +1345,20 @@ export function createIdealGraph(
|
|
|
1331
1345
|
let clusters = findMergeCandidates(
|
|
1332
1346
|
bundleGraph,
|
|
1333
1347
|
Array.from(sharedBundles),
|
|
1334
|
-
config
|
|
1348
|
+
mergeConfig.map((config): MergeGroup => ({
|
|
1349
|
+
...config,
|
|
1350
|
+
sourceBundles: config.sourceBundles?.map((assetMatch) => {
|
|
1351
|
+
let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
|
|
1352
|
+
|
|
1353
|
+
if (sourceBundleNodeId == null) {
|
|
1354
|
+
throw new Error(
|
|
1355
|
+
`Source bundle ${assetMatch} not found in merge source bundle lookup`,
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
return sourceBundleNodeId;
|
|
1360
|
+
}),
|
|
1361
|
+
})),
|
|
1335
1362
|
);
|
|
1336
1363
|
|
|
1337
1364
|
for (let cluster of clusters) {
|
package/src/memoize.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @flow strict
|
|
2
|
+
|
|
3
|
+
// $FlowFixMe
|
|
4
|
+
import ManyKeysMap from 'many-keys-map';
|
|
5
|
+
|
|
6
|
+
let caches = [];
|
|
7
|
+
|
|
8
|
+
export function clearCaches() {
|
|
9
|
+
for (let cache of caches) {
|
|
10
|
+
cache.clear();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function memoize<Args: Array<mixed>, Return>(
|
|
15
|
+
fn: (...args: Args) => Return,
|
|
16
|
+
): (...args: Args) => Return {
|
|
17
|
+
let cache = new ManyKeysMap();
|
|
18
|
+
caches.push(cache);
|
|
19
|
+
|
|
20
|
+
return function (...args: Args): Return {
|
|
21
|
+
// Navigate through the cache hierarchy
|
|
22
|
+
let cached = cache.get(args);
|
|
23
|
+
if (cached !== undefined) {
|
|
24
|
+
// If the result is cached, return it
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Calculate the result and cache it
|
|
29
|
+
const result = fn.apply(this, args);
|
|
30
|
+
// $FlowFixMe
|
|
31
|
+
cache.set(args, result);
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
}
|