@atlaspack/bundler-default 2.14.5-canary.6 → 2.14.5-canary.60
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 +88 -0
- package/lib/bundleMerge.js +91 -0
- package/lib/bundlerConfig.js +27 -6
- package/lib/idealGraph.js +63 -1
- package/package.json +8 -8
- package/src/bundleMerge.js +103 -0
- package/src/bundlerConfig.js +22 -3
- package/src/idealGraph.js +96 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# @atlaspack/bundler-default
|
|
2
2
|
|
|
3
|
+
## 2.16.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`35fdd4b`](https://github.com/atlassian-labs/atlaspack/commit/35fdd4b52da0af20f74667f7b8adfb2f90279b7c), [`6dd4ccb`](https://github.com/atlassian-labs/atlaspack/commit/6dd4ccb753541de32322d881f973d571dd57e4ca)]:
|
|
8
|
+
- @atlaspack/rust@3.3.5
|
|
9
|
+
- @atlaspack/plugin@2.14.10
|
|
10
|
+
- @atlaspack/utils@2.14.10
|
|
11
|
+
|
|
12
|
+
## 2.16.2
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 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)]:
|
|
17
|
+
- @atlaspack/rust@3.3.4
|
|
18
|
+
- @atlaspack/feature-flags@2.16.0
|
|
19
|
+
- @atlaspack/utils@2.14.9
|
|
20
|
+
- @atlaspack/graph@3.4.7
|
|
21
|
+
- @atlaspack/plugin@2.14.9
|
|
22
|
+
|
|
23
|
+
## 2.16.1
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- 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)]:
|
|
28
|
+
- @atlaspack/feature-flags@2.15.1
|
|
29
|
+
- @atlaspack/rust@3.3.3
|
|
30
|
+
- @atlaspack/graph@3.4.6
|
|
31
|
+
- @atlaspack/utils@2.14.8
|
|
32
|
+
- @atlaspack/plugin@2.14.8
|
|
33
|
+
|
|
34
|
+
## 2.16.0
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
37
|
+
|
|
38
|
+
- [#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.
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- Updated dependencies [[`a1773d2`](https://github.com/atlassian-labs/atlaspack/commit/a1773d2a62d0ef7805ac7524621dcabcc1afe929), [`556d6ab`](https://github.com/atlassian-labs/atlaspack/commit/556d6ab8ede759fa7f37fcd3f4da336ef1c55e8f)]:
|
|
43
|
+
- @atlaspack/feature-flags@2.15.0
|
|
44
|
+
- @atlaspack/rust@3.3.2
|
|
45
|
+
- @atlaspack/graph@3.4.5
|
|
46
|
+
- @atlaspack/utils@2.14.7
|
|
47
|
+
- @atlaspack/plugin@2.14.7
|
|
48
|
+
|
|
49
|
+
## 2.15.1
|
|
50
|
+
|
|
51
|
+
### Patch Changes
|
|
52
|
+
|
|
53
|
+
- Updated dependencies [[`e0f5337`](https://github.com/atlassian-labs/atlaspack/commit/e0f533757bd1019dbd108a04952c87da15286e09)]:
|
|
54
|
+
- @atlaspack/feature-flags@2.14.4
|
|
55
|
+
- @atlaspack/rust@3.3.1
|
|
56
|
+
- @atlaspack/graph@3.4.4
|
|
57
|
+
- @atlaspack/utils@2.14.6
|
|
58
|
+
- @atlaspack/plugin@2.14.6
|
|
59
|
+
|
|
60
|
+
## 2.15.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- [#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
|
|
65
|
+
|
|
66
|
+
In apps with lots of dynamic imports, many shared bundles are often removed
|
|
67
|
+
from the output to prevent an overload in network requests according to the
|
|
68
|
+
`maxParallelRequests` config. In these cases, setting `sharedBundleMergeThreshold` can
|
|
69
|
+
merge shared bundles with a high overlap in their source bundles (bundles that share the bundle).
|
|
70
|
+
This config trades-off potential overfetching to reduce asset duplication.
|
|
71
|
+
|
|
72
|
+
The following config would merge shared bundles that have a 75% or higher overlap in source bundles.
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"@atlaspack/bundler-default": {
|
|
77
|
+
"sharedBundleMergeThreshold": 0.75
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Patch Changes
|
|
83
|
+
|
|
84
|
+
- 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)]:
|
|
85
|
+
- @atlaspack/feature-flags@2.14.3
|
|
86
|
+
- @atlaspack/rust@3.3.0
|
|
87
|
+
- @atlaspack/graph@3.4.3
|
|
88
|
+
- @atlaspack/utils@2.14.5
|
|
89
|
+
- @atlaspack/plugin@2.14.5
|
|
90
|
+
|
|
3
91
|
## 2.14.4
|
|
4
92
|
|
|
5
93
|
### Patch Changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.findMergeCandidates = findMergeCandidates;
|
|
7
|
+
function _assert() {
|
|
8
|
+
const data = _interopRequireDefault(require("assert"));
|
|
9
|
+
_assert = function () {
|
|
10
|
+
return data;
|
|
11
|
+
};
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
function _nullthrows() {
|
|
15
|
+
const data = _interopRequireDefault(require("nullthrows"));
|
|
16
|
+
_nullthrows = function () {
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
function _graph() {
|
|
22
|
+
const data = require("@atlaspack/graph");
|
|
23
|
+
_graph = function () {
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
+
// Returns a decimal showing the proportion source bundles are common to
|
|
30
|
+
// both bundles versus the total number of source bundles.
|
|
31
|
+
function scoreBundleMerge(bundleA, bundleB) {
|
|
32
|
+
let sharedSourceBundles = 0;
|
|
33
|
+
let allSourceBundles = new Set([...bundleA.sourceBundles, ...bundleB.sourceBundles]);
|
|
34
|
+
for (let bundle of bundleB.sourceBundles) {
|
|
35
|
+
if (bundleA.sourceBundles.has(bundle)) {
|
|
36
|
+
sharedSourceBundles++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return sharedSourceBundles / allSourceBundles.size;
|
|
40
|
+
}
|
|
41
|
+
function getMergeClusters(graph, candidates) {
|
|
42
|
+
let clusters = [];
|
|
43
|
+
for (let candidate of candidates) {
|
|
44
|
+
let cluster = [];
|
|
45
|
+
graph.traverse(nodeId => {
|
|
46
|
+
cluster.push((0, _nullthrows().default)(graph.getNode(nodeId)));
|
|
47
|
+
// Remove node from candidates as it has already been processed
|
|
48
|
+
candidates.delete(nodeId);
|
|
49
|
+
}, candidate);
|
|
50
|
+
clusters.push(cluster);
|
|
51
|
+
}
|
|
52
|
+
return clusters;
|
|
53
|
+
}
|
|
54
|
+
function findMergeCandidates(bundleGraph, bundles, threshold) {
|
|
55
|
+
let graph = new (_graph().ContentGraph)();
|
|
56
|
+
let seen = new Set();
|
|
57
|
+
let candidates = new Set();
|
|
58
|
+
|
|
59
|
+
// Build graph of clustered merge candidates
|
|
60
|
+
for (let bundleId of bundles) {
|
|
61
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
62
|
+
(0, _assert().default)(bundle && bundle !== 'root');
|
|
63
|
+
if (bundle.type !== 'js') {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
for (let otherBundleId of bundles) {
|
|
67
|
+
if (bundleId === otherBundleId) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let key = [bundleId, otherBundleId].sort().join(':');
|
|
71
|
+
if (seen.has(key)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
seen.add(key);
|
|
75
|
+
let otherBundle = bundleGraph.getNode(otherBundleId);
|
|
76
|
+
(0, _assert().default)(otherBundle && otherBundle !== 'root');
|
|
77
|
+
let score = scoreBundleMerge(bundle, otherBundle);
|
|
78
|
+
if (score >= threshold) {
|
|
79
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(bundleId.toString(), bundleId);
|
|
80
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(otherBundleId.toString(), otherBundleId);
|
|
81
|
+
|
|
82
|
+
// Add edge in both directions
|
|
83
|
+
graph.addEdge(bundleNode, otherBundleNode);
|
|
84
|
+
graph.addEdge(otherBundleNode, bundleNode);
|
|
85
|
+
candidates.add(bundleNode);
|
|
86
|
+
candidates.add(otherBundleNode);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return getMergeClusters(graph, candidates);
|
|
91
|
+
}
|
package/lib/bundlerConfig.js
CHANGED
|
@@ -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
|
+
sharedBundleMergeThreshold: 1
|
|
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
|
+
sharedBundleMergeThreshold: 1
|
|
64
73
|
}
|
|
65
74
|
};
|
|
66
75
|
const CONFIG_SCHEMA = {
|
|
@@ -115,14 +124,25 @@ const CONFIG_SCHEMA = {
|
|
|
115
124
|
},
|
|
116
125
|
loadConditionalBundlesInParallel: {
|
|
117
126
|
type: 'boolean'
|
|
127
|
+
},
|
|
128
|
+
sharedBundleMergeThreshold: {
|
|
129
|
+
type: 'number'
|
|
118
130
|
}
|
|
119
131
|
},
|
|
120
132
|
additionalProperties: false
|
|
121
133
|
};
|
|
122
134
|
async function loadBundlerConfig(config, options, logger) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
var _conf;
|
|
136
|
+
let conf;
|
|
137
|
+
if ((0, _featureFlags().getFeatureFlag)('resolveBundlerConfigFromCwd')) {
|
|
138
|
+
conf = await config.getConfigFrom(`${process.cwd()}/index`, [], {
|
|
139
|
+
packageKey: '@atlaspack/bundler-default'
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
conf = await config.getConfig([], {
|
|
143
|
+
packageKey: '@atlaspack/bundler-default'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
126
146
|
if (!conf) {
|
|
127
147
|
const modDefault = {
|
|
128
148
|
...HTTP_OPTIONS['2'],
|
|
@@ -130,7 +150,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
130
150
|
};
|
|
131
151
|
return modDefault;
|
|
132
152
|
}
|
|
133
|
-
(0, _assert().default)((conf === null ||
|
|
153
|
+
(0, _assert().default)(((_conf = conf) === null || _conf === void 0 ? void 0 : _conf.contents) != null);
|
|
134
154
|
let modeConfig = resolveModeConfig(conf.contents, options.mode);
|
|
135
155
|
|
|
136
156
|
// minBundles will be ignored if shared bundles are disabled
|
|
@@ -172,6 +192,7 @@ async function loadBundlerConfig(config, options, logger) {
|
|
|
172
192
|
return {
|
|
173
193
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
174
194
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
195
|
+
sharedBundleMergeThreshold: modeConfig.sharedBundleMergeThreshold ?? defaults.sharedBundleMergeThreshold,
|
|
175
196
|
maxParallelRequests: modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
176
197
|
projectRoot: options.projectRoot,
|
|
177
198
|
disableSharedBundles: modeConfig.disableSharedBundles ?? defaults.disableSharedBundles,
|
package/lib/idealGraph.js
CHANGED
|
@@ -47,6 +47,7 @@ function _nullthrows() {
|
|
|
47
47
|
};
|
|
48
48
|
return data;
|
|
49
49
|
}
|
|
50
|
+
var _bundleMerge = require("./bundleMerge");
|
|
50
51
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
51
52
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
52
53
|
const dependencyPriorityEdges = {
|
|
@@ -848,6 +849,12 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
848
849
|
}
|
|
849
850
|
}
|
|
850
851
|
|
|
852
|
+
// Step merge shared bundles that meet the overlap threshold
|
|
853
|
+
// This step is skipped by default as the threshold defaults to 1
|
|
854
|
+
if (config.sharedBundleMergeThreshold < 1) {
|
|
855
|
+
mergeOverlapBundles();
|
|
856
|
+
}
|
|
857
|
+
|
|
851
858
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
852
859
|
// their source bundles, and remove the bundle.
|
|
853
860
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
@@ -857,9 +864,9 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
857
864
|
removeBundle(bundleGraph, bundleNodeId, assetReference);
|
|
858
865
|
}
|
|
859
866
|
}
|
|
860
|
-
let modifiedSourceBundles = new Set();
|
|
861
867
|
|
|
862
868
|
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
869
|
+
let modifiedSourceBundles = new Set();
|
|
863
870
|
if (config.disableSharedBundles === false) {
|
|
864
871
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
865
872
|
// Find shared bundles in this bundle group.
|
|
@@ -944,6 +951,61 @@ function createIdealGraph(assetGraph, config, entries, logger) {
|
|
|
944
951
|
}
|
|
945
952
|
}
|
|
946
953
|
}
|
|
954
|
+
function mergeBundles(bundleGraph, bundleToKeepId, bundleToRemoveId, assetReference) {
|
|
955
|
+
let bundleToKeep = (0, _nullthrows().default)(bundleGraph.getNode(bundleToKeepId));
|
|
956
|
+
let bundleToRemove = (0, _nullthrows().default)(bundleGraph.getNode(bundleToRemoveId));
|
|
957
|
+
(0, _assert().default)(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
958
|
+
for (let asset of bundleToRemove.assets) {
|
|
959
|
+
bundleToKeep.assets.add(asset);
|
|
960
|
+
bundleToKeep.size += asset.stats.size;
|
|
961
|
+
let newAssetReference = assetReference.get(asset).map(([dep, bundle]) => bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle]);
|
|
962
|
+
assetReference.set(asset, newAssetReference);
|
|
963
|
+
}
|
|
964
|
+
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
965
|
+
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
969
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Merge any internalized assets
|
|
973
|
+
if (bundleToRemove.internalizedAssets) {
|
|
974
|
+
if (bundleToKeep.internalizedAssets) {
|
|
975
|
+
bundleToKeep.internalizedAssets.union(bundleToRemove.internalizedAssets);
|
|
976
|
+
} else {
|
|
977
|
+
bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
bundleGraph.removeNode(bundleToRemoveId);
|
|
981
|
+
}
|
|
982
|
+
function mergeOverlapBundles() {
|
|
983
|
+
// Find all shared bundles
|
|
984
|
+
let sharedBundles = new Set();
|
|
985
|
+
bundleGraph.traverse(nodeId => {
|
|
986
|
+
let bundle = bundleGraph.getNode(nodeId);
|
|
987
|
+
if (!bundle) {
|
|
988
|
+
throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
|
|
989
|
+
}
|
|
990
|
+
if (bundle === 'root') {
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Only consider JS shared bundles and non-reused bundles.
|
|
995
|
+
// These count potentially be considered for merging in future but they're
|
|
996
|
+
// more complicated to merge
|
|
997
|
+
if (bundle.sourceBundles.size > 0 && bundle.manualSharedBundle == null && !bundle.mainEntryAsset && bundle.type === 'js') {
|
|
998
|
+
sharedBundles.add(nodeId);
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
let clusters = (0, _bundleMerge.findMergeCandidates)(bundleGraph, Array.from(sharedBundles), config.sharedBundleMergeThreshold);
|
|
1002
|
+
for (let cluster of clusters) {
|
|
1003
|
+
let [mergeTarget, ...rest] = cluster;
|
|
1004
|
+
for (let bundleIdToMerge of rest) {
|
|
1005
|
+
mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
947
1009
|
function getBigIntFromContentKey(contentKey) {
|
|
948
1010
|
let b = Buffer.alloc(64);
|
|
949
1011
|
b.write(contentKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/bundler-default",
|
|
3
|
-
"version": "2.14.5-canary.
|
|
3
|
+
"version": "2.14.5-canary.60+2e51b5a5d",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
"node": ">= 16.0.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@atlaspack/diagnostic": "2.14.1-canary.
|
|
20
|
-
"@atlaspack/feature-flags": "2.14.1-canary.
|
|
21
|
-
"@atlaspack/graph": "3.4.1-canary.
|
|
22
|
-
"@atlaspack/plugin": "2.14.5-canary.
|
|
23
|
-
"@atlaspack/rust": "3.2.1-canary.
|
|
24
|
-
"@atlaspack/utils": "2.14.5-canary.
|
|
19
|
+
"@atlaspack/diagnostic": "2.14.1-canary.128+2e51b5a5d",
|
|
20
|
+
"@atlaspack/feature-flags": "2.14.1-canary.128+2e51b5a5d",
|
|
21
|
+
"@atlaspack/graph": "3.4.1-canary.128+2e51b5a5d",
|
|
22
|
+
"@atlaspack/plugin": "2.14.5-canary.60+2e51b5a5d",
|
|
23
|
+
"@atlaspack/rust": "3.2.1-canary.60+2e51b5a5d",
|
|
24
|
+
"@atlaspack/utils": "2.14.5-canary.60+2e51b5a5d",
|
|
25
25
|
"nullthrows": "^1.1.1"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "2e51b5a5d41ba681170ec21d23ade4e0df55e112"
|
|
28
28
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
|
|
3
|
+
import invariant from 'assert';
|
|
4
|
+
import nullthrows from 'nullthrows';
|
|
5
|
+
import type {NodeId} from '@atlaspack/graph';
|
|
6
|
+
import type {Bundle, IdealBundleGraph} from './idealGraph';
|
|
7
|
+
import {ContentGraph} from '@atlaspack/graph';
|
|
8
|
+
|
|
9
|
+
// Returns a decimal showing the proportion source bundles are common to
|
|
10
|
+
// both bundles versus the total number of source bundles.
|
|
11
|
+
function scoreBundleMerge(bundleA: Bundle, bundleB: Bundle): number {
|
|
12
|
+
let sharedSourceBundles = 0;
|
|
13
|
+
let allSourceBundles = new Set([
|
|
14
|
+
...bundleA.sourceBundles,
|
|
15
|
+
...bundleB.sourceBundles,
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
for (let bundle of bundleB.sourceBundles) {
|
|
19
|
+
if (bundleA.sourceBundles.has(bundle)) {
|
|
20
|
+
sharedSourceBundles++;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return sharedSourceBundles / allSourceBundles.size;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getMergeClusters(
|
|
28
|
+
graph: ContentGraph<NodeId>,
|
|
29
|
+
candidates: Set<NodeId>,
|
|
30
|
+
): Array<Array<NodeId>> {
|
|
31
|
+
let clusters = [];
|
|
32
|
+
|
|
33
|
+
for (let candidate of candidates) {
|
|
34
|
+
let cluster: Array<NodeId> = [];
|
|
35
|
+
|
|
36
|
+
graph.traverse((nodeId) => {
|
|
37
|
+
cluster.push(nullthrows(graph.getNode(nodeId)));
|
|
38
|
+
// Remove node from candidates as it has already been processed
|
|
39
|
+
candidates.delete(nodeId);
|
|
40
|
+
}, candidate);
|
|
41
|
+
|
|
42
|
+
clusters.push(cluster);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return clusters;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function findMergeCandidates(
|
|
49
|
+
bundleGraph: IdealBundleGraph,
|
|
50
|
+
bundles: Array<NodeId>,
|
|
51
|
+
threshold: number,
|
|
52
|
+
): Array<Array<NodeId>> {
|
|
53
|
+
let graph = new ContentGraph<NodeId>();
|
|
54
|
+
let seen = new Set<string>();
|
|
55
|
+
let candidates = new Set<NodeId>();
|
|
56
|
+
|
|
57
|
+
// Build graph of clustered merge candidates
|
|
58
|
+
for (let bundleId of bundles) {
|
|
59
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
60
|
+
invariant(bundle && bundle !== 'root');
|
|
61
|
+
if (bundle.type !== 'js') {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (let otherBundleId of bundles) {
|
|
66
|
+
if (bundleId === otherBundleId) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let key = [bundleId, otherBundleId].sort().join(':');
|
|
71
|
+
|
|
72
|
+
if (seen.has(key)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
seen.add(key);
|
|
76
|
+
|
|
77
|
+
let otherBundle = bundleGraph.getNode(otherBundleId);
|
|
78
|
+
invariant(otherBundle && otherBundle !== 'root');
|
|
79
|
+
|
|
80
|
+
let score = scoreBundleMerge(bundle, otherBundle);
|
|
81
|
+
|
|
82
|
+
if (score >= threshold) {
|
|
83
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
84
|
+
bundleId.toString(),
|
|
85
|
+
bundleId,
|
|
86
|
+
);
|
|
87
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
88
|
+
otherBundleId.toString(),
|
|
89
|
+
otherBundleId,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Add edge in both directions
|
|
93
|
+
graph.addEdge(bundleNode, otherBundleNode);
|
|
94
|
+
graph.addEdge(otherBundleNode, bundleNode);
|
|
95
|
+
|
|
96
|
+
candidates.add(bundleNode);
|
|
97
|
+
candidates.add(otherBundleNode);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return getMergeClusters(graph, candidates);
|
|
103
|
+
}
|
package/src/bundlerConfig.js
CHANGED
|
@@ -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
|
|
|
@@ -28,6 +29,7 @@ type BaseBundlerConfig = {|
|
|
|
28
29
|
disableSharedBundles?: boolean,
|
|
29
30
|
manualSharedBundles?: ManualSharedBundles,
|
|
30
31
|
loadConditionalBundlesInParallel?: boolean,
|
|
32
|
+
sharedBundleMergeThreshold?: number,
|
|
31
33
|
|};
|
|
32
34
|
|
|
33
35
|
type BundlerConfig = {|
|
|
@@ -42,6 +44,7 @@ export type ResolvedBundlerConfig = {|
|
|
|
42
44
|
disableSharedBundles: boolean,
|
|
43
45
|
manualSharedBundles: ManualSharedBundles,
|
|
44
46
|
loadConditionalBundlesInParallel?: boolean,
|
|
47
|
+
sharedBundleMergeThreshold: number,
|
|
45
48
|
|};
|
|
46
49
|
|
|
47
50
|
function resolveModeConfig(
|
|
@@ -76,6 +79,7 @@ const HTTP_OPTIONS = {
|
|
|
76
79
|
minBundleSize: 30000,
|
|
77
80
|
maxParallelRequests: 6,
|
|
78
81
|
disableSharedBundles: false,
|
|
82
|
+
sharedBundleMergeThreshold: 1,
|
|
79
83
|
},
|
|
80
84
|
'2': {
|
|
81
85
|
minBundles: 1,
|
|
@@ -83,6 +87,7 @@ const HTTP_OPTIONS = {
|
|
|
83
87
|
minBundleSize: 20000,
|
|
84
88
|
maxParallelRequests: 25,
|
|
85
89
|
disableSharedBundles: false,
|
|
90
|
+
sharedBundleMergeThreshold: 1,
|
|
86
91
|
},
|
|
87
92
|
};
|
|
88
93
|
|
|
@@ -139,6 +144,9 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
139
144
|
loadConditionalBundlesInParallel: {
|
|
140
145
|
type: 'boolean',
|
|
141
146
|
},
|
|
147
|
+
sharedBundleMergeThreshold: {
|
|
148
|
+
type: 'number',
|
|
149
|
+
},
|
|
142
150
|
},
|
|
143
151
|
additionalProperties: false,
|
|
144
152
|
};
|
|
@@ -148,9 +156,17 @@ export async function loadBundlerConfig(
|
|
|
148
156
|
options: PluginOptions,
|
|
149
157
|
logger: PluginLogger,
|
|
150
158
|
): Promise<ResolvedBundlerConfig> {
|
|
151
|
-
let conf
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
let conf;
|
|
160
|
+
|
|
161
|
+
if (getFeatureFlag('resolveBundlerConfigFromCwd')) {
|
|
162
|
+
conf = await config.getConfigFrom(`${process.cwd()}/index`, [], {
|
|
163
|
+
packageKey: '@atlaspack/bundler-default',
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
conf = await config.getConfig<BundlerConfig>([], {
|
|
167
|
+
packageKey: '@atlaspack/bundler-default',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
154
170
|
|
|
155
171
|
if (!conf) {
|
|
156
172
|
const modDefault = {
|
|
@@ -224,6 +240,9 @@ export async function loadBundlerConfig(
|
|
|
224
240
|
return {
|
|
225
241
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
226
242
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
243
|
+
sharedBundleMergeThreshold:
|
|
244
|
+
modeConfig.sharedBundleMergeThreshold ??
|
|
245
|
+
defaults.sharedBundleMergeThreshold,
|
|
227
246
|
maxParallelRequests:
|
|
228
247
|
modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
229
248
|
projectRoot: options.projectRoot,
|
package/src/idealGraph.js
CHANGED
|
@@ -23,6 +23,7 @@ import {DefaultMap, globToRegex} from '@atlaspack/utils';
|
|
|
23
23
|
import invariant from 'assert';
|
|
24
24
|
import nullthrows from 'nullthrows';
|
|
25
25
|
|
|
26
|
+
import {findMergeCandidates} from './bundleMerge';
|
|
26
27
|
import type {ResolvedBundlerConfig} from './bundlerConfig';
|
|
27
28
|
|
|
28
29
|
/* BundleRoot - An asset that is the main entry of a Bundle. */
|
|
@@ -67,7 +68,7 @@ export const idealBundleGraphEdges = Object.freeze({
|
|
|
67
68
|
conditional: 2,
|
|
68
69
|
});
|
|
69
70
|
|
|
70
|
-
type IdealBundleGraph = Graph<
|
|
71
|
+
export type IdealBundleGraph = Graph<
|
|
71
72
|
Bundle | 'root',
|
|
72
73
|
$Values<typeof idealBundleGraphEdges>,
|
|
73
74
|
>;
|
|
@@ -1128,6 +1129,12 @@ export function createIdealGraph(
|
|
|
1128
1129
|
}
|
|
1129
1130
|
}
|
|
1130
1131
|
|
|
1132
|
+
// Step merge shared bundles that meet the overlap threshold
|
|
1133
|
+
// This step is skipped by default as the threshold defaults to 1
|
|
1134
|
+
if (config.sharedBundleMergeThreshold < 1) {
|
|
1135
|
+
mergeOverlapBundles();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1131
1138
|
// Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into
|
|
1132
1139
|
// their source bundles, and remove the bundle.
|
|
1133
1140
|
// We should include "bundle reuse" as shared bundles that may be removed but the bundle itself would have to be retained
|
|
@@ -1143,9 +1150,9 @@ export function createIdealGraph(
|
|
|
1143
1150
|
}
|
|
1144
1151
|
}
|
|
1145
1152
|
|
|
1153
|
+
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1146
1154
|
let modifiedSourceBundles = new Set();
|
|
1147
1155
|
|
|
1148
|
-
// Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.
|
|
1149
1156
|
if (config.disableSharedBundles === false) {
|
|
1150
1157
|
for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {
|
|
1151
1158
|
// Find shared bundles in this bundle group.
|
|
@@ -1249,6 +1256,93 @@ export function createIdealGraph(
|
|
|
1249
1256
|
}
|
|
1250
1257
|
}
|
|
1251
1258
|
|
|
1259
|
+
function mergeBundles(
|
|
1260
|
+
bundleGraph: IdealBundleGraph,
|
|
1261
|
+
bundleToKeepId: NodeId,
|
|
1262
|
+
bundleToRemoveId: NodeId,
|
|
1263
|
+
assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>,
|
|
1264
|
+
) {
|
|
1265
|
+
let bundleToKeep = nullthrows(bundleGraph.getNode(bundleToKeepId));
|
|
1266
|
+
let bundleToRemove = nullthrows(bundleGraph.getNode(bundleToRemoveId));
|
|
1267
|
+
invariant(bundleToKeep !== 'root' && bundleToRemove !== 'root');
|
|
1268
|
+
for (let asset of bundleToRemove.assets) {
|
|
1269
|
+
bundleToKeep.assets.add(asset);
|
|
1270
|
+
bundleToKeep.size += asset.stats.size;
|
|
1271
|
+
|
|
1272
|
+
let newAssetReference = assetReference
|
|
1273
|
+
.get(asset)
|
|
1274
|
+
.map(([dep, bundle]) =>
|
|
1275
|
+
bundle === bundleToRemove ? [dep, bundleToKeep] : [dep, bundle],
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
assetReference.set(asset, newAssetReference);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
for (let sourceBundleId of bundleToRemove.sourceBundles) {
|
|
1282
|
+
if (bundleToKeep.sourceBundles.has(sourceBundleId)) {
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
bundleToKeep.sourceBundles.add(sourceBundleId);
|
|
1287
|
+
bundleGraph.addEdge(sourceBundleId, bundleToKeepId);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// Merge any internalized assets
|
|
1291
|
+
if (bundleToRemove.internalizedAssets) {
|
|
1292
|
+
if (bundleToKeep.internalizedAssets) {
|
|
1293
|
+
bundleToKeep.internalizedAssets.union(
|
|
1294
|
+
bundleToRemove.internalizedAssets,
|
|
1295
|
+
);
|
|
1296
|
+
} else {
|
|
1297
|
+
bundleToKeep.internalizedAssets = bundleToRemove.internalizedAssets;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
bundleGraph.removeNode(bundleToRemoveId);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function mergeOverlapBundles() {
|
|
1305
|
+
// Find all shared bundles
|
|
1306
|
+
let sharedBundles = new Set<NodeId>();
|
|
1307
|
+
bundleGraph.traverse((nodeId) => {
|
|
1308
|
+
let bundle = bundleGraph.getNode(nodeId);
|
|
1309
|
+
|
|
1310
|
+
if (!bundle) {
|
|
1311
|
+
throw new Error(`Unable to find bundle ${nodeId} in bundle graph`);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
if (bundle === 'root') {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// Only consider JS shared bundles and non-reused bundles.
|
|
1319
|
+
// These count potentially be considered for merging in future but they're
|
|
1320
|
+
// more complicated to merge
|
|
1321
|
+
if (
|
|
1322
|
+
bundle.sourceBundles.size > 0 &&
|
|
1323
|
+
bundle.manualSharedBundle == null &&
|
|
1324
|
+
!bundle.mainEntryAsset &&
|
|
1325
|
+
bundle.type === 'js'
|
|
1326
|
+
) {
|
|
1327
|
+
sharedBundles.add(nodeId);
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
let clusters = findMergeCandidates(
|
|
1332
|
+
bundleGraph,
|
|
1333
|
+
Array.from(sharedBundles),
|
|
1334
|
+
config.sharedBundleMergeThreshold,
|
|
1335
|
+
);
|
|
1336
|
+
|
|
1337
|
+
for (let cluster of clusters) {
|
|
1338
|
+
let [mergeTarget, ...rest] = cluster;
|
|
1339
|
+
|
|
1340
|
+
for (let bundleIdToMerge of rest) {
|
|
1341
|
+
mergeBundles(bundleGraph, mergeTarget, bundleIdToMerge, assetReference);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1252
1346
|
function getBigIntFromContentKey(contentKey) {
|
|
1253
1347
|
let b = Buffer.alloc(64);
|
|
1254
1348
|
b.write(contentKey);
|