@atlaspack/bundler-default 2.14.5-canary.8 → 2.14.5-canary.81
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 +222 -0
- package/lib/bundleMerge.js +106 -37
- package/lib/bundlerConfig.js +46 -7
- package/lib/idealGraph.js +36 -20
- package/lib/memoize.js +40 -0
- package/package.json +9 -8
- package/src/bundleMerge.js +206 -61
- package/src/bundlerConfig.js +49 -10
- package/src/idealGraph.js +50 -27
- package/src/memoize.js +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,227 @@
|
|
|
1
1
|
# @atlaspack/bundler-default
|
|
2
2
|
|
|
3
|
+
## 3.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#622](https://github.com/atlassian-labs/atlaspack/pull/622) [`e39c6cf`](https://github.com/atlassian-labs/atlaspack/commit/e39c6cf05f7e95ce5420dbcea66f401b1cbd397c) Thanks [@benjervis](https://github.com/benjervis)! - Change the overlap calculation system in findMergeCandidates to improve performance
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`10fbcfb`](https://github.com/atlassian-labs/atlaspack/commit/10fbcfbfa49c7a83da5d7c40983e36e87f524a75), [`85c52d3`](https://github.com/atlassian-labs/atlaspack/commit/85c52d3f7717b3c84a118d18ab98cfbfd71dcbd2), [`e39c6cf`](https://github.com/atlassian-labs/atlaspack/commit/e39c6cf05f7e95ce5420dbcea66f401b1cbd397c)]:
|
|
10
|
+
- @atlaspack/feature-flags@2.18.0
|
|
11
|
+
- @atlaspack/utils@2.15.0
|
|
12
|
+
- @atlaspack/graph@3.5.2
|
|
13
|
+
- @atlaspack/plugin@2.14.12
|
|
14
|
+
|
|
15
|
+
## 3.0.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#613](https://github.com/atlassian-labs/atlaspack/pull/613) [`4ca19d8`](https://github.com/atlassian-labs/atlaspack/commit/4ca19d8060dfcd279183e4039f2ecb43334ac44c) Thanks [@marcins](https://github.com/marcins)! - Ensure that constant modules are correctly included in MSBs even if they wouldn't otherwise be.
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [[`73ea3c4`](https://github.com/atlassian-labs/atlaspack/commit/73ea3c4d85d4401fdd15abcbf988237e890e7ad3), [`b1b3693`](https://github.com/atlassian-labs/atlaspack/commit/b1b369317c66f8a431c170df2ebba4fa5b2e38ef)]:
|
|
22
|
+
- @atlaspack/feature-flags@2.17.0
|
|
23
|
+
- @atlaspack/graph@3.5.1
|
|
24
|
+
- @atlaspack/utils@2.14.11
|
|
25
|
+
- @atlaspack/plugin@2.14.11
|
|
26
|
+
|
|
27
|
+
## 3.0.1
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- [#608](https://github.com/atlassian-labs/atlaspack/pull/608) [`471b99e`](https://github.com/atlassian-labs/atlaspack/commit/471b99e41b4d97328c88f65e90bea284372cb1b0) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Fix require of ES Module error
|
|
32
|
+
|
|
33
|
+
## 3.0.0
|
|
34
|
+
|
|
35
|
+
### Major Changes
|
|
36
|
+
|
|
37
|
+
- [#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
|
|
38
|
+
|
|
39
|
+
This new config replaces the previously released `sharedBundleMergeThreshold`.
|
|
40
|
+
|
|
41
|
+
The following options are available for each merge group.
|
|
42
|
+
|
|
43
|
+
### Options
|
|
44
|
+
|
|
45
|
+
#### overlapThreshold
|
|
46
|
+
|
|
47
|
+
> The same as `sharedBundleMergeThreshold` from #535
|
|
48
|
+
|
|
49
|
+
Merge bundles share a percentage of source bundles
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
"@atlaspack/bundler-default": {
|
|
53
|
+
"sharedBundleMerge": [{
|
|
54
|
+
"overlapThreshold": 0.75
|
|
55
|
+
}]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### maxBundleSize
|
|
60
|
+
|
|
61
|
+
Merge bundles that are smaller than a configured amount of bytes.
|
|
62
|
+
|
|
63
|
+
> Keep in mind these bytes are pre-optimisation
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
"@atlaspack/bundler-default": {
|
|
67
|
+
"sharedBundleMerge": [{
|
|
68
|
+
"maxBundleSize": 20000
|
|
69
|
+
}]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### sourceBundles
|
|
74
|
+
|
|
75
|
+
Merge bundles that share a set of source bundles. The matching is relative to the project root, like how manual shared bundle roots work.
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
"@atlaspack/bundler-default": {
|
|
79
|
+
"sharedBundleMerge": [{
|
|
80
|
+
"sourceBundles": ["src/important-route", "src/important-route-2"]
|
|
81
|
+
}]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### minBundlesInGroup
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
"@atlaspack/bundler-default": {
|
|
91
|
+
"maxParallelRequests": 30,
|
|
92
|
+
"sharedBundleMerge": [{
|
|
93
|
+
"minBundlesInGroup": 30
|
|
94
|
+
}]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Combining options
|
|
99
|
+
|
|
100
|
+
When multiple options are provided, all must be true for a merge to be relevant.
|
|
101
|
+
|
|
102
|
+
For example, merge bundles that are smaller than 20kb and share at least 50% of the same source bundles.
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
"@atlaspack/bundler-default": {
|
|
106
|
+
"sharedBundleMerge": [{
|
|
107
|
+
"overlapThreshold": 0.5,
|
|
108
|
+
"maxBundleSize": 20000
|
|
109
|
+
}]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Multiple merges
|
|
114
|
+
|
|
115
|
+
You can also have multiple merge configs.
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
"@atlaspack/bundler-default": {
|
|
119
|
+
"sharedBundleMerge": [
|
|
120
|
+
{
|
|
121
|
+
"overlapThreshold": 0.75,
|
|
122
|
+
"maxBundleSize": 20000
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"minBundlesInGroup": 30
|
|
126
|
+
"sourceBundles": ["src/important-route", "src/important-route-2"]
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Patch Changes
|
|
133
|
+
|
|
134
|
+
- Updated dependencies [[`1b52b99`](https://github.com/atlassian-labs/atlaspack/commit/1b52b99db4298b04c1a6eb0f97994d75a2d436f9)]:
|
|
135
|
+
- @atlaspack/graph@3.5.0
|
|
136
|
+
|
|
137
|
+
## 2.16.3
|
|
138
|
+
|
|
139
|
+
### Patch Changes
|
|
140
|
+
|
|
141
|
+
- Updated dependencies [[`35fdd4b`](https://github.com/atlassian-labs/atlaspack/commit/35fdd4b52da0af20f74667f7b8adfb2f90279b7c), [`6dd4ccb`](https://github.com/atlassian-labs/atlaspack/commit/6dd4ccb753541de32322d881f973d571dd57e4ca)]:
|
|
142
|
+
- @atlaspack/rust@3.3.5
|
|
143
|
+
- @atlaspack/plugin@2.14.10
|
|
144
|
+
- @atlaspack/utils@2.14.10
|
|
145
|
+
|
|
146
|
+
## 2.16.2
|
|
147
|
+
|
|
148
|
+
### Patch Changes
|
|
149
|
+
|
|
150
|
+
- Updated dependencies [[`124b7ff`](https://github.com/atlassian-labs/atlaspack/commit/124b7fff44f71aac9fbad289a9a9509b3dfc9aaa), [`e052521`](https://github.com/atlassian-labs/atlaspack/commit/e0525210850ed1606146eb86991049cf567c5dec), [`15c6d70`](https://github.com/atlassian-labs/atlaspack/commit/15c6d7000bd89da876bc590aa75b17a619a41896), [`e4d966c`](https://github.com/atlassian-labs/atlaspack/commit/e4d966c3c9c4292c5013372ae65b10d19d4bacc6), [`209692f`](https://github.com/atlassian-labs/atlaspack/commit/209692ffb11eae103a0d65c5e1118a5aa1625818), [`42a775d`](https://github.com/atlassian-labs/atlaspack/commit/42a775de8eec638ad188f3271964170d8c04d84b), [`29c2f10`](https://github.com/atlassian-labs/atlaspack/commit/29c2f106de9679adfb5afa04e1910471dc65a427), [`f4da1e1`](https://github.com/atlassian-labs/atlaspack/commit/f4da1e120e73eeb5e8b8927f05e88f04d6148c7b), [`1ef91fc`](https://github.com/atlassian-labs/atlaspack/commit/1ef91fcc863fdd2831511937083dbbc1263b3d9d)]:
|
|
151
|
+
- @atlaspack/rust@3.3.4
|
|
152
|
+
- @atlaspack/feature-flags@2.16.0
|
|
153
|
+
- @atlaspack/utils@2.14.9
|
|
154
|
+
- @atlaspack/graph@3.4.7
|
|
155
|
+
- @atlaspack/plugin@2.14.9
|
|
156
|
+
|
|
157
|
+
## 2.16.1
|
|
158
|
+
|
|
159
|
+
### Patch Changes
|
|
160
|
+
|
|
161
|
+
- Updated dependencies [[`30f6017`](https://github.com/atlassian-labs/atlaspack/commit/30f60175ba4d272c5fc193973c63bc298584775b), [`1ab0a27`](https://github.com/atlassian-labs/atlaspack/commit/1ab0a275aeca40350415e2b03e7440d1dddc6228), [`b8a4ae8`](https://github.com/atlassian-labs/atlaspack/commit/b8a4ae8f83dc0a83d8b145c5f729936ce52080a3)]:
|
|
162
|
+
- @atlaspack/feature-flags@2.15.1
|
|
163
|
+
- @atlaspack/rust@3.3.3
|
|
164
|
+
- @atlaspack/graph@3.4.6
|
|
165
|
+
- @atlaspack/utils@2.14.8
|
|
166
|
+
- @atlaspack/plugin@2.14.8
|
|
167
|
+
|
|
168
|
+
## 2.16.0
|
|
169
|
+
|
|
170
|
+
### Minor Changes
|
|
171
|
+
|
|
172
|
+
- [#547](https://github.com/atlassian-labs/atlaspack/pull/547) [`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929) Thanks [@benjervis](https://github.com/benjervis)! - Add a feature flag for resolving the configuration for `@atlaspack/bundler-default` from CWD, rather than exclusively from the project root.
|
|
173
|
+
|
|
174
|
+
### Patch Changes
|
|
175
|
+
|
|
176
|
+
- Updated dependencies [[`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929), [`556d6ab`](https://github.com/atlassian-labs/atlaspack/commit/556d6ab8ede759fa7f37fcd3f4da336ef1c55e8f)]:
|
|
177
|
+
- @atlaspack/feature-flags@2.15.0
|
|
178
|
+
- @atlaspack/rust@3.3.2
|
|
179
|
+
- @atlaspack/graph@3.4.5
|
|
180
|
+
- @atlaspack/utils@2.14.7
|
|
181
|
+
- @atlaspack/plugin@2.14.7
|
|
182
|
+
|
|
183
|
+
## 2.15.1
|
|
184
|
+
|
|
185
|
+
### Patch Changes
|
|
186
|
+
|
|
187
|
+
- Updated dependencies [[`e0f5337`](https://github.com/atlassian-labs/atlaspack/commit/e0f533757bd1019dbd108a04952c87da15286e09)]:
|
|
188
|
+
- @atlaspack/feature-flags@2.14.4
|
|
189
|
+
- @atlaspack/rust@3.3.1
|
|
190
|
+
- @atlaspack/graph@3.4.4
|
|
191
|
+
- @atlaspack/utils@2.14.6
|
|
192
|
+
- @atlaspack/plugin@2.14.6
|
|
193
|
+
|
|
194
|
+
## 2.15.0
|
|
195
|
+
|
|
196
|
+
### Minor Changes
|
|
197
|
+
|
|
198
|
+
- [#535](https://github.com/atlassian-labs/atlaspack/pull/535) [`a4bc259`](https://github.com/atlassian-labs/atlaspack/commit/a4bc2590196b6c1e743e4edcb0337e8c4c240ab4) Thanks [@mattcompiles](https://github.com/mattcompiles)! - Add `sharedBundleMergeThreshold` config option
|
|
199
|
+
|
|
200
|
+
In apps with lots of dynamic imports, many shared bundles are often removed
|
|
201
|
+
from the output to prevent an overload in network requests according to the
|
|
202
|
+
`maxParallelRequests` config. In these cases, setting `sharedBundleMergeThreshold` can
|
|
203
|
+
merge shared bundles with a high overlap in their source bundles (bundles that share the bundle).
|
|
204
|
+
This config trades-off potential overfetching to reduce asset duplication.
|
|
205
|
+
|
|
206
|
+
The following config would merge shared bundles that have a 75% or higher overlap in source bundles.
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"@atlaspack/bundler-default": {
|
|
211
|
+
"sharedBundleMergeThreshold": 0.75
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Patch Changes
|
|
217
|
+
|
|
218
|
+
- Updated dependencies [[`11d6f16`](https://github.com/atlassian-labs/atlaspack/commit/11d6f16b6397dee2f217167e5c98b39edb63f7a7), [`e2ba0f6`](https://github.com/atlassian-labs/atlaspack/commit/e2ba0f69702656f3d1ce95ab1454e35062b13b39), [`d2c50c2`](https://github.com/atlassian-labs/atlaspack/commit/d2c50c2c020888b33bb25b8690d9320c2b69e2a6), [`46a90dc`](https://github.com/atlassian-labs/atlaspack/commit/46a90dccd019a26b222c878a92d23acc75dc67c5)]:
|
|
219
|
+
- @atlaspack/feature-flags@2.14.3
|
|
220
|
+
- @atlaspack/rust@3.3.0
|
|
221
|
+
- @atlaspack/graph@3.4.3
|
|
222
|
+
- @atlaspack/utils@2.14.5
|
|
223
|
+
- @atlaspack/plugin@2.14.5
|
|
224
|
+
|
|
3
225
|
## 2.14.4
|
|
4
226
|
|
|
5
227
|
### Patch Changes
|
package/lib/bundleMerge.js
CHANGED
|
@@ -25,67 +25,136 @@ function _graph() {
|
|
|
25
25
|
};
|
|
26
26
|
return data;
|
|
27
27
|
}
|
|
28
|
+
function _utils() {
|
|
29
|
+
const data = require("@atlaspack/utils");
|
|
30
|
+
_utils = function () {
|
|
31
|
+
return data;
|
|
32
|
+
};
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
var _memoize = require("./memoize");
|
|
28
36
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
37
|
+
function getBundlesForBundleGroup(bundleGraph, bundleGroupId) {
|
|
38
|
+
let count = 0;
|
|
39
|
+
bundleGraph.traverse(nodeId => {
|
|
40
|
+
var _bundleGraph$getNode;
|
|
41
|
+
if (((_bundleGraph$getNode = bundleGraph.getNode(nodeId)) === null || _bundleGraph$getNode === void 0 ? void 0 : _bundleGraph$getNode.bundleBehavior) !== 'inline') {
|
|
42
|
+
count++;
|
|
43
|
+
}
|
|
44
|
+
}, bundleGroupId);
|
|
45
|
+
return count;
|
|
46
|
+
}
|
|
47
|
+
let getBundleOverlap = (sourceBundlesA, sourceBundlesB) => {
|
|
48
|
+
let allSourceBundles = (0, _utils().setUnion)(sourceBundlesA, sourceBundlesB);
|
|
49
|
+
let sharedSourceBundles = (0, _utils().setIntersectStatic)(sourceBundlesA, sourceBundlesB);
|
|
50
|
+
return sharedSourceBundles.size / allSourceBundles.size;
|
|
51
|
+
};
|
|
52
|
+
|
|
29
53
|
// Returns a decimal showing the proportion source bundles are common to
|
|
30
54
|
// both bundles versus the total number of source bundles.
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
55
|
+
function checkBundleThreshold(bundleA, bundleB, threshold) {
|
|
56
|
+
return getBundleOverlap(bundleA.bundle.sourceBundles, bundleB.bundle.sourceBundles) >= threshold;
|
|
57
|
+
}
|
|
58
|
+
let checkSharedSourceBundles = (0, _memoize.memoize)((bundle, importantAncestorBundles) => {
|
|
59
|
+
return importantAncestorBundles.every(ancestorId => bundle.sourceBundles.has(ancestorId));
|
|
60
|
+
});
|
|
61
|
+
let hasSuitableBundleGroup = (0, _memoize.memoize)((bundleGraph, bundle, minBundlesInGroup) => {
|
|
62
|
+
for (let sourceBundle of bundle.sourceBundles) {
|
|
63
|
+
let bundlesInGroup = getBundlesForBundleGroup(bundleGraph, sourceBundle);
|
|
64
|
+
if (bundlesInGroup >= minBundlesInGroup) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
});
|
|
70
|
+
function validMerge(bundleGraph, config, bundleA, bundleB) {
|
|
71
|
+
if (config.maxBundleSize != null) {
|
|
72
|
+
if (bundleA.bundle.size > config.maxBundleSize || bundleB.bundle.size > config.maxBundleSize) {
|
|
73
|
+
return false;
|
|
37
74
|
}
|
|
38
75
|
}
|
|
39
|
-
|
|
76
|
+
if (config.overlapThreshold != null) {
|
|
77
|
+
if (!checkBundleThreshold(bundleA, bundleB, config.overlapThreshold)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (config.sourceBundles != null) {
|
|
82
|
+
if (!checkSharedSourceBundles(bundleA.bundle, config.sourceBundles) || !checkSharedSourceBundles(bundleB.bundle, config.sourceBundles)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (config.minBundlesInGroup != null) {
|
|
87
|
+
if (!hasSuitableBundleGroup(bundleGraph, bundleA.bundle, config.minBundlesInGroup) || !hasSuitableBundleGroup(bundleGraph, bundleB.bundle, config.minBundlesInGroup)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
40
92
|
}
|
|
41
93
|
function getMergeClusters(graph, candidates) {
|
|
42
94
|
let clusters = [];
|
|
43
|
-
for (let candidate of candidates) {
|
|
95
|
+
for (let [candidate, edgeType] of candidates.entries()) {
|
|
44
96
|
let cluster = [];
|
|
45
97
|
graph.traverse(nodeId => {
|
|
46
98
|
cluster.push((0, _nullthrows().default)(graph.getNode(nodeId)));
|
|
47
99
|
// Remove node from candidates as it has already been processed
|
|
48
100
|
candidates.delete(nodeId);
|
|
49
|
-
}, candidate);
|
|
101
|
+
}, candidate, edgeType);
|
|
50
102
|
clusters.push(cluster);
|
|
51
103
|
}
|
|
52
104
|
return clusters;
|
|
53
105
|
}
|
|
54
|
-
function
|
|
106
|
+
function getPossibleMergeCandidates(bundleGraph, bundles) {
|
|
107
|
+
let mergeCandidates = bundles.map(bundleId => {
|
|
108
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
109
|
+
(0, _assert().default)(bundle && bundle !== 'root', 'Bundle should exist');
|
|
110
|
+
return {
|
|
111
|
+
id: bundleId,
|
|
112
|
+
bundle,
|
|
113
|
+
contentKey: bundleId.toString()
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
const uniquePairs = [];
|
|
117
|
+
for (let i = 0; i < mergeCandidates.length; i++) {
|
|
118
|
+
for (let j = i + 1; j < mergeCandidates.length; j++) {
|
|
119
|
+
let a = mergeCandidates[i];
|
|
120
|
+
let b = mergeCandidates[j];
|
|
121
|
+
if (
|
|
122
|
+
// $FlowFixMe both bundles will always have internalizedAssets
|
|
123
|
+
a.bundle.internalizedAssets.equals(b.bundle.internalizedAssets)) {
|
|
124
|
+
uniquePairs.push([a, b]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return uniquePairs;
|
|
129
|
+
}
|
|
130
|
+
function findMergeCandidates(bundleGraph, bundles, config) {
|
|
55
131
|
let graph = new (_graph().ContentGraph)();
|
|
56
|
-
let
|
|
57
|
-
let
|
|
132
|
+
let candidates = new Map();
|
|
133
|
+
let allPossibleMergeCandidates = getPossibleMergeCandidates(bundleGraph, bundles);
|
|
58
134
|
|
|
59
135
|
// Build graph of clustered merge candidates
|
|
60
|
-
for (let
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (bundleId === otherBundleId) {
|
|
136
|
+
for (let i = 0; i < config.length; i++) {
|
|
137
|
+
// Ensure edge type coresponds to config index
|
|
138
|
+
let edgeType = i + 1;
|
|
139
|
+
for (let group of allPossibleMergeCandidates) {
|
|
140
|
+
let candidateA = group[0];
|
|
141
|
+
let candidateB = group[1];
|
|
142
|
+
if (!validMerge(bundleGraph, config[i], candidateA, candidateB)) {
|
|
68
143
|
continue;
|
|
69
144
|
}
|
|
70
|
-
let
|
|
71
|
-
|
|
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);
|
|
145
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(candidateA.contentKey, candidateA.id);
|
|
146
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(candidateB.contentKey, candidateB.id);
|
|
81
147
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
148
|
+
// Add edge in both directions
|
|
149
|
+
graph.addEdge(bundleNode, otherBundleNode, edgeType);
|
|
150
|
+
graph.addEdge(otherBundleNode, bundleNode, edgeType);
|
|
151
|
+
candidates.set(bundleNode, edgeType);
|
|
152
|
+
candidates.set(otherBundleNode, edgeType);
|
|
88
153
|
}
|
|
154
|
+
|
|
155
|
+
// Remove bundles that have been allocated to a higher priority merge
|
|
156
|
+
allPossibleMergeCandidates = allPossibleMergeCandidates.filter(group => !graph.hasContentKey(group[0].contentKey) && !graph.hasContentKey(group[1].contentKey));
|
|
89
157
|
}
|
|
158
|
+
(0, _memoize.clearCaches)();
|
|
90
159
|
return getMergeClusters(graph, candidates);
|
|
91
160
|
}
|
package/lib/bundlerConfig.js
CHANGED
|
@@ -11,6 +11,13 @@ function _diagnostic() {
|
|
|
11
11
|
};
|
|
12
12
|
return data;
|
|
13
13
|
}
|
|
14
|
+
function _featureFlags() {
|
|
15
|
+
const data = require("@atlaspack/feature-flags");
|
|
16
|
+
_featureFlags = function () {
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
14
21
|
function _utils() {
|
|
15
22
|
const data = require("@atlaspack/utils");
|
|
16
23
|
_utils = function () {
|
|
@@ -54,7 +61,7 @@ const HTTP_OPTIONS = {
|
|
|
54
61
|
minBundleSize: 30000,
|
|
55
62
|
maxParallelRequests: 6,
|
|
56
63
|
disableSharedBundles: false,
|
|
57
|
-
|
|
64
|
+
sharedBundleMerge: []
|
|
58
65
|
},
|
|
59
66
|
'2': {
|
|
60
67
|
minBundles: 1,
|
|
@@ -62,7 +69,7 @@ const HTTP_OPTIONS = {
|
|
|
62
69
|
minBundleSize: 20000,
|
|
63
70
|
maxParallelRequests: 25,
|
|
64
71
|
disableSharedBundles: false,
|
|
65
|
-
|
|
72
|
+
sharedBundleMerge: []
|
|
66
73
|
}
|
|
67
74
|
};
|
|
68
75
|
const CONFIG_SCHEMA = {
|
|
@@ -103,6 +110,30 @@ const CONFIG_SCHEMA = {
|
|
|
103
110
|
additionalProperties: false
|
|
104
111
|
}
|
|
105
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
|
+
},
|
|
106
137
|
minBundles: {
|
|
107
138
|
type: 'number'
|
|
108
139
|
},
|
|
@@ -125,9 +156,17 @@ const CONFIG_SCHEMA = {
|
|
|
125
156
|
additionalProperties: false
|
|
126
157
|
};
|
|
127
158
|
async function loadBundlerConfig(config, options, logger) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
159
|
+
var _conf;
|
|
160
|
+
let conf;
|
|
161
|
+
if ((0, _featureFlags().getFeatureFlag)('resolveBundlerConfigFromCwd')) {
|
|
162
|
+
conf = await config.getConfigFrom(`${process.cwd()}/index`, [], {
|
|
163
|
+
packageKey: '@atlaspack/bundler-default'
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
conf = await config.getConfig([], {
|
|
167
|
+
packageKey: '@atlaspack/bundler-default'
|
|
168
|
+
});
|
|
169
|
+
}
|
|
131
170
|
if (!conf) {
|
|
132
171
|
const modDefault = {
|
|
133
172
|
...HTTP_OPTIONS['2'],
|
|
@@ -135,7 +174,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
135
174
|
};
|
|
136
175
|
return modDefault;
|
|
137
176
|
}
|
|
138
|
-
(0, _assert().default)((conf === null ||
|
|
177
|
+
(0, _assert().default)(((_conf = conf) === null || _conf === void 0 ? void 0 : _conf.contents) != null);
|
|
139
178
|
let modeConfig = resolveModeConfig(conf.contents, options.mode);
|
|
140
179
|
|
|
141
180
|
// minBundles will be ignored if shared bundles are disabled
|
|
@@ -177,7 +216,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
177
216
|
return {
|
|
178
217
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
179
218
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
180
|
-
|
|
219
|
+
sharedBundleMerge: modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
|
|
181
220
|
maxParallelRequests: modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
182
221
|
projectRoot: options.projectRoot,
|
|
183
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();
|
|
@@ -160,17 +161,15 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
160
161
|
assetGraph.traverse((node, _, actions) => {
|
|
161
162
|
if (node.type === 'asset' && (!Array.isArray(c.types) || c.types.includes(node.value.type))) {
|
|
162
163
|
let projectRelativePath = _path().default.relative(config.projectRoot, node.value.filePath);
|
|
163
|
-
if (!assetRegexes.some(regex => regex.test(projectRelativePath))) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
164
|
|
|
167
165
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
168
166
|
// and need to be assigned to all matching bundles
|
|
169
167
|
if (node.value.meta.isConstantModule === true) {
|
|
170
168
|
constantModuleToMSB.get(node.value).push(c);
|
|
171
169
|
}
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
if (assetRegexes.some(regex => regex.test(projectRelativePath))) {
|
|
171
|
+
manualAssetToConfig.set(node.value, c);
|
|
172
|
+
}
|
|
174
173
|
}
|
|
175
174
|
if (node.type === 'dependency' && (node.value.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional') && parentAsset) {
|
|
176
175
|
// Don't walk past the bundle group assets
|
|
@@ -184,6 +183,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
184
183
|
};
|
|
185
184
|
}();
|
|
186
185
|
let manualBundleToInternalizedAsset = new (_utils().DefaultMap)(() => []);
|
|
186
|
+
let mergeSourceBundleLookup = new Map();
|
|
187
|
+
let mergeSourceBundleAssets = new Set((_config$sharedBundleM = config.sharedBundleMerge) === null || _config$sharedBundleM === void 0 ? void 0 : _config$sharedBundleM.flatMap(c => {
|
|
188
|
+
var _c$sourceBundles;
|
|
189
|
+
return ((_c$sourceBundles = c.sourceBundles) === null || _c$sourceBundles === void 0 ? void 0 : _c$sourceBundles.map(assetMatch => _path().default.join(config.projectRoot, assetMatch))) ?? [];
|
|
190
|
+
}));
|
|
187
191
|
|
|
188
192
|
/**
|
|
189
193
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
@@ -250,6 +254,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
250
254
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
251
255
|
bundleGroupBundleIds.add(bundleId);
|
|
252
256
|
bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
|
|
257
|
+
// If this asset is relevant for merging then track it's source
|
|
258
|
+
// bundle id for later
|
|
259
|
+
if (mergeSourceBundleAssets.has(childAsset.filePath)) {
|
|
260
|
+
mergeSourceBundleLookup.set(_path().default.relative(config.projectRoot, childAsset.filePath), bundleId);
|
|
261
|
+
}
|
|
253
262
|
if (manualSharedObject) {
|
|
254
263
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
255
264
|
// since MSBs should not have main entry assets
|
|
@@ -851,8 +860,8 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
851
860
|
|
|
852
861
|
// Step merge shared bundles that meet the overlap threshold
|
|
853
862
|
// This step is skipped by default as the threshold defaults to 1
|
|
854
|
-
if (config.
|
|
855
|
-
mergeOverlapBundles();
|
|
863
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
864
|
+
mergeOverlapBundles(config.sharedBundleMerge);
|
|
856
865
|
}
|
|
857
866
|
|
|
858
867
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
@@ -952,8 +961,8 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
952
961
|
}
|
|
953
962
|
}
|
|
954
963
|
function mergeBundles(bundleGraph, bundleToKeepId, bundleToRemoveId, assetReference) {
|
|
955
|
-
let bundleToKeep = (0, _nullthrows().default)(bundleGraph.getNode(bundleToKeepId)
|
|
956
|
-
let bundleToRemove = (0, _nullthrows().default)(bundleGraph.getNode(bundleToRemoveId)
|
|
964
|
+
let bundleToKeep = (0, _nullthrows().default)(bundleGraph.getNode(bundleToKeepId));
|
|
965
|
+
let bundleToRemove = (0, _nullthrows().default)(bundleGraph.getNode(bundleToRemoveId));
|
|
957
966
|
(0, _assert().default)(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
958
967
|
for (let asset of bundleToRemove.assets) {
|
|
959
968
|
bundleToKeep.assets.add(asset);
|
|
@@ -961,6 +970,10 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
961
970
|
let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
|
|
962
971
|
assetReference.set(asset, newAssetReference);
|
|
963
972
|
}
|
|
973
|
+
|
|
974
|
+
// Merge any internalized assets
|
|
975
|
+
(0, _assert().default)(bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets, 'All shared bundles should have internalized assets');
|
|
976
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
964
977
|
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
965
978
|
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
966
979
|
continue;
|
|
@@ -968,18 +981,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
968
981
|
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
969
982
|
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
970
983
|
}
|
|
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
984
|
bundleGraph.removeNode(bundleToRemoveId);
|
|
981
985
|
}
|
|
982
|
-
function mergeOverlapBundles() {
|
|
986
|
+
function mergeOverlapBundles(mergeConfig) {
|
|
983
987
|
// Find all shared bundles
|
|
984
988
|
let sharedBundles = new Set();
|
|
985
989
|
bundleGraph.traverse(nodeId => {
|
|
@@ -998,7 +1002,19 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
998
1002
|
sharedBundles.add(nodeId);
|
|
999
1003
|
}
|
|
1000
1004
|
});
|
|
1001
|
-
let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), config
|
|
1005
|
+
let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), mergeConfig.map(config => {
|
|
1006
|
+
var _config$sourceBundles;
|
|
1007
|
+
return {
|
|
1008
|
+
...config,
|
|
1009
|
+
sourceBundles: (_config$sourceBundles = config.sourceBundles) === null || _config$sourceBundles === void 0 ? void 0 : _config$sourceBundles.map(assetMatch => {
|
|
1010
|
+
let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
|
|
1011
|
+
if (sourceBundleNodeId == null) {
|
|
1012
|
+
throw new Error(`Source bundle ${assetMatch} not found in merge source bundle lookup`);
|
|
1013
|
+
}
|
|
1014
|
+
return sourceBundleNodeId;
|
|
1015
|
+
})
|
|
1016
|
+
};
|
|
1017
|
+
}));
|
|
1002
1018
|
for (let cluster of clusters) {
|
|
1003
1019
|
let [mergeTarget, ...rest] = cluster;
|
|
1004
1020
|
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": "2.14.5-canary.
|
|
3
|
+
"version": "2.14.5-canary.81+bb6785315",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,13 +16,14 @@
|
|
|
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.149+bb6785315",
|
|
20
|
+
"@atlaspack/feature-flags": "2.14.1-canary.149+bb6785315",
|
|
21
|
+
"@atlaspack/graph": "3.4.1-canary.149+bb6785315",
|
|
22
|
+
"@atlaspack/plugin": "2.14.5-canary.81+bb6785315",
|
|
23
|
+
"@atlaspack/rust": "3.2.1-canary.81+bb6785315",
|
|
24
|
+
"@atlaspack/utils": "2.14.5-canary.81+bb6785315",
|
|
25
|
+
"many-keys-map": "^1.0.3",
|
|
25
26
|
"nullthrows": "^1.1.1"
|
|
26
27
|
},
|
|
27
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "bb6785315eea7df56b621d9e81cc106836c30d75"
|
|
28
29
|
}
|
package/src/bundleMerge.js
CHANGED
|
@@ -2,102 +2,247 @@
|
|
|
2
2
|
|
|
3
3
|
import invariant from 'assert';
|
|
4
4
|
import nullthrows from 'nullthrows';
|
|
5
|
+
import {ContentGraph} from '@atlaspack/graph';
|
|
5
6
|
import type {NodeId} from '@atlaspack/graph';
|
|
7
|
+
import {setUnion, setIntersectStatic} from '@atlaspack/utils';
|
|
6
8
|
import type {Bundle, IdealBundleGraph} from './idealGraph';
|
|
7
|
-
import {
|
|
9
|
+
import {memoize, clearCaches} from './memoize';
|
|
10
|
+
|
|
11
|
+
function getBundlesForBundleGroup(
|
|
12
|
+
bundleGraph: IdealBundleGraph,
|
|
13
|
+
bundleGroupId: NodeId,
|
|
14
|
+
): number {
|
|
15
|
+
let count = 0;
|
|
16
|
+
bundleGraph.traverse((nodeId) => {
|
|
17
|
+
if (bundleGraph.getNode(nodeId)?.bundleBehavior !== 'inline') {
|
|
18
|
+
count++;
|
|
19
|
+
}
|
|
20
|
+
}, bundleGroupId);
|
|
21
|
+
return count;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let getBundleOverlap = (
|
|
25
|
+
sourceBundlesA: Set<NodeId>,
|
|
26
|
+
sourceBundlesB: Set<NodeId>,
|
|
27
|
+
): number => {
|
|
28
|
+
let allSourceBundles = setUnion(sourceBundlesA, sourceBundlesB);
|
|
29
|
+
let sharedSourceBundles = setIntersectStatic(sourceBundlesA, sourceBundlesB);
|
|
30
|
+
|
|
31
|
+
return sharedSourceBundles.size / allSourceBundles.size;
|
|
32
|
+
};
|
|
8
33
|
|
|
9
34
|
// Returns a decimal showing the proportion source bundles are common to
|
|
10
35
|
// both bundles versus the total number of source bundles.
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
36
|
+
function checkBundleThreshold(
|
|
37
|
+
bundleA: MergeCandidate,
|
|
38
|
+
bundleB: MergeCandidate,
|
|
39
|
+
threshold: number,
|
|
40
|
+
): boolean {
|
|
41
|
+
return (
|
|
42
|
+
getBundleOverlap(
|
|
43
|
+
bundleA.bundle.sourceBundles,
|
|
44
|
+
bundleB.bundle.sourceBundles,
|
|
45
|
+
) >= threshold
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let checkSharedSourceBundles = memoize(
|
|
50
|
+
(bundle: Bundle, importantAncestorBundles: Array<NodeId>): boolean => {
|
|
51
|
+
return importantAncestorBundles.every((ancestorId) =>
|
|
52
|
+
bundle.sourceBundles.has(ancestorId),
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
let hasSuitableBundleGroup = memoize(
|
|
58
|
+
(
|
|
59
|
+
bundleGraph: IdealBundleGraph,
|
|
60
|
+
bundle: Bundle,
|
|
61
|
+
minBundlesInGroup: number,
|
|
62
|
+
): boolean => {
|
|
63
|
+
for (let sourceBundle of bundle.sourceBundles) {
|
|
64
|
+
let bundlesInGroup = getBundlesForBundleGroup(bundleGraph, sourceBundle);
|
|
65
|
+
|
|
66
|
+
if (bundlesInGroup >= minBundlesInGroup) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
function validMerge(
|
|
75
|
+
bundleGraph: IdealBundleGraph,
|
|
76
|
+
config: MergeGroup,
|
|
77
|
+
bundleA: MergeCandidate,
|
|
78
|
+
bundleB: MergeCandidate,
|
|
79
|
+
): boolean {
|
|
80
|
+
if (config.maxBundleSize != null) {
|
|
81
|
+
if (
|
|
82
|
+
bundleA.bundle.size > config.maxBundleSize ||
|
|
83
|
+
bundleB.bundle.size > config.maxBundleSize
|
|
84
|
+
) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (config.overlapThreshold != null) {
|
|
90
|
+
if (!checkBundleThreshold(bundleA, bundleB, config.overlapThreshold)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (config.sourceBundles != null) {
|
|
96
|
+
if (
|
|
97
|
+
!checkSharedSourceBundles(bundleA.bundle, config.sourceBundles) ||
|
|
98
|
+
!checkSharedSourceBundles(bundleB.bundle, config.sourceBundles)
|
|
99
|
+
) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (config.minBundlesInGroup != null) {
|
|
105
|
+
if (
|
|
106
|
+
!hasSuitableBundleGroup(
|
|
107
|
+
bundleGraph,
|
|
108
|
+
bundleA.bundle,
|
|
109
|
+
config.minBundlesInGroup,
|
|
110
|
+
) ||
|
|
111
|
+
!hasSuitableBundleGroup(
|
|
112
|
+
bundleGraph,
|
|
113
|
+
bundleB.bundle,
|
|
114
|
+
config.minBundlesInGroup,
|
|
115
|
+
)
|
|
116
|
+
) {
|
|
117
|
+
return false;
|
|
21
118
|
}
|
|
22
119
|
}
|
|
23
120
|
|
|
24
|
-
return
|
|
121
|
+
return true;
|
|
25
122
|
}
|
|
26
123
|
|
|
27
124
|
function getMergeClusters(
|
|
28
|
-
graph: ContentGraph<NodeId>,
|
|
29
|
-
candidates:
|
|
125
|
+
graph: ContentGraph<NodeId, EdgeType>,
|
|
126
|
+
candidates: Map<NodeId, EdgeType>,
|
|
30
127
|
): Array<Array<NodeId>> {
|
|
31
128
|
let clusters = [];
|
|
32
129
|
|
|
33
|
-
for (let candidate of candidates) {
|
|
130
|
+
for (let [candidate, edgeType] of candidates.entries()) {
|
|
34
131
|
let cluster: Array<NodeId> = [];
|
|
35
132
|
|
|
36
|
-
graph.traverse(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
133
|
+
graph.traverse(
|
|
134
|
+
(nodeId) => {
|
|
135
|
+
cluster.push(nullthrows(graph.getNode(nodeId)));
|
|
136
|
+
// Remove node from candidates as it has already been processed
|
|
137
|
+
candidates.delete(nodeId);
|
|
138
|
+
},
|
|
139
|
+
candidate,
|
|
140
|
+
edgeType,
|
|
141
|
+
);
|
|
42
142
|
clusters.push(cluster);
|
|
43
143
|
}
|
|
44
144
|
|
|
45
145
|
return clusters;
|
|
46
146
|
}
|
|
47
147
|
|
|
48
|
-
|
|
148
|
+
type MergeCandidate = {|
|
|
149
|
+
bundle: Bundle,
|
|
150
|
+
id: NodeId,
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
159
|
+
invariant(bundle && bundle !== 'root', 'Bundle should exist');
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
id: bundleId,
|
|
163
|
+
bundle,
|
|
164
|
+
contentKey: bundleId.toString(),
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const uniquePairs = [];
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < mergeCandidates.length; i++) {
|
|
171
|
+
for (let j = i + 1; j < mergeCandidates.length; j++) {
|
|
172
|
+
let a = mergeCandidates[i];
|
|
173
|
+
let b = mergeCandidates[j];
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
// $FlowFixMe both bundles will always have internalizedAssets
|
|
177
|
+
a.bundle.internalizedAssets.equals(b.bundle.internalizedAssets)
|
|
178
|
+
) {
|
|
179
|
+
uniquePairs.push([a, b]);
|
|
68
180
|
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return uniquePairs;
|
|
184
|
+
}
|
|
69
185
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
186
|
+
export type MergeGroup = {|
|
|
187
|
+
overlapThreshold?: number,
|
|
188
|
+
maxBundleSize?: number,
|
|
189
|
+
sourceBundles?: Array<NodeId>,
|
|
190
|
+
minBundlesInGroup?: number,
|
|
191
|
+
|};
|
|
192
|
+
type EdgeType = number;
|
|
76
193
|
|
|
77
|
-
|
|
78
|
-
|
|
194
|
+
export function findMergeCandidates(
|
|
195
|
+
bundleGraph: IdealBundleGraph,
|
|
196
|
+
bundles: Array<NodeId>,
|
|
197
|
+
config: Array<MergeGroup>,
|
|
198
|
+
): Array<Array<NodeId>> {
|
|
199
|
+
let graph = new ContentGraph<NodeId, EdgeType>();
|
|
200
|
+
let candidates = new Map<NodeId, EdgeType>();
|
|
79
201
|
|
|
80
|
-
|
|
202
|
+
let allPossibleMergeCandidates = getPossibleMergeCandidates(
|
|
203
|
+
bundleGraph,
|
|
204
|
+
bundles,
|
|
205
|
+
);
|
|
81
206
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
);
|
|
87
|
-
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
88
|
-
otherBundleId.toString(),
|
|
89
|
-
otherBundleId,
|
|
90
|
-
);
|
|
207
|
+
// Build graph of clustered merge candidates
|
|
208
|
+
for (let i = 0; i < config.length; i++) {
|
|
209
|
+
// Ensure edge type coresponds to config index
|
|
210
|
+
let edgeType = i + 1;
|
|
91
211
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
212
|
+
for (let group of allPossibleMergeCandidates) {
|
|
213
|
+
let candidateA = group[0];
|
|
214
|
+
let candidateB = group[1];
|
|
95
215
|
|
|
96
|
-
|
|
97
|
-
|
|
216
|
+
if (!validMerge(bundleGraph, config[i], candidateA, candidateB)) {
|
|
217
|
+
continue;
|
|
98
218
|
}
|
|
219
|
+
|
|
220
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
221
|
+
candidateA.contentKey,
|
|
222
|
+
candidateA.id,
|
|
223
|
+
);
|
|
224
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
225
|
+
candidateB.contentKey,
|
|
226
|
+
candidateB.id,
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Add edge in both directions
|
|
230
|
+
graph.addEdge(bundleNode, otherBundleNode, edgeType);
|
|
231
|
+
graph.addEdge(otherBundleNode, bundleNode, edgeType);
|
|
232
|
+
|
|
233
|
+
candidates.set(bundleNode, edgeType);
|
|
234
|
+
candidates.set(otherBundleNode, edgeType);
|
|
99
235
|
}
|
|
236
|
+
|
|
237
|
+
// Remove bundles that have been allocated to a higher priority merge
|
|
238
|
+
allPossibleMergeCandidates = allPossibleMergeCandidates.filter(
|
|
239
|
+
(group) =>
|
|
240
|
+
!graph.hasContentKey(group[0].contentKey) &&
|
|
241
|
+
!graph.hasContentKey(group[1].contentKey),
|
|
242
|
+
);
|
|
100
243
|
}
|
|
101
244
|
|
|
245
|
+
clearCaches();
|
|
246
|
+
|
|
102
247
|
return getMergeClusters(graph, candidates);
|
|
103
248
|
}
|
package/src/bundlerConfig.js
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
BuildMode,
|
|
8
8
|
PluginLogger,
|
|
9
9
|
} from '@atlaspack/types';
|
|
10
|
+
import {getFeatureFlag} from '@atlaspack/feature-flags';
|
|
10
11
|
import {type SchemaEntity, validateSchema} from '@atlaspack/utils';
|
|
11
12
|
import invariant from 'assert';
|
|
12
13
|
|
|
@@ -20,6 +21,13 @@ type ManualSharedBundles = Array<{|
|
|
|
20
21
|
split?: number,
|
|
21
22
|
|}>;
|
|
22
23
|
|
|
24
|
+
export type MergeCandidates = Array<{|
|
|
25
|
+
overlapThreshold?: number,
|
|
26
|
+
maxBundleSize?: number,
|
|
27
|
+
sourceBundles?: Array<string>,
|
|
28
|
+
minBundlesInGroup?: number,
|
|
29
|
+
|}>;
|
|
30
|
+
|
|
23
31
|
type BaseBundlerConfig = {|
|
|
24
32
|
http?: number,
|
|
25
33
|
minBundles?: number,
|
|
@@ -28,7 +36,7 @@ type BaseBundlerConfig = {|
|
|
|
28
36
|
disableSharedBundles?: boolean,
|
|
29
37
|
manualSharedBundles?: ManualSharedBundles,
|
|
30
38
|
loadConditionalBundlesInParallel?: boolean,
|
|
31
|
-
|
|
39
|
+
sharedBundleMerge?: MergeCandidates,
|
|
32
40
|
|};
|
|
33
41
|
|
|
34
42
|
type BundlerConfig = {|
|
|
@@ -43,7 +51,7 @@ export type ResolvedBundlerConfig = {|
|
|
|
43
51
|
disableSharedBundles: boolean,
|
|
44
52
|
manualSharedBundles: ManualSharedBundles,
|
|
45
53
|
loadConditionalBundlesInParallel?: boolean,
|
|
46
|
-
|
|
54
|
+
sharedBundleMerge?: MergeCandidates,
|
|
47
55
|
|};
|
|
48
56
|
|
|
49
57
|
function resolveModeConfig(
|
|
@@ -78,7 +86,7 @@ const HTTP_OPTIONS = {
|
|
|
78
86
|
minBundleSize: 30000,
|
|
79
87
|
maxParallelRequests: 6,
|
|
80
88
|
disableSharedBundles: false,
|
|
81
|
-
|
|
89
|
+
sharedBundleMerge: [],
|
|
82
90
|
},
|
|
83
91
|
'2': {
|
|
84
92
|
minBundles: 1,
|
|
@@ -86,7 +94,7 @@ const HTTP_OPTIONS = {
|
|
|
86
94
|
minBundleSize: 20000,
|
|
87
95
|
maxParallelRequests: 25,
|
|
88
96
|
disableSharedBundles: false,
|
|
89
|
-
|
|
97
|
+
sharedBundleMerge: [],
|
|
90
98
|
},
|
|
91
99
|
};
|
|
92
100
|
|
|
@@ -128,6 +136,30 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
128
136
|
additionalProperties: false,
|
|
129
137
|
},
|
|
130
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
|
+
},
|
|
131
163
|
minBundles: {
|
|
132
164
|
type: 'number',
|
|
133
165
|
},
|
|
@@ -155,9 +187,17 @@ export async function loadBundlerConfig(
|
|
|
155
187
|
options: PluginOptions,
|
|
156
188
|
logger: PluginLogger,
|
|
157
189
|
): Promise<ResolvedBundlerConfig> {
|
|
158
|
-
let conf
|
|
159
|
-
|
|
160
|
-
|
|
190
|
+
let conf;
|
|
191
|
+
|
|
192
|
+
if (getFeatureFlag('resolveBundlerConfigFromCwd')) {
|
|
193
|
+
conf = await config.getConfigFrom(`${process.cwd()}/index`, [], {
|
|
194
|
+
packageKey: '@atlaspack/bundler-default',
|
|
195
|
+
});
|
|
196
|
+
} else {
|
|
197
|
+
conf = await config.getConfig<BundlerConfig>([], {
|
|
198
|
+
packageKey: '@atlaspack/bundler-default',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
161
201
|
|
|
162
202
|
if (!conf) {
|
|
163
203
|
const modDefault = {
|
|
@@ -231,9 +271,8 @@ export async function loadBundlerConfig(
|
|
|
231
271
|
return {
|
|
232
272
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
233
273
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
234
|
-
|
|
235
|
-
modeConfig.
|
|
236
|
-
defaults.sharedBundleMergeThreshold,
|
|
274
|
+
sharedBundleMerge:
|
|
275
|
+
modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
|
|
237
276
|
maxParallelRequests:
|
|
238
277
|
modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
239
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;
|
|
@@ -204,17 +204,16 @@ export function createIdealGraph(
|
|
|
204
204
|
config.projectRoot,
|
|
205
205
|
node.value.filePath,
|
|
206
206
|
);
|
|
207
|
-
if (!assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
207
|
|
|
211
208
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
212
209
|
// and need to be assigned to all matching bundles
|
|
213
210
|
if (node.value.meta.isConstantModule === true) {
|
|
214
211
|
constantModuleToMSB.get(node.value).push(c);
|
|
215
212
|
}
|
|
216
|
-
|
|
217
|
-
|
|
213
|
+
|
|
214
|
+
if (assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
215
|
+
manualAssetToConfig.set(node.value, c);
|
|
216
|
+
}
|
|
218
217
|
}
|
|
219
218
|
|
|
220
219
|
if (
|
|
@@ -245,6 +244,16 @@ export function createIdealGraph(
|
|
|
245
244
|
Array<Asset>,
|
|
246
245
|
> = new DefaultMap(() => []);
|
|
247
246
|
|
|
247
|
+
let mergeSourceBundleLookup = new Map<string, NodeId>();
|
|
248
|
+
let mergeSourceBundleAssets = new Set(
|
|
249
|
+
config.sharedBundleMerge?.flatMap(
|
|
250
|
+
(c) =>
|
|
251
|
+
c.sourceBundles?.map((assetMatch) =>
|
|
252
|
+
path.join(config.projectRoot, assetMatch),
|
|
253
|
+
) ?? [],
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
|
|
248
257
|
/**
|
|
249
258
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
250
259
|
* for asset type changes, parallel, inline, and async or lazy dependencies,
|
|
@@ -335,6 +344,14 @@ export function createIdealGraph(
|
|
|
335
344
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
336
345
|
bundleGroupBundleIds.add(bundleId);
|
|
337
346
|
bundleGraph.addEdge(bundleGraphRootNodeId, bundleId);
|
|
347
|
+
// If this asset is relevant for merging then track it's source
|
|
348
|
+
// bundle id for later
|
|
349
|
+
if (mergeSourceBundleAssets.has(childAsset.filePath)) {
|
|
350
|
+
mergeSourceBundleLookup.set(
|
|
351
|
+
path.relative(config.projectRoot, childAsset.filePath),
|
|
352
|
+
bundleId,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
338
355
|
if (manualSharedObject) {
|
|
339
356
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
340
357
|
// since MSBs should not have main entry assets
|
|
@@ -1131,8 +1148,8 @@ export function createIdealGraph(
|
|
|
1131
1148
|
|
|
1132
1149
|
// Step merge shared bundles that meet the overlap threshold
|
|
1133
1150
|
// This step is skipped by default as the threshold defaults to 1
|
|
1134
|
-
if (config.
|
|
1135
|
-
mergeOverlapBundles();
|
|
1151
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
1152
|
+
mergeOverlapBundles(config.sharedBundleMerge);
|
|
1136
1153
|
}
|
|
1137
1154
|
|
|
1138
1155
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
@@ -1262,11 +1279,8 @@ export function createIdealGraph(
|
|
|
1262
1279
|
bundleToRemoveId: NodeId,
|
|
1263
1280
|
assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>,
|
|
1264
1281
|
) {
|
|
1265
|
-
let bundleToKeep = nullthrows(bundleGraph.getNode(bundleToKeepId)
|
|
1266
|
-
let bundleToRemove = nullthrows(
|
|
1267
|
-
bundleGraph.getNode(bundleToRemoveId),
|
|
1268
|
-
'19',
|
|
1269
|
-
);
|
|
1282
|
+
let bundleToKeep = nullthrows(bundleGraph.getNode(bundleToKeepId));
|
|
1283
|
+
let bundleToRemove = nullthrows(bundleGraph.getNode(bundleToRemoveId));
|
|
1270
1284
|
invariant(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
1271
1285
|
for (let asset of bundleToRemove.assets) {
|
|
1272
1286
|
bundleToKeep.assets.add(asset);
|
|
@@ -1281,6 +1295,13 @@ export function createIdealGraph(
|
|
|
1281
1295
|
assetReference.set(asset, newAssetReference);
|
|
1282
1296
|
}
|
|
1283
1297
|
|
|
1298
|
+
// Merge any internalized assets
|
|
1299
|
+
invariant(
|
|
1300
|
+
bundleToKeep.internalizedAssets && bundleToRemove.internalizedAssets,
|
|
1301
|
+
'All shared bundles should have internalized assets',
|
|
1302
|
+
);
|
|
1303
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
1304
|
+
|
|
1284
1305
|
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1285
1306
|
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1286
1307
|
continue;
|
|
@@ -1290,21 +1311,10 @@ export function createIdealGraph(
|
|
|
1290
1311
|
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1291
1312
|
}
|
|
1292
1313
|
|
|
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
1314
|
bundleGraph.removeNode(bundleToRemoveId);
|
|
1305
1315
|
}
|
|
1306
1316
|
|
|
1307
|
-
function mergeOverlapBundles() {
|
|
1317
|
+
function mergeOverlapBundles(mergeConfig: MergeCandidates) {
|
|
1308
1318
|
// Find all shared bundles
|
|
1309
1319
|
let sharedBundles = new Set<NodeId>();
|
|
1310
1320
|
bundleGraph.traverse((nodeId) => {
|
|
@@ -1334,7 +1344,20 @@ export function createIdealGraph(
|
|
|
1334
1344
|
let clusters = findMergeCandidates(
|
|
1335
1345
|
bundleGraph,
|
|
1336
1346
|
Array.from(sharedBundles),
|
|
1337
|
-
config
|
|
1347
|
+
mergeConfig.map((config): MergeGroup => ({
|
|
1348
|
+
...config,
|
|
1349
|
+
sourceBundles: config.sourceBundles?.map((assetMatch) => {
|
|
1350
|
+
let sourceBundleNodeId = mergeSourceBundleLookup.get(assetMatch);
|
|
1351
|
+
|
|
1352
|
+
if (sourceBundleNodeId == null) {
|
|
1353
|
+
throw new Error(
|
|
1354
|
+
`Source bundle ${assetMatch} not found in merge source bundle lookup`,
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
return sourceBundleNodeId;
|
|
1359
|
+
}),
|
|
1360
|
+
})),
|
|
1338
1361
|
);
|
|
1339
1362
|
|
|
1340
1363
|
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
|
+
}
|