@atlaspack/bundler-default 2.14.5-canary.0 → 2.14.5-canary.100
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 +252 -0
- package/lib/bundleMerge.js +160 -0
- package/lib/bundlerConfig.js +51 -6
- package/lib/idealGraph.js +84 -6
- package/lib/memoize.js +40 -0
- package/package.json +9 -8
- package/src/bundleMerge.js +248 -0
- package/src/bundlerConfig.js +52 -3
- package/src/idealGraph.js +128 -8
- package/src/memoize.js +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,257 @@
|
|
|
1
1
|
# @atlaspack/bundler-default
|
|
2
2
|
|
|
3
|
+
## 3.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`5ded263`](https://github.com/atlassian-labs/atlaspack/commit/5ded263c7f11b866e8885b81c73e20dd060b25be)]:
|
|
8
|
+
- @atlaspack/feature-flags@2.18.3
|
|
9
|
+
- @atlaspack/graph@3.5.5
|
|
10
|
+
- @atlaspack/utils@2.15.3
|
|
11
|
+
- @atlaspack/plugin@2.14.15
|
|
12
|
+
|
|
13
|
+
## 3.0.5
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [[`644b157`](https://github.com/atlassian-labs/atlaspack/commit/644b157dee72a871acc2d0facf0b87b8eea51956)]:
|
|
18
|
+
- @atlaspack/feature-flags@2.18.2
|
|
19
|
+
- @atlaspack/graph@3.5.4
|
|
20
|
+
- @atlaspack/utils@2.15.2
|
|
21
|
+
- @atlaspack/plugin@2.14.14
|
|
22
|
+
|
|
23
|
+
## 3.0.4
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [[`26aa9c5`](https://github.com/atlassian-labs/atlaspack/commit/26aa9c599d2be45ce1438a74c5fa22f39b9b554b), [`0501255`](https://github.com/atlassian-labs/atlaspack/commit/05012550da35b05ce7d356a8cc29311e7f9afdca)]:
|
|
28
|
+
- @atlaspack/feature-flags@2.18.1
|
|
29
|
+
- @atlaspack/utils@2.15.1
|
|
30
|
+
- @atlaspack/graph@3.5.3
|
|
31
|
+
- @atlaspack/plugin@2.14.13
|
|
32
|
+
|
|
33
|
+
## 3.0.3
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- [#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
|
|
38
|
+
|
|
39
|
+
- 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)]:
|
|
40
|
+
- @atlaspack/feature-flags@2.18.0
|
|
41
|
+
- @atlaspack/utils@2.15.0
|
|
42
|
+
- @atlaspack/graph@3.5.2
|
|
43
|
+
- @atlaspack/plugin@2.14.12
|
|
44
|
+
|
|
45
|
+
## 3.0.2
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- [#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.
|
|
50
|
+
|
|
51
|
+
- Updated dependencies [[`73ea3c4`](https://github.com/atlassian-labs/atlaspack/commit/73ea3c4d85d4401fdd15abcbf988237e890e7ad3), [`b1b3693`](https://github.com/atlassian-labs/atlaspack/commit/b1b369317c66f8a431c170df2ebba4fa5b2e38ef)]:
|
|
52
|
+
- @atlaspack/feature-flags@2.17.0
|
|
53
|
+
- @atlaspack/graph@3.5.1
|
|
54
|
+
- @atlaspack/utils@2.14.11
|
|
55
|
+
- @atlaspack/plugin@2.14.11
|
|
56
|
+
|
|
57
|
+
## 3.0.1
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- [#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
|
|
62
|
+
|
|
63
|
+
## 3.0.0
|
|
64
|
+
|
|
65
|
+
### Major Changes
|
|
66
|
+
|
|
67
|
+
- [#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
|
|
68
|
+
|
|
69
|
+
This new config replaces the previously released `sharedBundleMergeThreshold`.
|
|
70
|
+
|
|
71
|
+
The following options are available for each merge group.
|
|
72
|
+
|
|
73
|
+
### Options
|
|
74
|
+
|
|
75
|
+
#### overlapThreshold
|
|
76
|
+
|
|
77
|
+
> The same as `sharedBundleMergeThreshold` from #535
|
|
78
|
+
|
|
79
|
+
Merge bundles share a percentage of source bundles
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
"@atlaspack/bundler-default": {
|
|
83
|
+
"sharedBundleMerge": [{
|
|
84
|
+
"overlapThreshold": 0.75
|
|
85
|
+
}]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### maxBundleSize
|
|
90
|
+
|
|
91
|
+
Merge bundles that are smaller than a configured amount of bytes.
|
|
92
|
+
|
|
93
|
+
> Keep in mind these bytes are pre-optimisation
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
"@atlaspack/bundler-default": {
|
|
97
|
+
"sharedBundleMerge": [{
|
|
98
|
+
"maxBundleSize": 20000
|
|
99
|
+
}]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### sourceBundles
|
|
104
|
+
|
|
105
|
+
Merge bundles that share a set of source bundles. The matching is relative to the project root, like how manual shared bundle roots work.
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
"@atlaspack/bundler-default": {
|
|
109
|
+
"sharedBundleMerge": [{
|
|
110
|
+
"sourceBundles": ["src/important-route", "src/important-route-2"]
|
|
111
|
+
}]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### minBundlesInGroup
|
|
116
|
+
|
|
117
|
+
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.
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
"@atlaspack/bundler-default": {
|
|
121
|
+
"maxParallelRequests": 30,
|
|
122
|
+
"sharedBundleMerge": [{
|
|
123
|
+
"minBundlesInGroup": 30
|
|
124
|
+
}]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Combining options
|
|
129
|
+
|
|
130
|
+
When multiple options are provided, all must be true for a merge to be relevant.
|
|
131
|
+
|
|
132
|
+
For example, merge bundles that are smaller than 20kb and share at least 50% of the same source bundles.
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
"@atlaspack/bundler-default": {
|
|
136
|
+
"sharedBundleMerge": [{
|
|
137
|
+
"overlapThreshold": 0.5,
|
|
138
|
+
"maxBundleSize": 20000
|
|
139
|
+
}]
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Multiple merges
|
|
144
|
+
|
|
145
|
+
You can also have multiple merge configs.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
"@atlaspack/bundler-default": {
|
|
149
|
+
"sharedBundleMerge": [
|
|
150
|
+
{
|
|
151
|
+
"overlapThreshold": 0.75,
|
|
152
|
+
"maxBundleSize": 20000
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"minBundlesInGroup": 30
|
|
156
|
+
"sourceBundles": ["src/important-route", "src/important-route-2"]
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Patch Changes
|
|
163
|
+
|
|
164
|
+
- Updated dependencies [[`1b52b99`](https://github.com/atlassian-labs/atlaspack/commit/1b52b99db4298b04c1a6eb0f97994d75a2d436f9)]:
|
|
165
|
+
- @atlaspack/graph@3.5.0
|
|
166
|
+
|
|
167
|
+
## 2.16.3
|
|
168
|
+
|
|
169
|
+
### Patch Changes
|
|
170
|
+
|
|
171
|
+
- Updated dependencies [[`35fdd4b`](https://github.com/atlassian-labs/atlaspack/commit/35fdd4b52da0af20f74667f7b8adfb2f90279b7c), [`6dd4ccb`](https://github.com/atlassian-labs/atlaspack/commit/6dd4ccb753541de32322d881f973d571dd57e4ca)]:
|
|
172
|
+
- @atlaspack/rust@3.3.5
|
|
173
|
+
- @atlaspack/plugin@2.14.10
|
|
174
|
+
- @atlaspack/utils@2.14.10
|
|
175
|
+
|
|
176
|
+
## 2.16.2
|
|
177
|
+
|
|
178
|
+
### Patch Changes
|
|
179
|
+
|
|
180
|
+
- 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)]:
|
|
181
|
+
- @atlaspack/rust@3.3.4
|
|
182
|
+
- @atlaspack/feature-flags@2.16.0
|
|
183
|
+
- @atlaspack/utils@2.14.9
|
|
184
|
+
- @atlaspack/graph@3.4.7
|
|
185
|
+
- @atlaspack/plugin@2.14.9
|
|
186
|
+
|
|
187
|
+
## 2.16.1
|
|
188
|
+
|
|
189
|
+
### Patch Changes
|
|
190
|
+
|
|
191
|
+
- 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)]:
|
|
192
|
+
- @atlaspack/feature-flags@2.15.1
|
|
193
|
+
- @atlaspack/rust@3.3.3
|
|
194
|
+
- @atlaspack/graph@3.4.6
|
|
195
|
+
- @atlaspack/utils@2.14.8
|
|
196
|
+
- @atlaspack/plugin@2.14.8
|
|
197
|
+
|
|
198
|
+
## 2.16.0
|
|
199
|
+
|
|
200
|
+
### Minor Changes
|
|
201
|
+
|
|
202
|
+
- [#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.
|
|
203
|
+
|
|
204
|
+
### Patch Changes
|
|
205
|
+
|
|
206
|
+
- Updated dependencies [[`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929), [`556d6ab`](https://github.com/atlassian-labs/atlaspack/commit/556d6ab8ede759fa7f37fcd3f4da336ef1c55e8f)]:
|
|
207
|
+
- @atlaspack/feature-flags@2.15.0
|
|
208
|
+
- @atlaspack/rust@3.3.2
|
|
209
|
+
- @atlaspack/graph@3.4.5
|
|
210
|
+
- @atlaspack/utils@2.14.7
|
|
211
|
+
- @atlaspack/plugin@2.14.7
|
|
212
|
+
|
|
213
|
+
## 2.15.1
|
|
214
|
+
|
|
215
|
+
### Patch Changes
|
|
216
|
+
|
|
217
|
+
- Updated dependencies [[`e0f5337`](https://github.com/atlassian-labs/atlaspack/commit/e0f533757bd1019dbd108a04952c87da15286e09)]:
|
|
218
|
+
- @atlaspack/feature-flags@2.14.4
|
|
219
|
+
- @atlaspack/rust@3.3.1
|
|
220
|
+
- @atlaspack/graph@3.4.4
|
|
221
|
+
- @atlaspack/utils@2.14.6
|
|
222
|
+
- @atlaspack/plugin@2.14.6
|
|
223
|
+
|
|
224
|
+
## 2.15.0
|
|
225
|
+
|
|
226
|
+
### Minor Changes
|
|
227
|
+
|
|
228
|
+
- [#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
|
|
229
|
+
|
|
230
|
+
In apps with lots of dynamic imports, many shared bundles are often removed
|
|
231
|
+
from the output to prevent an overload in network requests according to the
|
|
232
|
+
`maxParallelRequests` config. In these cases, setting `sharedBundleMergeThreshold` can
|
|
233
|
+
merge shared bundles with a high overlap in their source bundles (bundles that share the bundle).
|
|
234
|
+
This config trades-off potential overfetching to reduce asset duplication.
|
|
235
|
+
|
|
236
|
+
The following config would merge shared bundles that have a 75% or higher overlap in source bundles.
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"@atlaspack/bundler-default": {
|
|
241
|
+
"sharedBundleMergeThreshold": 0.75
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Patch Changes
|
|
247
|
+
|
|
248
|
+
- 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)]:
|
|
249
|
+
- @atlaspack/feature-flags@2.14.3
|
|
250
|
+
- @atlaspack/rust@3.3.0
|
|
251
|
+
- @atlaspack/graph@3.4.3
|
|
252
|
+
- @atlaspack/utils@2.14.5
|
|
253
|
+
- @atlaspack/plugin@2.14.5
|
|
254
|
+
|
|
3
255
|
## 2.14.4
|
|
4
256
|
|
|
5
257
|
### Patch Changes
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.findMergeCandidates = findMergeCandidates;
|
|
7
|
+
function _assert() {
|
|
8
|
+
const data = _interopRequireDefault(require("assert"));
|
|
9
|
+
_assert = function () {
|
|
10
|
+
return data;
|
|
11
|
+
};
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
function _nullthrows() {
|
|
15
|
+
const data = _interopRequireDefault(require("nullthrows"));
|
|
16
|
+
_nullthrows = function () {
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
function _graph() {
|
|
22
|
+
const data = require("@atlaspack/graph");
|
|
23
|
+
_graph = function () {
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
function _utils() {
|
|
29
|
+
const data = require("@atlaspack/utils");
|
|
30
|
+
_utils = function () {
|
|
31
|
+
return data;
|
|
32
|
+
};
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
var _memoize = require("./memoize");
|
|
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
|
+
|
|
53
|
+
// Returns a decimal showing the proportion source bundles are common to
|
|
54
|
+
// both bundles versus the total number of source bundles.
|
|
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;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
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;
|
|
92
|
+
}
|
|
93
|
+
function getMergeClusters(graph, candidates) {
|
|
94
|
+
let clusters = [];
|
|
95
|
+
for (let [candidate, edgeType] of candidates.entries()) {
|
|
96
|
+
let cluster = [];
|
|
97
|
+
graph.traverse(nodeId => {
|
|
98
|
+
cluster.push((0, _nullthrows().default)(graph.getNode(nodeId)));
|
|
99
|
+
// Remove node from candidates as it has already been processed
|
|
100
|
+
candidates.delete(nodeId);
|
|
101
|
+
}, candidate, edgeType);
|
|
102
|
+
clusters.push(cluster);
|
|
103
|
+
}
|
|
104
|
+
return clusters;
|
|
105
|
+
}
|
|
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) {
|
|
131
|
+
let graph = new (_graph().ContentGraph)();
|
|
132
|
+
let candidates = new Map();
|
|
133
|
+
let allPossibleMergeCandidates = getPossibleMergeCandidates(bundleGraph, bundles);
|
|
134
|
+
|
|
135
|
+
// Build graph of clustered merge candidates
|
|
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)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(candidateA.contentKey, candidateA.id);
|
|
146
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(candidateB.contentKey, candidateB.id);
|
|
147
|
+
|
|
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);
|
|
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));
|
|
157
|
+
}
|
|
158
|
+
(0, _memoize.clearCaches)();
|
|
159
|
+
return getMergeClusters(graph, candidates);
|
|
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 () {
|
|
@@ -53,14 +60,16 @@ const HTTP_OPTIONS = {
|
|
|
53
60
|
manualSharedBundles: [],
|
|
54
61
|
minBundleSize: 30000,
|
|
55
62
|
maxParallelRequests: 6,
|
|
56
|
-
disableSharedBundles: false
|
|
63
|
+
disableSharedBundles: false,
|
|
64
|
+
sharedBundleMerge: []
|
|
57
65
|
},
|
|
58
66
|
'2': {
|
|
59
67
|
minBundles: 1,
|
|
60
68
|
manualSharedBundles: [],
|
|
61
69
|
minBundleSize: 20000,
|
|
62
70
|
maxParallelRequests: 25,
|
|
63
|
-
disableSharedBundles: false
|
|
71
|
+
disableSharedBundles: false,
|
|
72
|
+
sharedBundleMerge: []
|
|
64
73
|
}
|
|
65
74
|
};
|
|
66
75
|
const CONFIG_SCHEMA = {
|
|
@@ -101,6 +110,30 @@ const CONFIG_SCHEMA = {
|
|
|
101
110
|
additionalProperties: false
|
|
102
111
|
}
|
|
103
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
|
+
},
|
|
104
137
|
minBundles: {
|
|
105
138
|
type: 'number'
|
|
106
139
|
},
|
|
@@ -115,14 +148,25 @@ const CONFIG_SCHEMA = {
|
|
|
115
148
|
},
|
|
116
149
|
loadConditionalBundlesInParallel: {
|
|
117
150
|
type: 'boolean'
|
|
151
|
+
},
|
|
152
|
+
sharedBundleMergeThreshold: {
|
|
153
|
+
type: 'number'
|
|
118
154
|
}
|
|
119
155
|
},
|
|
120
156
|
additionalProperties: false
|
|
121
157
|
};
|
|
122
158
|
async function loadBundlerConfig(config, options, logger) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
+
}
|
|
126
170
|
if (!conf) {
|
|
127
171
|
const modDefault = {
|
|
128
172
|
...HTTP_OPTIONS['2'],
|
|
@@ -130,7 +174,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
130
174
|
};
|
|
131
175
|
return modDefault;
|
|
132
176
|
}
|
|
133
|
-
(0, _assert().default)((conf === null ||
|
|
177
|
+
(0, _assert().default)(((_conf = conf) === null || _conf === void 0 ? void 0 : _conf.contents) != null);
|
|
134
178
|
let modeConfig = resolveModeConfig(conf.contents, options.mode);
|
|
135
179
|
|
|
136
180
|
// minBundles will be ignored if shared bundles are disabled
|
|
@@ -172,6 +216,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
172
216
|
return {
|
|
173
217
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
174
218
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
219
|
+
sharedBundleMerge: modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
|
|
175
220
|
maxParallelRequests: modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
176
221
|
projectRoot: options.projectRoot,
|
|
177
222
|
disableSharedBundles: modeConfig.disableSharedBundles ?? defaults.disableSharedBundles,
|
package/lib/idealGraph.js
CHANGED
|
@@ -47,6 +47,7 @@ function _nullthrows() {
|
|
|
47
47
|
};
|
|
48
48
|
return data;
|
|
49
49
|
}
|
|
50
|
+
var _bundleMerge = require("./bundleMerge");
|
|
50
51
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
51
52
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
52
53
|
const dependencyPriorityEdges = {
|
|
@@ -65,6 +66,7 @@ const idealBundleGraphEdges = exports.idealBundleGraphEdges = Object.freeze({
|
|
|
65
66
|
// expect from default bundler
|
|
66
67
|
|
|
67
68
|
function createIdealGraph(assetGraph, config, entries, logger) {
|
|
69
|
+
var _config$sharedBundleM;
|
|
68
70
|
// Asset to the bundle and group it's an entry of
|
|
69
71
|
let bundleRoots = new Map();
|
|
70
72
|
let bundles = new Map();
|
|
@@ -159,17 +161,15 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
159
161
|
assetGraph.traverse((node, _, actions) => {
|
|
160
162
|
if (node.type === 'asset' && (!Array.isArray(c.types) || c.types.includes(node.value.type))) {
|
|
161
163
|
let projectRelativePath = _path().default.relative(config.projectRoot, node.value.filePath);
|
|
162
|
-
if (!assetRegexes.some(regex => regex.test(projectRelativePath))) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
164
|
|
|
166
165
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
167
166
|
// and need to be assigned to all matching bundles
|
|
168
167
|
if (node.value.meta.isConstantModule === true) {
|
|
169
168
|
constantModuleToMSB.get(node.value).push(c);
|
|
170
169
|
}
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
if (assetRegexes.some(regex => regex.test(projectRelativePath))) {
|
|
171
|
+
manualAssetToConfig.set(node.value, c);
|
|
172
|
+
}
|
|
173
173
|
}
|
|
174
174
|
if (node.type === 'dependency' && (node.value.priority === 'lazy' || (0, _featureFlags().getFeatureFlag)('conditionalBundlingApi') && node.value.priority === 'conditional') && parentAsset) {
|
|
175
175
|
// Don't walk past the bundle group assets
|
|
@@ -183,6 +183,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
183
183
|
};
|
|
184
184
|
}();
|
|
185
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
|
+
}));
|
|
186
191
|
|
|
187
192
|
/**
|
|
188
193
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
@@ -249,6 +254,11 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
249
254
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
250
255
|
bundleGroupBundleIds.add(bundleId);
|
|
251
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
|
+
}
|
|
252
262
|
if (manualSharedObject) {
|
|
253
263
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
254
264
|
// since MSBs should not have main entry assets
|
|
@@ -848,6 +858,12 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
848
858
|
}
|
|
849
859
|
}
|
|
850
860
|
|
|
861
|
+
// Step merge shared bundles that meet the overlap threshold
|
|
862
|
+
// This step is skipped by default as the threshold defaults to 1
|
|
863
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
864
|
+
mergeOverlapBundles(config.sharedBundleMerge);
|
|
865
|
+
}
|
|
866
|
+
|
|
851
867
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
852
868
|
// their source bundles, and remove the bundle.
|
|
853
869
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
@@ -857,9 +873,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
857
873
|
removeBundle(bundleGraph, bundleNodeId, assetReference);
|
|
858
874
|
}
|
|
859
875
|
}
|
|
860
|
-
let modifiedSourceBundles = new Set();
|
|
861
876
|
|
|
862
877
|
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
878
|
+
let modifiedSourceBundles = new Set();
|
|
863
879
|
if (config.disableSharedBundles === false) {
|
|
864
880
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
865
881
|
// Find shared bundles in this bundle group.
|
|
@@ -944,6 +960,68 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
944
960
|
}
|
|
945
961
|
}
|
|
946
962
|
}
|
|
963
|
+
function mergeBundles(bundleGraph, bundleToKeepId, bundleToRemoveId, assetReference) {
|
|
964
|
+
let bundleToKeep = (0, _nullthrows().default)(bundleGraph.getNode(bundleToKeepId));
|
|
965
|
+
let bundleToRemove = (0, _nullthrows().default)(bundleGraph.getNode(bundleToRemoveId));
|
|
966
|
+
(0, _assert().default)(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
967
|
+
for (let asset of bundleToRemove.assets) {
|
|
968
|
+
bundleToKeep.assets.add(asset);
|
|
969
|
+
bundleToKeep.size += asset.stats.size;
|
|
970
|
+
let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
|
|
971
|
+
assetReference.set(asset, newAssetReference);
|
|
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);
|
|
977
|
+
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
978
|
+
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
982
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
983
|
+
}
|
|
984
|
+
bundleGraph.removeNode(bundleToRemoveId);
|
|
985
|
+
}
|
|
986
|
+
function mergeOverlapBundles(mergeConfig) {
|
|
987
|
+
// Find all shared bundles
|
|
988
|
+
let sharedBundles = new Set();
|
|
989
|
+
bundleGraph.traverse(nodeId => {
|
|
990
|
+
let bundle = bundleGraph.getNode(nodeId);
|
|
991
|
+
if (!bundle) {
|
|
992
|
+
throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
|
|
993
|
+
}
|
|
994
|
+
if (bundle === 'root') {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Only consider JS shared bundles and non-reused bundles.
|
|
999
|
+
// These count potentially be considered for merging in future but they're
|
|
1000
|
+
// more complicated to merge
|
|
1001
|
+
if (bundle.sourceBundles.size > 0 && bundle.manualSharedBundle == null && !bundle.mainEntryAsset && bundle.type === 'js') {
|
|
1002
|
+
sharedBundles.add(nodeId);
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
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
|
+
}));
|
|
1018
|
+
for (let cluster of clusters) {
|
|
1019
|
+
let [mergeTarget, ...rest] = cluster;
|
|
1020
|
+
for (let bundleIdToMerge of rest) {
|
|
1021
|
+
mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
947
1025
|
function getBigIntFromContentKey(contentKey) {
|
|
948
1026
|
let b = Buffer.alloc(64);
|
|
949
1027
|
b.write(contentKey);
|
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.100+f609bf49f",
|
|
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.168+f609bf49f",
|
|
20
|
+
"@atlaspack/feature-flags": "2.14.1-canary.168+f609bf49f",
|
|
21
|
+
"@atlaspack/graph": "3.4.1-canary.168+f609bf49f",
|
|
22
|
+
"@atlaspack/plugin": "2.14.5-canary.100+f609bf49f",
|
|
23
|
+
"@atlaspack/rust": "3.2.1-canary.100+f609bf49f",
|
|
24
|
+
"@atlaspack/utils": "2.14.5-canary.100+f609bf49f",
|
|
25
|
+
"many-keys-map": "^1.0.3",
|
|
25
26
|
"nullthrows": "^1.1.1"
|
|
26
27
|
},
|
|
27
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "f609bf49ffa3984c0ff81d4853a5c850aaee5fce"
|
|
28
29
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
|
|
3
|
+
import invariant from 'assert';
|
|
4
|
+
import nullthrows from 'nullthrows';
|
|
5
|
+
import {ContentGraph} from '@atlaspack/graph';
|
|
6
|
+
import type {NodeId} from '@atlaspack/graph';
|
|
7
|
+
import {setUnion, setIntersectStatic} from '@atlaspack/utils';
|
|
8
|
+
import type {Bundle, IdealBundleGraph} from './idealGraph';
|
|
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
|
+
};
|
|
33
|
+
|
|
34
|
+
// Returns a decimal showing the proportion source bundles are common to
|
|
35
|
+
// both bundles versus the total number of source bundles.
|
|
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;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getMergeClusters(
|
|
125
|
+
graph: ContentGraph<NodeId, EdgeType>,
|
|
126
|
+
candidates: Map<NodeId, EdgeType>,
|
|
127
|
+
): Array<Array<NodeId>> {
|
|
128
|
+
let clusters = [];
|
|
129
|
+
|
|
130
|
+
for (let [candidate, edgeType] of candidates.entries()) {
|
|
131
|
+
let cluster: Array<NodeId> = [];
|
|
132
|
+
|
|
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
|
+
);
|
|
142
|
+
clusters.push(cluster);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return clusters;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
type MergeCandidate = {|
|
|
149
|
+
bundle: Bundle,
|
|
150
|
+
id: NodeId,
|
|
151
|
+
contentKey: string,
|
|
152
|
+
|};
|
|
153
|
+
function getPossibleMergeCandidates(
|
|
154
|
+
bundleGraph: IdealBundleGraph,
|
|
155
|
+
bundles: Array<NodeId>,
|
|
156
|
+
): Array<[MergeCandidate, MergeCandidate]> {
|
|
157
|
+
let mergeCandidates = bundles.map((bundleId) => {
|
|
158
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
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]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return uniquePairs;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export type MergeGroup = {|
|
|
187
|
+
overlapThreshold?: number,
|
|
188
|
+
maxBundleSize?: number,
|
|
189
|
+
sourceBundles?: Array<NodeId>,
|
|
190
|
+
minBundlesInGroup?: number,
|
|
191
|
+
|};
|
|
192
|
+
type EdgeType = number;
|
|
193
|
+
|
|
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>();
|
|
201
|
+
|
|
202
|
+
let allPossibleMergeCandidates = getPossibleMergeCandidates(
|
|
203
|
+
bundleGraph,
|
|
204
|
+
bundles,
|
|
205
|
+
);
|
|
206
|
+
|
|
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;
|
|
211
|
+
|
|
212
|
+
for (let group of allPossibleMergeCandidates) {
|
|
213
|
+
let candidateA = group[0];
|
|
214
|
+
let candidateB = group[1];
|
|
215
|
+
|
|
216
|
+
if (!validMerge(bundleGraph, config[i], candidateA, candidateB)) {
|
|
217
|
+
continue;
|
|
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);
|
|
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
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
clearCaches();
|
|
246
|
+
|
|
247
|
+
return getMergeClusters(graph, candidates);
|
|
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,6 +36,7 @@ type BaseBundlerConfig = {|
|
|
|
28
36
|
disableSharedBundles?: boolean,
|
|
29
37
|
manualSharedBundles?: ManualSharedBundles,
|
|
30
38
|
loadConditionalBundlesInParallel?: boolean,
|
|
39
|
+
sharedBundleMerge?: MergeCandidates,
|
|
31
40
|
|};
|
|
32
41
|
|
|
33
42
|
type BundlerConfig = {|
|
|
@@ -42,6 +51,7 @@ export type ResolvedBundlerConfig = {|
|
|
|
42
51
|
disableSharedBundles: boolean,
|
|
43
52
|
manualSharedBundles: ManualSharedBundles,
|
|
44
53
|
loadConditionalBundlesInParallel?: boolean,
|
|
54
|
+
sharedBundleMerge?: MergeCandidates,
|
|
45
55
|
|};
|
|
46
56
|
|
|
47
57
|
function resolveModeConfig(
|
|
@@ -76,6 +86,7 @@ const HTTP_OPTIONS = {
|
|
|
76
86
|
minBundleSize: 30000,
|
|
77
87
|
maxParallelRequests: 6,
|
|
78
88
|
disableSharedBundles: false,
|
|
89
|
+
sharedBundleMerge: [],
|
|
79
90
|
},
|
|
80
91
|
'2': {
|
|
81
92
|
minBundles: 1,
|
|
@@ -83,6 +94,7 @@ const HTTP_OPTIONS = {
|
|
|
83
94
|
minBundleSize: 20000,
|
|
84
95
|
maxParallelRequests: 25,
|
|
85
96
|
disableSharedBundles: false,
|
|
97
|
+
sharedBundleMerge: [],
|
|
86
98
|
},
|
|
87
99
|
};
|
|
88
100
|
|
|
@@ -124,6 +136,30 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
124
136
|
additionalProperties: false,
|
|
125
137
|
},
|
|
126
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
|
+
},
|
|
127
163
|
minBundles: {
|
|
128
164
|
type: 'number',
|
|
129
165
|
},
|
|
@@ -139,6 +175,9 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
139
175
|
loadConditionalBundlesInParallel: {
|
|
140
176
|
type: 'boolean',
|
|
141
177
|
},
|
|
178
|
+
sharedBundleMergeThreshold: {
|
|
179
|
+
type: 'number',
|
|
180
|
+
},
|
|
142
181
|
},
|
|
143
182
|
additionalProperties: false,
|
|
144
183
|
};
|
|
@@ -148,9 +187,17 @@ export async function loadBundlerConfig(
|
|
|
148
187
|
options: PluginOptions,
|
|
149
188
|
logger: PluginLogger,
|
|
150
189
|
): Promise<ResolvedBundlerConfig> {
|
|
151
|
-
let conf
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
}
|
|
154
201
|
|
|
155
202
|
if (!conf) {
|
|
156
203
|
const modDefault = {
|
|
@@ -224,6 +271,8 @@ export async function loadBundlerConfig(
|
|
|
224
271
|
return {
|
|
225
272
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
226
273
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
274
|
+
sharedBundleMerge:
|
|
275
|
+
modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
|
|
227
276
|
maxParallelRequests:
|
|
228
277
|
modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
229
278
|
projectRoot: options.projectRoot,
|
package/src/idealGraph.js
CHANGED
|
@@ -23,7 +23,8 @@ import {DefaultMap, globToRegex} from '@atlaspack/utils';
|
|
|
23
23
|
import invariant from 'assert';
|
|
24
24
|
import nullthrows from 'nullthrows';
|
|
25
25
|
|
|
26
|
-
import type
|
|
26
|
+
import {findMergeCandidates, type MergeGroup} from './bundleMerge';
|
|
27
|
+
import type {ResolvedBundlerConfig, MergeCandidates} from './bundlerConfig';
|
|
27
28
|
|
|
28
29
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
29
30
|
type BundleRoot = Asset;
|
|
@@ -67,7 +68,7 @@ export const idealBundleGraphEdges = Object.freeze({
|
|
|
67
68
|
conditional: 2,
|
|
68
69
|
});
|
|
69
70
|
|
|
70
|
-
type IdealBundleGraph = Graph<
|
|
71
|
+
export type IdealBundleGraph = Graph<
|
|
71
72
|
Bundle | 'root',
|
|
72
73
|
$Values<typeof idealBundleGraphEdges>,
|
|
73
74
|
>;
|
|
@@ -203,17 +204,16 @@ export function createIdealGraph(
|
|
|
203
204
|
config.projectRoot,
|
|
204
205
|
node.value.filePath,
|
|
205
206
|
);
|
|
206
|
-
if (!assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
207
|
|
|
210
208
|
// We track all matching MSB's for constant modules as they are never duplicated
|
|
211
209
|
// and need to be assigned to all matching bundles
|
|
212
210
|
if (node.value.meta.isConstantModule === true) {
|
|
213
211
|
constantModuleToMSB.get(node.value).push(c);
|
|
214
212
|
}
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
|
|
214
|
+
if (assetRegexes.some((regex) => regex.test(projectRelativePath))) {
|
|
215
|
+
manualAssetToConfig.set(node.value, c);
|
|
216
|
+
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
if (
|
|
@@ -244,6 +244,16 @@ export function createIdealGraph(
|
|
|
244
244
|
Array<Asset>,
|
|
245
245
|
> = new DefaultMap(() => []);
|
|
246
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
|
+
|
|
247
257
|
/**
|
|
248
258
|
* Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles
|
|
249
259
|
* for asset type changes, parallel, inline, and async or lazy dependencies,
|
|
@@ -334,6 +344,14 @@ export function createIdealGraph(
|
|
|
334
344
|
bundleRoots.set(childAsset, [bundleId, bundleId]);
|
|
335
345
|
bundleGroupBundleIds.add(bundleId);
|
|
336
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
|
+
}
|
|
337
355
|
if (manualSharedObject) {
|
|
338
356
|
// MSB Step 4: If this was the first instance of a match, mark mainAsset for internalization
|
|
339
357
|
// since MSBs should not have main entry assets
|
|
@@ -1128,6 +1146,12 @@ export function createIdealGraph(
|
|
|
1128
1146
|
}
|
|
1129
1147
|
}
|
|
1130
1148
|
|
|
1149
|
+
// Step merge shared bundles that meet the overlap threshold
|
|
1150
|
+
// This step is skipped by default as the threshold defaults to 1
|
|
1151
|
+
if (config.sharedBundleMerge && config.sharedBundleMerge.length > 0) {
|
|
1152
|
+
mergeOverlapBundles(config.sharedBundleMerge);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1131
1155
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
1132
1156
|
// their source bundles, and remove the bundle.
|
|
1133
1157
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
@@ -1143,9 +1167,9 @@ export function createIdealGraph(
|
|
|
1143
1167
|
}
|
|
1144
1168
|
}
|
|
1145
1169
|
|
|
1170
|
+
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1146
1171
|
let modifiedSourceBundles = new Set();
|
|
1147
1172
|
|
|
1148
|
-
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1149
1173
|
if (config.disableSharedBundles === false) {
|
|
1150
1174
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
1151
1175
|
// Find shared bundles in this bundle group.
|
|
@@ -1249,6 +1273,102 @@ export function createIdealGraph(
|
|
|
1249
1273
|
}
|
|
1250
1274
|
}
|
|
1251
1275
|
|
|
1276
|
+
function mergeBundles(
|
|
1277
|
+
bundleGraph: IdealBundleGraph,
|
|
1278
|
+
bundleToKeepId: NodeId,
|
|
1279
|
+
bundleToRemoveId: NodeId,
|
|
1280
|
+
assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>,
|
|
1281
|
+
) {
|
|
1282
|
+
let bundleToKeep = nullthrows(bundleGraph.getNode(bundleToKeepId));
|
|
1283
|
+
let bundleToRemove = nullthrows(bundleGraph.getNode(bundleToRemoveId));
|
|
1284
|
+
invariant(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
1285
|
+
for (let asset of bundleToRemove.assets) {
|
|
1286
|
+
bundleToKeep.assets.add(asset);
|
|
1287
|
+
bundleToKeep.size += asset.stats.size;
|
|
1288
|
+
|
|
1289
|
+
let newAssetReference = assetReference
|
|
1290
|
+
.get(asset)
|
|
1291
|
+
.map(([dep, bundle]) =>
|
|
1292
|
+
bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
|
|
1293
|
+
);
|
|
1294
|
+
|
|
1295
|
+
assetReference.set(asset, newAssetReference);
|
|
1296
|
+
}
|
|
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
|
+
|
|
1305
|
+
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1306
|
+
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
1311
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
bundleGraph.removeNode(bundleToRemoveId);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function mergeOverlapBundles(mergeConfig: MergeCandidates) {
|
|
1318
|
+
// Find all shared bundles
|
|
1319
|
+
let sharedBundles = new Set<NodeId>();
|
|
1320
|
+
bundleGraph.traverse((nodeId) => {
|
|
1321
|
+
let bundle = bundleGraph.getNode(nodeId);
|
|
1322
|
+
|
|
1323
|
+
if (!bundle) {
|
|
1324
|
+
throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
if (bundle === 'root') {
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// Only consider JS shared bundles and non-reused bundles.
|
|
1332
|
+
// These count potentially be considered for merging in future but they're
|
|
1333
|
+
// more complicated to merge
|
|
1334
|
+
if (
|
|
1335
|
+
bundle.sourceBundles.size > 0 &&
|
|
1336
|
+
bundle.manualSharedBundle == null &&
|
|
1337
|
+
!bundle.mainEntryAsset &&
|
|
1338
|
+
bundle.type === 'js'
|
|
1339
|
+
) {
|
|
1340
|
+
sharedBundles.add(nodeId);
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
let clusters = findMergeCandidates(
|
|
1345
|
+
bundleGraph,
|
|
1346
|
+
Array.from(sharedBundles),
|
|
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
|
+
})),
|
|
1361
|
+
);
|
|
1362
|
+
|
|
1363
|
+
for (let cluster of clusters) {
|
|
1364
|
+
let [mergeTarget, ...rest] = cluster;
|
|
1365
|
+
|
|
1366
|
+
for (let bundleIdToMerge of rest) {
|
|
1367
|
+
mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1252
1372
|
function getBigIntFromContentKey(contentKey) {
|
|
1253
1373
|
let b = Buffer.alloc(64);
|
|
1254
1374
|
b.write(contentKey);
|
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
|
+
}
|