@atlaspack/bundler-default 2.14.5-canary.17 → 2.14.5-canary.170
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 +370 -0
- package/lib/DefaultBundler.js +6 -1
- package/lib/MonolithicBundler.js +2 -2
- package/lib/bundleMerge.js +106 -37
- package/lib/bundlerConfig.js +30 -5
- package/lib/decorateLegacyGraph.js +24 -3
- package/lib/idealGraph.js +227 -46
- package/lib/memoize.js +39 -0
- package/lib/types/DefaultBundler.d.ts +18 -0
- package/lib/types/MonolithicBundler.d.ts +2 -0
- package/lib/types/bundleMerge.d.ts +9 -0
- package/lib/types/bundlerConfig.d.ts +27 -0
- package/lib/types/decorateLegacyGraph.d.ts +3 -0
- package/lib/types/idealGraph.d.ts +40 -0
- package/lib/types/memoize.d.ts +2 -0
- package/package.json +17 -10
- package/src/{DefaultBundler.js → DefaultBundler.ts} +21 -6
- package/src/{MonolithicBundler.js → MonolithicBundler.ts} +10 -4
- package/src/bundleMerge.ts +250 -0
- package/src/{bundlerConfig.js → bundlerConfig.ts} +73 -44
- package/src/{decorateLegacyGraph.js → decorateLegacyGraph.ts} +26 -7
- package/src/{idealGraph.js → idealGraph.ts} +391 -102
- package/src/memoize.ts +32 -0
- package/tsconfig.json +4 -0
- package/src/bundleMerge.js +0 -103
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Bundler } from '@atlaspack/plugin';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* The Bundler works by creating an IdealGraph, which contains a BundleGraph that models bundles
|
|
5
|
+
* connected to other bundles by what references them, and thus models BundleGroups.
|
|
6
|
+
*
|
|
7
|
+
* First, we enter `bundle({bundleGraph, config})`. Here, "bundleGraph" is actually just the
|
|
8
|
+
* assetGraph turned into a type `MutableBundleGraph`, which will then be mutated in decorate,
|
|
9
|
+
* and turned into what we expect the bundleGraph to be as per the old (default) bundler structure
|
|
10
|
+
* & what the rest of Atlaspack expects a BundleGraph to be.
|
|
11
|
+
*
|
|
12
|
+
* `bundle({bundleGraph, config})` First gets a Mapping of target to entries, In most cases there is
|
|
13
|
+
* only one target, and one or more entries. (Targets are pertinent in monorepos or projects where you
|
|
14
|
+
* will have two or more distDirs, or output folders.) Then calls create IdealGraph and Decorate per target.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
declare const _default: Bundler<unknown>;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NodeId } from '@atlaspack/graph';
|
|
2
|
+
import type { IdealBundleGraph } from './idealGraph';
|
|
3
|
+
export type MergeGroup = {
|
|
4
|
+
overlapThreshold?: number;
|
|
5
|
+
maxBundleSize?: number;
|
|
6
|
+
sourceBundles?: Array<NodeId>;
|
|
7
|
+
minBundlesInGroup?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function findMergeCandidates(bundleGraph: IdealBundleGraph, bundles: Array<NodeId>, config: Array<MergeGroup>): Array<Array<NodeId>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Config, PluginOptions, PluginLogger } from '@atlaspack/types-internal';
|
|
2
|
+
type Glob = string;
|
|
3
|
+
type ManualSharedBundles = Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
assets: Array<Glob>;
|
|
6
|
+
types?: Array<string>;
|
|
7
|
+
root?: string;
|
|
8
|
+
split?: number;
|
|
9
|
+
}>;
|
|
10
|
+
export type MergeCandidates = Array<{
|
|
11
|
+
overlapThreshold?: number;
|
|
12
|
+
maxBundleSize?: number;
|
|
13
|
+
sourceBundles?: Array<string>;
|
|
14
|
+
minBundlesInGroup?: number;
|
|
15
|
+
}>;
|
|
16
|
+
export type ResolvedBundlerConfig = {
|
|
17
|
+
minBundles: number;
|
|
18
|
+
minBundleSize: number;
|
|
19
|
+
maxParallelRequests: number;
|
|
20
|
+
projectRoot: string;
|
|
21
|
+
disableSharedBundles: boolean;
|
|
22
|
+
manualSharedBundles: ManualSharedBundles;
|
|
23
|
+
loadConditionalBundlesInParallel?: boolean;
|
|
24
|
+
sharedBundleMerge?: MergeCandidates;
|
|
25
|
+
};
|
|
26
|
+
export declare function loadBundlerConfig(config: Config, options: PluginOptions, logger: PluginLogger): Promise<ResolvedBundlerConfig>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BitSet, ContentGraph, Graph, NodeId } from '@atlaspack/graph';
|
|
2
|
+
import type { Asset, BundleBehavior, Dependency, Environment, MutableBundleGraph, Target, PluginLogger } from '@atlaspack/types';
|
|
3
|
+
import { DefaultMap } from '@atlaspack/utils';
|
|
4
|
+
import type { ResolvedBundlerConfig } from './bundlerConfig';
|
|
5
|
+
export type Bundle = {
|
|
6
|
+
uniqueKey: string | null | undefined;
|
|
7
|
+
assets: Set<Asset>;
|
|
8
|
+
internalizedAssets?: BitSet;
|
|
9
|
+
bundleBehavior?: BundleBehavior | null | undefined;
|
|
10
|
+
needsStableName: boolean;
|
|
11
|
+
mainEntryAsset: Asset | null | undefined;
|
|
12
|
+
bundleRoots: Set<Asset>;
|
|
13
|
+
size: number;
|
|
14
|
+
sourceBundles: Set<NodeId>;
|
|
15
|
+
target: Target;
|
|
16
|
+
env: Environment;
|
|
17
|
+
type: string;
|
|
18
|
+
manualSharedBundle: string | null | undefined;
|
|
19
|
+
};
|
|
20
|
+
export type DependencyBundleGraph = ContentGraph<{
|
|
21
|
+
value: Bundle;
|
|
22
|
+
type: 'bundle';
|
|
23
|
+
} | {
|
|
24
|
+
value: Dependency;
|
|
25
|
+
type: 'dependency';
|
|
26
|
+
}, number>;
|
|
27
|
+
export declare const idealBundleGraphEdges: Readonly<{
|
|
28
|
+
default: 1;
|
|
29
|
+
conditional: 2;
|
|
30
|
+
}>;
|
|
31
|
+
export type IdealBundleGraph = Graph<Bundle | 'root', (typeof idealBundleGraphEdges)[keyof typeof idealBundleGraphEdges]>;
|
|
32
|
+
export type IdealGraph = {
|
|
33
|
+
assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>;
|
|
34
|
+
assets: Array<Asset>;
|
|
35
|
+
bundleGraph: IdealBundleGraph;
|
|
36
|
+
bundleGroupBundleIds: Set<NodeId>;
|
|
37
|
+
dependencyBundleGraph: DependencyBundleGraph;
|
|
38
|
+
manualAssetToBundle: Map<Asset, NodeId>;
|
|
39
|
+
};
|
|
40
|
+
export declare function createIdealGraph(assetGraph: MutableBundleGraph, config: ResolvedBundlerConfig, entries: Map<Asset, Dependency>, logger: PluginLogger): IdealGraph;
|
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.170+c940954d2",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"publishConfig": {
|
|
@@ -10,19 +10,26 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/atlassian-labs/atlaspack.git"
|
|
12
12
|
},
|
|
13
|
-
"main": "lib/DefaultBundler.js",
|
|
14
|
-
"source": "src/DefaultBundler.
|
|
13
|
+
"main": "./lib/DefaultBundler.js",
|
|
14
|
+
"source": "./src/DefaultBundler.ts",
|
|
15
|
+
"types": "./lib/types/DefaultBundler.d.ts",
|
|
15
16
|
"engines": {
|
|
16
17
|
"node": ">= 16.0.0"
|
|
17
18
|
},
|
|
18
19
|
"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/
|
|
20
|
+
"@atlaspack/diagnostic": "2.14.1-canary.238+c940954d2",
|
|
21
|
+
"@atlaspack/feature-flags": "2.14.1-canary.238+c940954d2",
|
|
22
|
+
"@atlaspack/graph": "3.4.1-canary.238+c940954d2",
|
|
23
|
+
"@atlaspack/plugin": "2.14.5-canary.170+c940954d2",
|
|
24
|
+
"@atlaspack/rust": "3.2.1-canary.170+c940954d2",
|
|
25
|
+
"@atlaspack/types-internal": "2.14.1-canary.238+c940954d2",
|
|
26
|
+
"@atlaspack/utils": "2.14.5-canary.170+c940954d2",
|
|
27
|
+
"many-keys-map": "^1.0.3",
|
|
25
28
|
"nullthrows": "^1.1.1"
|
|
26
29
|
},
|
|
27
|
-
"
|
|
30
|
+
"scripts": {
|
|
31
|
+
"check-ts": "tsc --emitDeclarationOnly --rootDir src",
|
|
32
|
+
"build:lib": "gulp build --gulpfile ../../../gulpfile.js --cwd ."
|
|
33
|
+
},
|
|
34
|
+
"gitHead": "c940954d27d657c3c93cd328dfb394778da46eab"
|
|
28
35
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @flow strict-local
|
|
2
|
-
|
|
3
1
|
import {Bundler} from '@atlaspack/plugin';
|
|
4
2
|
import type {Asset, Dependency, MutableBundleGraph} from '@atlaspack/types';
|
|
5
3
|
import {DefaultMap} from '@atlaspack/utils';
|
|
@@ -25,14 +23,15 @@ import {addJSMonolithBundle} from './MonolithicBundler';
|
|
|
25
23
|
* will have two or more distDirs, or output folders.) Then calls create IdealGraph and Decorate per target.
|
|
26
24
|
*
|
|
27
25
|
*/
|
|
28
|
-
export default
|
|
26
|
+
export default new Bundler({
|
|
29
27
|
loadConfig({config, options, logger}) {
|
|
30
28
|
return loadBundlerConfig(config, options, logger);
|
|
31
29
|
},
|
|
32
30
|
|
|
33
31
|
bundle({bundleGraph, config, logger}) {
|
|
34
32
|
let targetMap = getEntryByTarget(bundleGraph); // Organize entries by target output folder/ distDir
|
|
35
|
-
|
|
33
|
+
// @ts-expect-error TS2304
|
|
34
|
+
let graphs: Array<IdealGraph> = [];
|
|
36
35
|
|
|
37
36
|
for (let entries of targetMap.values()) {
|
|
38
37
|
let singleFileEntries = new Map();
|
|
@@ -64,7 +63,7 @@ export default (new Bundler({
|
|
|
64
63
|
}
|
|
65
64
|
},
|
|
66
65
|
optimize() {},
|
|
67
|
-
})
|
|
66
|
+
}) as Bundler<unknown>;
|
|
68
67
|
|
|
69
68
|
function getEntryByTarget(
|
|
70
69
|
bundleGraph: MutableBundleGraph,
|
|
@@ -74,7 +73,23 @@ function getEntryByTarget(
|
|
|
74
73
|
() => new Map(),
|
|
75
74
|
);
|
|
76
75
|
bundleGraph.traverse({
|
|
77
|
-
enter(
|
|
76
|
+
enter(
|
|
77
|
+
// @ts-expect-error TS2304
|
|
78
|
+
node: BundleGraphTraversable,
|
|
79
|
+
context:
|
|
80
|
+
| {
|
|
81
|
+
readonly type: 'asset';
|
|
82
|
+
value: Asset;
|
|
83
|
+
}
|
|
84
|
+
| null
|
|
85
|
+
| undefined
|
|
86
|
+
| {
|
|
87
|
+
readonly type: 'dependency';
|
|
88
|
+
value: Dependency;
|
|
89
|
+
},
|
|
90
|
+
// @ts-expect-error TS2304
|
|
91
|
+
actions: TraversalActions,
|
|
92
|
+
) {
|
|
78
93
|
if (node.type !== 'asset') {
|
|
79
94
|
return node;
|
|
80
95
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
Asset,
|
|
3
|
+
Dependency,
|
|
4
|
+
MutableBundleGraph,
|
|
5
|
+
} from '@atlaspack/types-internal';
|
|
3
6
|
import nullthrows from 'nullthrows';
|
|
4
7
|
|
|
5
8
|
export function addJSMonolithBundle(
|
|
@@ -39,9 +42,12 @@ export function addJSMonolithBundle(
|
|
|
39
42
|
let assets = bundleGraph.getDependencyAssets(dependency);
|
|
40
43
|
|
|
41
44
|
for (const asset of assets) {
|
|
42
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
asset.bundleBehavior === 'isolated' ||
|
|
47
|
+
asset.bundleBehavior === 'inlineIsolated'
|
|
48
|
+
) {
|
|
43
49
|
throw new Error(
|
|
44
|
-
'Isolated assets are not supported for single file output builds
|
|
50
|
+
`${asset.bundleBehavior === 'isolated' ? 'Isolated' : 'Inline isolated'} assets are not supported for single file output builds`,
|
|
45
51
|
);
|
|
46
52
|
}
|
|
47
53
|
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import invariant from 'assert';
|
|
2
|
+
import nullthrows from 'nullthrows';
|
|
3
|
+
import {ContentGraph} from '@atlaspack/graph';
|
|
4
|
+
import type {NodeId} from '@atlaspack/graph';
|
|
5
|
+
import {setUnion, setIntersectStatic} from '@atlaspack/utils';
|
|
6
|
+
import type {Bundle, IdealBundleGraph} from './idealGraph';
|
|
7
|
+
import {memoize, clearCaches} from './memoize';
|
|
8
|
+
|
|
9
|
+
function getBundlesForBundleGroup(
|
|
10
|
+
bundleGraph: IdealBundleGraph,
|
|
11
|
+
bundleGroupId: NodeId,
|
|
12
|
+
): number {
|
|
13
|
+
let count = 0;
|
|
14
|
+
bundleGraph.traverse((nodeId) => {
|
|
15
|
+
const node = bundleGraph.getNode(nodeId);
|
|
16
|
+
if (
|
|
17
|
+
node &&
|
|
18
|
+
(node === 'root' ||
|
|
19
|
+
(node.bundleBehavior !== 'inline' &&
|
|
20
|
+
node.bundleBehavior !== 'inlineIsolated'))
|
|
21
|
+
) {
|
|
22
|
+
count++;
|
|
23
|
+
}
|
|
24
|
+
}, bundleGroupId);
|
|
25
|
+
return count;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let getBundleOverlap = (
|
|
29
|
+
sourceBundlesA: Set<NodeId>,
|
|
30
|
+
sourceBundlesB: Set<NodeId>,
|
|
31
|
+
): number => {
|
|
32
|
+
let allSourceBundles = setUnion(sourceBundlesA, sourceBundlesB);
|
|
33
|
+
let sharedSourceBundles = setIntersectStatic(sourceBundlesA, sourceBundlesB);
|
|
34
|
+
|
|
35
|
+
return sharedSourceBundles.size / allSourceBundles.size;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Returns a decimal showing the proportion source bundles are common to
|
|
39
|
+
// both bundles versus the total number of source bundles.
|
|
40
|
+
function checkBundleThreshold(
|
|
41
|
+
bundleA: MergeCandidate,
|
|
42
|
+
bundleB: MergeCandidate,
|
|
43
|
+
threshold: number,
|
|
44
|
+
): boolean {
|
|
45
|
+
return (
|
|
46
|
+
getBundleOverlap(
|
|
47
|
+
bundleA.bundle.sourceBundles,
|
|
48
|
+
bundleB.bundle.sourceBundles,
|
|
49
|
+
) >= threshold
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let checkSharedSourceBundles = memoize(
|
|
54
|
+
(bundle: Bundle, importantAncestorBundles: Array<NodeId>): boolean => {
|
|
55
|
+
return importantAncestorBundles.every((ancestorId) =>
|
|
56
|
+
bundle.sourceBundles.has(ancestorId),
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
let hasSuitableBundleGroup = memoize(
|
|
62
|
+
(
|
|
63
|
+
bundleGraph: IdealBundleGraph,
|
|
64
|
+
bundle: Bundle,
|
|
65
|
+
minBundlesInGroup: number,
|
|
66
|
+
): boolean => {
|
|
67
|
+
for (let sourceBundle of bundle.sourceBundles) {
|
|
68
|
+
let bundlesInGroup = getBundlesForBundleGroup(bundleGraph, sourceBundle);
|
|
69
|
+
|
|
70
|
+
if (bundlesInGroup >= minBundlesInGroup) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
function validMerge(
|
|
79
|
+
bundleGraph: IdealBundleGraph,
|
|
80
|
+
config: MergeGroup,
|
|
81
|
+
bundleA: MergeCandidate,
|
|
82
|
+
bundleB: MergeCandidate,
|
|
83
|
+
): boolean {
|
|
84
|
+
if (config.maxBundleSize != null) {
|
|
85
|
+
if (
|
|
86
|
+
bundleA.bundle.size > config.maxBundleSize ||
|
|
87
|
+
bundleB.bundle.size > config.maxBundleSize
|
|
88
|
+
) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (config.overlapThreshold != null) {
|
|
94
|
+
if (!checkBundleThreshold(bundleA, bundleB, config.overlapThreshold)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (config.sourceBundles != null) {
|
|
100
|
+
if (
|
|
101
|
+
!checkSharedSourceBundles(bundleA.bundle, config.sourceBundles) ||
|
|
102
|
+
!checkSharedSourceBundles(bundleB.bundle, config.sourceBundles)
|
|
103
|
+
) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (config.minBundlesInGroup != null) {
|
|
109
|
+
if (
|
|
110
|
+
!hasSuitableBundleGroup(
|
|
111
|
+
bundleGraph,
|
|
112
|
+
bundleA.bundle,
|
|
113
|
+
config.minBundlesInGroup,
|
|
114
|
+
) ||
|
|
115
|
+
!hasSuitableBundleGroup(
|
|
116
|
+
bundleGraph,
|
|
117
|
+
bundleB.bundle,
|
|
118
|
+
config.minBundlesInGroup,
|
|
119
|
+
)
|
|
120
|
+
) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getMergeClusters(
|
|
129
|
+
graph: ContentGraph<NodeId, EdgeType>,
|
|
130
|
+
candidates: Map<NodeId, EdgeType>,
|
|
131
|
+
): Array<Array<NodeId>> {
|
|
132
|
+
let clusters: Array<Array<NodeId>> = [];
|
|
133
|
+
|
|
134
|
+
for (let [candidate, edgeType] of candidates.entries()) {
|
|
135
|
+
let cluster: Array<NodeId> = [];
|
|
136
|
+
|
|
137
|
+
graph.traverse(
|
|
138
|
+
(nodeId) => {
|
|
139
|
+
cluster.push(nullthrows(graph.getNode(nodeId)));
|
|
140
|
+
// Remove node from candidates as it has already been processed
|
|
141
|
+
candidates.delete(nodeId);
|
|
142
|
+
},
|
|
143
|
+
candidate,
|
|
144
|
+
edgeType,
|
|
145
|
+
);
|
|
146
|
+
clusters.push(cluster);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return clusters;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
type MergeCandidate = {
|
|
153
|
+
bundle: Bundle;
|
|
154
|
+
id: NodeId;
|
|
155
|
+
contentKey: string;
|
|
156
|
+
};
|
|
157
|
+
function getPossibleMergeCandidates(
|
|
158
|
+
bundleGraph: IdealBundleGraph,
|
|
159
|
+
bundles: Array<NodeId>,
|
|
160
|
+
): Array<[MergeCandidate, MergeCandidate]> {
|
|
161
|
+
let mergeCandidates = bundles.map((bundleId) => {
|
|
162
|
+
let bundle = bundleGraph.getNode(bundleId);
|
|
163
|
+
invariant(bundle && bundle !== 'root', 'Bundle should exist');
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
id: bundleId,
|
|
167
|
+
bundle,
|
|
168
|
+
contentKey: bundleId.toString(),
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const uniquePairs: Array<[MergeCandidate, MergeCandidate]> = [];
|
|
173
|
+
|
|
174
|
+
for (let i = 0; i < mergeCandidates.length; i++) {
|
|
175
|
+
for (let j = i + 1; j < mergeCandidates.length; j++) {
|
|
176
|
+
let a = mergeCandidates[i];
|
|
177
|
+
let b = mergeCandidates[j];
|
|
178
|
+
|
|
179
|
+
// @ts-expect-error TS18048
|
|
180
|
+
if (a.bundle.internalizedAssets.equals(b.bundle.internalizedAssets)) {
|
|
181
|
+
uniquePairs.push([a, b]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return uniquePairs;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type MergeGroup = {
|
|
189
|
+
overlapThreshold?: number;
|
|
190
|
+
maxBundleSize?: number;
|
|
191
|
+
sourceBundles?: Array<NodeId>;
|
|
192
|
+
minBundlesInGroup?: number;
|
|
193
|
+
};
|
|
194
|
+
type EdgeType = number;
|
|
195
|
+
|
|
196
|
+
export function findMergeCandidates(
|
|
197
|
+
bundleGraph: IdealBundleGraph,
|
|
198
|
+
bundles: Array<NodeId>,
|
|
199
|
+
config: Array<MergeGroup>,
|
|
200
|
+
): Array<Array<NodeId>> {
|
|
201
|
+
let graph = new ContentGraph<NodeId, EdgeType>();
|
|
202
|
+
let candidates = new Map<NodeId, EdgeType>();
|
|
203
|
+
|
|
204
|
+
let allPossibleMergeCandidates = getPossibleMergeCandidates(
|
|
205
|
+
bundleGraph,
|
|
206
|
+
bundles,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Build graph of clustered merge candidates
|
|
210
|
+
for (let i = 0; i < config.length; i++) {
|
|
211
|
+
// Ensure edge type coresponds to config index
|
|
212
|
+
let edgeType = i + 1;
|
|
213
|
+
|
|
214
|
+
for (let group of allPossibleMergeCandidates) {
|
|
215
|
+
let candidateA = group[0];
|
|
216
|
+
let candidateB = group[1];
|
|
217
|
+
|
|
218
|
+
if (!validMerge(bundleGraph, config[i], candidateA, candidateB)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let bundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
223
|
+
candidateA.contentKey,
|
|
224
|
+
candidateA.id,
|
|
225
|
+
);
|
|
226
|
+
let otherBundleNode = graph.addNodeByContentKeyIfNeeded(
|
|
227
|
+
candidateB.contentKey,
|
|
228
|
+
candidateB.id,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// Add edge in both directions
|
|
232
|
+
graph.addEdge(bundleNode, otherBundleNode, edgeType);
|
|
233
|
+
graph.addEdge(otherBundleNode, bundleNode, edgeType);
|
|
234
|
+
|
|
235
|
+
candidates.set(bundleNode, edgeType);
|
|
236
|
+
candidates.set(otherBundleNode, edgeType);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Remove bundles that have been allocated to a higher priority merge
|
|
240
|
+
allPossibleMergeCandidates = allPossibleMergeCandidates.filter(
|
|
241
|
+
(group) =>
|
|
242
|
+
!graph.hasContentKey(group[0].contentKey) &&
|
|
243
|
+
!graph.hasContentKey(group[1].contentKey),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
clearCaches();
|
|
248
|
+
|
|
249
|
+
return getMergeClusters(graph, candidates);
|
|
250
|
+
}
|
|
@@ -1,62 +1,67 @@
|
|
|
1
|
-
// @flow strict-local
|
|
2
|
-
|
|
3
1
|
import {encodeJSONKeyComponent} from '@atlaspack/diagnostic';
|
|
4
2
|
import type {
|
|
5
3
|
Config,
|
|
6
4
|
PluginOptions,
|
|
7
5
|
BuildMode,
|
|
8
6
|
PluginLogger,
|
|
9
|
-
} from '@atlaspack/types';
|
|
7
|
+
} from '@atlaspack/types-internal';
|
|
10
8
|
import {getFeatureFlag} from '@atlaspack/feature-flags';
|
|
11
|
-
import {
|
|
9
|
+
import {SchemaEntity, validateSchema} from '@atlaspack/utils';
|
|
12
10
|
import invariant from 'assert';
|
|
13
11
|
|
|
14
12
|
type Glob = string;
|
|
15
13
|
|
|
16
|
-
type ManualSharedBundles = Array<{
|
|
17
|
-
name: string
|
|
18
|
-
assets: Array<Glob
|
|
19
|
-
types?: Array<string
|
|
20
|
-
root?: string
|
|
21
|
-
split?: number
|
|
22
|
-
|
|
14
|
+
type ManualSharedBundles = Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
assets: Array<Glob>;
|
|
17
|
+
types?: Array<string>;
|
|
18
|
+
root?: string;
|
|
19
|
+
split?: number;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
export type MergeCandidates = Array<{
|
|
23
|
+
overlapThreshold?: number;
|
|
24
|
+
maxBundleSize?: number;
|
|
25
|
+
sourceBundles?: Array<string>;
|
|
26
|
+
minBundlesInGroup?: number;
|
|
27
|
+
}>;
|
|
23
28
|
|
|
24
|
-
type BaseBundlerConfig = {
|
|
25
|
-
http?: number
|
|
26
|
-
minBundles?: number
|
|
27
|
-
minBundleSize?: number
|
|
28
|
-
maxParallelRequests?: number
|
|
29
|
-
disableSharedBundles?: boolean
|
|
30
|
-
manualSharedBundles?: ManualSharedBundles
|
|
31
|
-
loadConditionalBundlesInParallel?: boolean
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
type BaseBundlerConfig = {
|
|
30
|
+
http?: number;
|
|
31
|
+
minBundles?: number;
|
|
32
|
+
minBundleSize?: number;
|
|
33
|
+
maxParallelRequests?: number;
|
|
34
|
+
disableSharedBundles?: boolean;
|
|
35
|
+
manualSharedBundles?: ManualSharedBundles;
|
|
36
|
+
loadConditionalBundlesInParallel?: boolean;
|
|
37
|
+
sharedBundleMerge?: MergeCandidates;
|
|
38
|
+
};
|
|
34
39
|
|
|
35
|
-
type BundlerConfig =
|
|
36
|
-
|
|
37
|
-
|} & BaseBundlerConfig;
|
|
40
|
+
type BundlerConfig = Partial<Record<BuildMode, BaseBundlerConfig>> &
|
|
41
|
+
BaseBundlerConfig;
|
|
38
42
|
|
|
39
|
-
export type ResolvedBundlerConfig = {
|
|
40
|
-
minBundles: number
|
|
41
|
-
minBundleSize: number
|
|
42
|
-
maxParallelRequests: number
|
|
43
|
-
projectRoot: string
|
|
44
|
-
disableSharedBundles: boolean
|
|
45
|
-
manualSharedBundles: ManualSharedBundles
|
|
46
|
-
loadConditionalBundlesInParallel?: boolean
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
export type ResolvedBundlerConfig = {
|
|
44
|
+
minBundles: number;
|
|
45
|
+
minBundleSize: number;
|
|
46
|
+
maxParallelRequests: number;
|
|
47
|
+
projectRoot: string;
|
|
48
|
+
disableSharedBundles: boolean;
|
|
49
|
+
manualSharedBundles: ManualSharedBundles;
|
|
50
|
+
loadConditionalBundlesInParallel?: boolean;
|
|
51
|
+
sharedBundleMerge?: MergeCandidates;
|
|
52
|
+
};
|
|
49
53
|
|
|
50
54
|
function resolveModeConfig(
|
|
51
55
|
config: BundlerConfig,
|
|
52
56
|
mode: BuildMode,
|
|
53
57
|
): BaseBundlerConfig {
|
|
54
|
-
let generalConfig = {};
|
|
55
|
-
let modeConfig = {};
|
|
58
|
+
let generalConfig: Record<string, any> = {};
|
|
59
|
+
let modeConfig: Record<string, any> = {};
|
|
56
60
|
|
|
57
61
|
for (const key of Object.keys(config)) {
|
|
58
62
|
if (key === 'development' || key === 'production') {
|
|
59
63
|
if (key === mode) {
|
|
64
|
+
// @ts-expect-error TS2322
|
|
60
65
|
modeConfig = config[key];
|
|
61
66
|
}
|
|
62
67
|
} else {
|
|
@@ -64,7 +69,6 @@ function resolveModeConfig(
|
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
// $FlowFixMe Not sure how to convince flow here...
|
|
68
72
|
return {
|
|
69
73
|
...generalConfig,
|
|
70
74
|
...modeConfig,
|
|
@@ -79,7 +83,7 @@ const HTTP_OPTIONS = {
|
|
|
79
83
|
minBundleSize: 30000,
|
|
80
84
|
maxParallelRequests: 6,
|
|
81
85
|
disableSharedBundles: false,
|
|
82
|
-
|
|
86
|
+
sharedBundleMerge: [],
|
|
83
87
|
},
|
|
84
88
|
'2': {
|
|
85
89
|
minBundles: 1,
|
|
@@ -87,9 +91,9 @@ const HTTP_OPTIONS = {
|
|
|
87
91
|
minBundleSize: 20000,
|
|
88
92
|
maxParallelRequests: 25,
|
|
89
93
|
disableSharedBundles: false,
|
|
90
|
-
|
|
94
|
+
sharedBundleMerge: [],
|
|
91
95
|
},
|
|
92
|
-
};
|
|
96
|
+
} as const;
|
|
93
97
|
|
|
94
98
|
const CONFIG_SCHEMA: SchemaEntity = {
|
|
95
99
|
type: 'object',
|
|
@@ -129,6 +133,30 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
129
133
|
additionalProperties: false,
|
|
130
134
|
},
|
|
131
135
|
},
|
|
136
|
+
sharedBundleMerge: {
|
|
137
|
+
type: 'array',
|
|
138
|
+
items: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
overlapThreshold: {
|
|
142
|
+
type: 'number',
|
|
143
|
+
},
|
|
144
|
+
maxBundleSize: {
|
|
145
|
+
type: 'number',
|
|
146
|
+
},
|
|
147
|
+
sourceBundles: {
|
|
148
|
+
type: 'array',
|
|
149
|
+
items: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
minBundlesInGroup: {
|
|
154
|
+
type: 'number',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
additionalProperties: false,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
132
160
|
minBundles: {
|
|
133
161
|
type: 'number',
|
|
134
162
|
},
|
|
@@ -172,7 +200,8 @@ export async function loadBundlerConfig(
|
|
|
172
200
|
const modDefault = {
|
|
173
201
|
...HTTP_OPTIONS['2'],
|
|
174
202
|
projectRoot: options.projectRoot,
|
|
175
|
-
};
|
|
203
|
+
} as const;
|
|
204
|
+
// @ts-expect-error TS2322
|
|
176
205
|
return modDefault;
|
|
177
206
|
}
|
|
178
207
|
|
|
@@ -235,14 +264,14 @@ export async function loadBundlerConfig(
|
|
|
235
264
|
);
|
|
236
265
|
|
|
237
266
|
let http = modeConfig.http ?? 2;
|
|
267
|
+
// @ts-expect-error TS7053
|
|
238
268
|
let defaults = HTTP_OPTIONS[http];
|
|
239
269
|
|
|
240
270
|
return {
|
|
241
271
|
minBundles: modeConfig.minBundles ?? defaults.minBundles,
|
|
242
272
|
minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
|
|
243
|
-
|
|
244
|
-
modeConfig.
|
|
245
|
-
defaults.sharedBundleMergeThreshold,
|
|
273
|
+
sharedBundleMerge:
|
|
274
|
+
modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
|
|
246
275
|
maxParallelRequests:
|
|
247
276
|
modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
|
|
248
277
|
projectRoot: options.projectRoot,
|