@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.
@@ -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,2 @@
1
+ import type { Asset, Dependency, MutableBundleGraph } from '@atlaspack/types-internal';
2
+ export declare function addJSMonolithBundle(bundleGraph: MutableBundleGraph, entryAsset: Asset, entryDep: Dependency): void;
@@ -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,3 @@
1
+ import type { MutableBundleGraph } from '@atlaspack/types-internal';
2
+ import type { IdealGraph } from './idealGraph';
3
+ export declare function decorateLegacyGraph(idealGraph: IdealGraph, bundleGraph: MutableBundleGraph): void;
@@ -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;
@@ -0,0 +1,2 @@
1
+ export declare function clearCaches(): void;
2
+ export declare function memoize<Args extends Array<unknown>, Return>(fn: (...args: Args) => Return): (...args: Args) => Return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaspack/bundler-default",
3
- "version": "2.14.5-canary.17+a1773d2a6",
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.js",
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.85+a1773d2a6",
20
- "@atlaspack/feature-flags": "2.14.1-canary.85+a1773d2a6",
21
- "@atlaspack/graph": "3.4.1-canary.85+a1773d2a6",
22
- "@atlaspack/plugin": "2.14.5-canary.17+a1773d2a6",
23
- "@atlaspack/rust": "3.2.1-canary.17+a1773d2a6",
24
- "@atlaspack/utils": "2.14.5-canary.17+a1773d2a6",
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
- "gitHead": "a1773d2a62d0ef7805ac7524621dcabcc1afe929"
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 (new Bundler({
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
- let graphs = [];
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
- }): Bundler);
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(node, context, actions) {
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
- // @flow strict-local
2
- import type {Asset, Dependency, MutableBundleGraph} from '@atlaspack/types';
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 (asset.bundleBehavior === 'isolated') {
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 {type SchemaEntity, validateSchema} from '@atlaspack/utils';
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
- sharedBundleMergeThreshold?: number,
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
- [mode: BuildMode]: BaseBundlerConfig,
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
- sharedBundleMergeThreshold: number,
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
- sharedBundleMergeThreshold: 1,
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
- sharedBundleMergeThreshold: 1,
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
- sharedBundleMergeThreshold:
244
- modeConfig.sharedBundleMergeThreshold ??
245
- defaults.sharedBundleMergeThreshold,
273
+ sharedBundleMerge:
274
+ modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
246
275
  maxParallelRequests:
247
276
  modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
248
277
  projectRoot: options.projectRoot,