@atlaspack/bundler-default 2.14.5-canary.36 → 2.14.5-canary.361

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +613 -0
  2. package/dist/DefaultBundler.js +84 -0
  3. package/dist/MonolithicBundler.js +68 -0
  4. package/dist/bundleMerge.js +137 -0
  5. package/dist/bundlerConfig.js +223 -0
  6. package/dist/decorateLegacyGraph.js +189 -0
  7. package/dist/idealGraph.js +1471 -0
  8. package/dist/memoize.js +31 -0
  9. package/dist/stats.js +69 -0
  10. package/lib/DefaultBundler.js +6 -1
  11. package/lib/MonolithicBundler.js +11 -3
  12. package/lib/bundleMerge.js +106 -37
  13. package/lib/bundlerConfig.js +52 -6
  14. package/lib/decorateLegacyGraph.js +24 -3
  15. package/lib/idealGraph.js +410 -55
  16. package/lib/memoize.js +39 -0
  17. package/lib/stats.js +85 -0
  18. package/lib/types/DefaultBundler.d.ts +18 -0
  19. package/lib/types/MonolithicBundler.d.ts +2 -0
  20. package/lib/types/bundleMerge.d.ts +9 -0
  21. package/lib/types/bundlerConfig.d.ts +36 -0
  22. package/lib/types/decorateLegacyGraph.d.ts +3 -0
  23. package/lib/types/idealGraph.d.ts +40 -0
  24. package/lib/types/memoize.d.ts +2 -0
  25. package/lib/types/stats.d.ts +16 -0
  26. package/package.json +20 -12
  27. package/src/{DefaultBundler.js → DefaultBundler.ts} +21 -6
  28. package/src/{MonolithicBundler.js → MonolithicBundler.ts} +17 -5
  29. package/src/bundleMerge.ts +250 -0
  30. package/src/{bundlerConfig.js → bundlerConfig.ts} +106 -45
  31. package/src/{decorateLegacyGraph.js → decorateLegacyGraph.ts} +26 -7
  32. package/src/{idealGraph.js → idealGraph.ts} +729 -137
  33. package/src/memoize.ts +32 -0
  34. package/src/stats.ts +97 -0
  35. package/tsconfig.json +30 -0
  36. package/tsconfig.tsbuildinfo +1 -0
  37. package/src/bundleMerge.js +0 -103
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const plugin_1 = require("@atlaspack/plugin");
7
+ const utils_1 = require("@atlaspack/utils");
8
+ const assert_1 = __importDefault(require("assert"));
9
+ const bundlerConfig_1 = require("./bundlerConfig");
10
+ const decorateLegacyGraph_1 = require("./decorateLegacyGraph");
11
+ const idealGraph_1 = require("./idealGraph");
12
+ const MonolithicBundler_1 = require("./MonolithicBundler");
13
+ /**
14
+ *
15
+ * The Bundler works by creating an IdealGraph, which contains a BundleGraph that models bundles
16
+ * connected to other bundles by what references them, and thus models BundleGroups.
17
+ *
18
+ * First, we enter `bundle({bundleGraph, config})`. Here, "bundleGraph" is actually just the
19
+ * assetGraph turned into a type `MutableBundleGraph`, which will then be mutated in decorate,
20
+ * and turned into what we expect the bundleGraph to be as per the old (default) bundler structure
21
+ * & what the rest of Atlaspack expects a BundleGraph to be.
22
+ *
23
+ * `bundle({bundleGraph, config})` First gets a Mapping of target to entries, In most cases there is
24
+ * only one target, and one or more entries. (Targets are pertinent in monorepos or projects where you
25
+ * will have two or more distDirs, or output folders.) Then calls create IdealGraph and Decorate per target.
26
+ *
27
+ */
28
+ exports.default = new plugin_1.Bundler({
29
+ loadConfig({ config, options, logger }) {
30
+ return (0, bundlerConfig_1.loadBundlerConfig)(config, options, logger);
31
+ },
32
+ bundle({ bundleGraph, config, logger }) {
33
+ let targetMap = getEntryByTarget(bundleGraph); // Organize entries by target output folder/ distDir
34
+ // @ts-expect-error TS2304
35
+ let graphs = [];
36
+ for (let entries of targetMap.values()) {
37
+ let singleFileEntries = new Map();
38
+ let idealGraphEntries = new Map();
39
+ // Separate out the monolith bundles based on the option on target
40
+ for (let [entryAsset, entryDep] of entries.entries()) {
41
+ if (entryDep.target?.env.unstableSingleFileOutput === true) {
42
+ singleFileEntries.set(entryAsset, entryDep);
43
+ }
44
+ else {
45
+ idealGraphEntries.set(entryAsset, entryDep);
46
+ }
47
+ }
48
+ // Create separate bundleGraphs per distDir
49
+ graphs.push((0, idealGraph_1.createIdealGraph)(bundleGraph, config, idealGraphEntries, logger));
50
+ // Do this after the ideal graph so that the mutation of the bundleGraph doesn't
51
+ // interfere with the main bundling algorithm
52
+ for (let [entryAsset, entryDep] of singleFileEntries.entries()) {
53
+ (0, MonolithicBundler_1.addJSMonolithBundle)(bundleGraph, entryAsset, entryDep);
54
+ }
55
+ }
56
+ for (let g of graphs) {
57
+ (0, decorateLegacyGraph_1.decorateLegacyGraph)(g, bundleGraph); //mutate original graph
58
+ }
59
+ },
60
+ optimize() { },
61
+ });
62
+ function getEntryByTarget(bundleGraph) {
63
+ // Find entries from assetGraph per target
64
+ let targets = new utils_1.DefaultMap(() => new Map());
65
+ bundleGraph.traverse({
66
+ enter(
67
+ // @ts-expect-error TS2304
68
+ node, context,
69
+ // @ts-expect-error TS2304
70
+ actions) {
71
+ if (node.type !== 'asset') {
72
+ return node;
73
+ }
74
+ (0, assert_1.default)(context != null &&
75
+ context.type === 'dependency' &&
76
+ context.value.isEntry &&
77
+ context.value.target != null);
78
+ targets.get(context.value.target.distDir).set(node.value, context.value);
79
+ actions.skipChildren();
80
+ return node;
81
+ },
82
+ });
83
+ return targets;
84
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addJSMonolithBundle = addJSMonolithBundle;
7
+ const feature_flags_1 = require("@atlaspack/feature-flags");
8
+ const nullthrows_1 = __importDefault(require("nullthrows"));
9
+ function addJSMonolithBundle(bundleGraph, entryAsset, entryDep) {
10
+ let target = (0, nullthrows_1.default)(entryDep.target, 'Expected dependency to have a valid target');
11
+ // Create a single bundle to hold all JS assets
12
+ let bundle = bundleGraph.createBundle({
13
+ entryAsset,
14
+ target,
15
+ needsStableName: (0, feature_flags_1.getFeatureFlag)('singleFileOutputStableName'),
16
+ });
17
+ bundleGraph.traverse((node, _, actions) => {
18
+ // JS assets can be added to the bundle, but the rest are ignored
19
+ if (node.type === 'asset' && node.value.type === 'js') {
20
+ bundleGraph.addAssetToBundle(node.value, bundle);
21
+ return;
22
+ }
23
+ if (node.type !== 'dependency') {
24
+ return;
25
+ }
26
+ let dependency = node.value;
27
+ if (dependency.priority === 'lazy') {
28
+ // Any async dependencies need to be internalized into the bundle, and will
29
+ // be included by the asset check above
30
+ bundleGraph.internalizeAsyncDependency(bundle, dependency);
31
+ return;
32
+ }
33
+ let assets = bundleGraph.getDependencyAssets(dependency);
34
+ for (const asset of assets) {
35
+ if (asset.bundleBehavior === 'isolated' ||
36
+ asset.bundleBehavior === 'inlineIsolated') {
37
+ throw new Error(`${asset.bundleBehavior === 'isolated' ? 'Isolated' : 'Inline isolated'} assets are not supported for single file output builds`);
38
+ }
39
+ // For assets marked as inline, we create new bundles and let other
40
+ // plugins like optimizers include them in the primary bundle
41
+ if (asset.bundleBehavior === 'inline') {
42
+ // Create a new bundle to hold the isolated asset
43
+ let isolatedBundle = bundleGraph.createBundle({
44
+ entryAsset: asset,
45
+ target,
46
+ bundleBehavior: asset.bundleBehavior,
47
+ });
48
+ bundleGraph.addAssetToBundle(asset, isolatedBundle);
49
+ // Add the new bundle to the bundle graph, in its own bundle group
50
+ bundleGraph.createBundleReference(bundle, isolatedBundle);
51
+ bundleGraph.addBundleToBundleGroup(isolatedBundle, bundleGraph.createBundleGroup(dependency, target));
52
+ // Nothing below the isolated asset needs to go in the main bundle, so
53
+ // we can stop traversal here
54
+ actions.skipChildren();
55
+ // To be properly isolated, all of this asset's dependencies need to go
56
+ // in this new bundle
57
+ bundleGraph.traverse((subNode) => {
58
+ if (subNode.type === 'asset' && subNode.value.type === 'js') {
59
+ bundleGraph.addAssetToBundle(subNode.value, isolatedBundle);
60
+ return;
61
+ }
62
+ }, asset, { skipUnusedDependencies: true });
63
+ }
64
+ }
65
+ }, entryAsset, { skipUnusedDependencies: true });
66
+ let bundleGroup = bundleGraph.createBundleGroup(entryDep, target);
67
+ bundleGraph.addBundleToBundleGroup(bundle, bundleGroup);
68
+ }
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.findMergeCandidates = findMergeCandidates;
7
+ const assert_1 = __importDefault(require("assert"));
8
+ const nullthrows_1 = __importDefault(require("nullthrows"));
9
+ const graph_1 = require("@atlaspack/graph");
10
+ const utils_1 = require("@atlaspack/utils");
11
+ const memoize_1 = require("./memoize");
12
+ function getBundlesForBundleGroup(bundleGraph, bundleGroupId) {
13
+ let count = 0;
14
+ bundleGraph.traverse((nodeId) => {
15
+ const node = bundleGraph.getNode(nodeId);
16
+ if (node &&
17
+ (node === 'root' ||
18
+ (node.bundleBehavior !== 'inline' &&
19
+ node.bundleBehavior !== 'inlineIsolated'))) {
20
+ count++;
21
+ }
22
+ }, bundleGroupId);
23
+ return count;
24
+ }
25
+ let getBundleOverlap = (sourceBundlesA, sourceBundlesB) => {
26
+ let allSourceBundles = (0, utils_1.setUnion)(sourceBundlesA, sourceBundlesB);
27
+ let sharedSourceBundles = (0, utils_1.setIntersectStatic)(sourceBundlesA, sourceBundlesB);
28
+ return sharedSourceBundles.size / allSourceBundles.size;
29
+ };
30
+ // Returns a decimal showing the proportion source bundles are common to
31
+ // both bundles versus the total number of source bundles.
32
+ function checkBundleThreshold(bundleA, bundleB, threshold) {
33
+ return (getBundleOverlap(bundleA.bundle.sourceBundles, bundleB.bundle.sourceBundles) >= threshold);
34
+ }
35
+ let checkSharedSourceBundles = (0, memoize_1.memoize)((bundle, importantAncestorBundles) => {
36
+ return importantAncestorBundles.every((ancestorId) => bundle.sourceBundles.has(ancestorId));
37
+ });
38
+ let hasSuitableBundleGroup = (0, memoize_1.memoize)((bundleGraph, bundle, minBundlesInGroup) => {
39
+ for (let sourceBundle of bundle.sourceBundles) {
40
+ let bundlesInGroup = getBundlesForBundleGroup(bundleGraph, sourceBundle);
41
+ if (bundlesInGroup >= minBundlesInGroup) {
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ });
47
+ function validMerge(bundleGraph, config, bundleA, bundleB) {
48
+ if (config.maxBundleSize != null) {
49
+ if (bundleA.bundle.size > config.maxBundleSize ||
50
+ bundleB.bundle.size > config.maxBundleSize) {
51
+ return false;
52
+ }
53
+ }
54
+ if (config.overlapThreshold != null) {
55
+ if (!checkBundleThreshold(bundleA, bundleB, config.overlapThreshold)) {
56
+ return false;
57
+ }
58
+ }
59
+ if (config.sourceBundles != null) {
60
+ if (!checkSharedSourceBundles(bundleA.bundle, config.sourceBundles) ||
61
+ !checkSharedSourceBundles(bundleB.bundle, config.sourceBundles)) {
62
+ return false;
63
+ }
64
+ }
65
+ if (config.minBundlesInGroup != null) {
66
+ if (!hasSuitableBundleGroup(bundleGraph, bundleA.bundle, config.minBundlesInGroup) ||
67
+ !hasSuitableBundleGroup(bundleGraph, bundleB.bundle, config.minBundlesInGroup)) {
68
+ return false;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+ function getMergeClusters(graph, candidates) {
74
+ let clusters = [];
75
+ for (let [candidate, edgeType] of candidates.entries()) {
76
+ let cluster = [];
77
+ graph.traverse((nodeId) => {
78
+ cluster.push((0, nullthrows_1.default)(graph.getNode(nodeId)));
79
+ // Remove node from candidates as it has already been processed
80
+ candidates.delete(nodeId);
81
+ }, candidate, edgeType);
82
+ clusters.push(cluster);
83
+ }
84
+ return clusters;
85
+ }
86
+ function getPossibleMergeCandidates(bundleGraph, bundles) {
87
+ let mergeCandidates = bundles.map((bundleId) => {
88
+ let bundle = bundleGraph.getNode(bundleId);
89
+ (0, assert_1.default)(bundle && bundle !== 'root', 'Bundle should exist');
90
+ return {
91
+ id: bundleId,
92
+ bundle,
93
+ contentKey: bundleId.toString(),
94
+ };
95
+ });
96
+ const uniquePairs = [];
97
+ for (let i = 0; i < mergeCandidates.length; i++) {
98
+ for (let j = i + 1; j < mergeCandidates.length; j++) {
99
+ let a = mergeCandidates[i];
100
+ let b = mergeCandidates[j];
101
+ // @ts-expect-error TS18048
102
+ if (a.bundle.internalizedAssets.equals(b.bundle.internalizedAssets)) {
103
+ uniquePairs.push([a, b]);
104
+ }
105
+ }
106
+ }
107
+ return uniquePairs;
108
+ }
109
+ function findMergeCandidates(bundleGraph, bundles, config) {
110
+ let graph = new graph_1.ContentGraph();
111
+ let candidates = new Map();
112
+ let allPossibleMergeCandidates = getPossibleMergeCandidates(bundleGraph, bundles);
113
+ // Build graph of clustered merge candidates
114
+ for (let i = 0; i < config.length; i++) {
115
+ // Ensure edge type coresponds to config index
116
+ let edgeType = i + 1;
117
+ for (let group of allPossibleMergeCandidates) {
118
+ let candidateA = group[0];
119
+ let candidateB = group[1];
120
+ if (!validMerge(bundleGraph, config[i], candidateA, candidateB)) {
121
+ continue;
122
+ }
123
+ let bundleNode = graph.addNodeByContentKeyIfNeeded(candidateA.contentKey, candidateA.id);
124
+ let otherBundleNode = graph.addNodeByContentKeyIfNeeded(candidateB.contentKey, candidateB.id);
125
+ // Add edge in both directions
126
+ graph.addEdge(bundleNode, otherBundleNode, edgeType);
127
+ graph.addEdge(otherBundleNode, bundleNode, edgeType);
128
+ candidates.set(bundleNode, edgeType);
129
+ candidates.set(otherBundleNode, edgeType);
130
+ }
131
+ // Remove bundles that have been allocated to a higher priority merge
132
+ allPossibleMergeCandidates = allPossibleMergeCandidates.filter((group) => !graph.hasContentKey(group[0].contentKey) &&
133
+ !graph.hasContentKey(group[1].contentKey));
134
+ }
135
+ (0, memoize_1.clearCaches)();
136
+ return getMergeClusters(graph, candidates);
137
+ }
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadBundlerConfig = loadBundlerConfig;
7
+ const diagnostic_1 = require("@atlaspack/diagnostic");
8
+ const feature_flags_1 = require("@atlaspack/feature-flags");
9
+ const utils_1 = require("@atlaspack/utils");
10
+ const assert_1 = __importDefault(require("assert"));
11
+ function resolveModeConfig(config, mode) {
12
+ let generalConfig = {};
13
+ let modeConfig = {};
14
+ for (const key of Object.keys(config)) {
15
+ if (key === 'development' || key === 'production') {
16
+ if (key === mode) {
17
+ // @ts-expect-error TS2322
18
+ modeConfig = config[key];
19
+ }
20
+ }
21
+ else {
22
+ generalConfig[key] = config[key];
23
+ }
24
+ }
25
+ return {
26
+ ...generalConfig,
27
+ ...modeConfig,
28
+ };
29
+ }
30
+ // Default options by http version.
31
+ const HTTP_OPTIONS = {
32
+ '1': {
33
+ minBundles: 1,
34
+ manualSharedBundles: [],
35
+ minBundleSize: 30000,
36
+ maxParallelRequests: 6,
37
+ disableSharedBundles: false,
38
+ sharedBundleMerge: [],
39
+ },
40
+ '2': {
41
+ minBundles: 1,
42
+ manualSharedBundles: [],
43
+ minBundleSize: 20000,
44
+ maxParallelRequests: 25,
45
+ disableSharedBundles: false,
46
+ sharedBundleMerge: [],
47
+ },
48
+ };
49
+ const CONFIG_SCHEMA = {
50
+ type: 'object',
51
+ properties: {
52
+ http: {
53
+ type: 'number',
54
+ enum: Object.keys(HTTP_OPTIONS).map((k) => Number(k)),
55
+ },
56
+ manualSharedBundles: {
57
+ type: 'array',
58
+ items: {
59
+ type: 'object',
60
+ properties: {
61
+ name: {
62
+ type: 'string',
63
+ },
64
+ assets: {
65
+ type: 'array',
66
+ items: {
67
+ type: 'string',
68
+ },
69
+ },
70
+ types: {
71
+ type: 'array',
72
+ items: {
73
+ type: 'string',
74
+ },
75
+ },
76
+ root: {
77
+ type: 'string',
78
+ },
79
+ split: {
80
+ type: 'number',
81
+ },
82
+ },
83
+ required: ['name', 'assets'],
84
+ additionalProperties: false,
85
+ },
86
+ },
87
+ sharedBundleMerge: {
88
+ type: 'array',
89
+ items: {
90
+ type: 'object',
91
+ properties: {
92
+ overlapThreshold: {
93
+ type: 'number',
94
+ },
95
+ maxBundleSize: {
96
+ type: 'number',
97
+ },
98
+ sourceBundles: {
99
+ type: 'array',
100
+ items: {
101
+ type: 'string',
102
+ },
103
+ },
104
+ minBundlesInGroup: {
105
+ type: 'number',
106
+ },
107
+ },
108
+ additionalProperties: false,
109
+ },
110
+ },
111
+ asyncBundleMerge: {
112
+ type: 'object',
113
+ properties: {
114
+ bundleSize: {
115
+ type: 'number',
116
+ required: true,
117
+ },
118
+ maxOverfetchSize: {
119
+ type: 'number',
120
+ required: true,
121
+ },
122
+ ignore: {
123
+ type: 'array',
124
+ items: {
125
+ type: 'string',
126
+ },
127
+ },
128
+ },
129
+ additionalProperties: false,
130
+ },
131
+ minBundles: {
132
+ type: 'number',
133
+ },
134
+ minBundleSize: {
135
+ type: 'number',
136
+ },
137
+ maxParallelRequests: {
138
+ type: 'number',
139
+ },
140
+ disableSharedBundles: {
141
+ type: 'boolean',
142
+ },
143
+ loadConditionalBundlesInParallel: {
144
+ type: 'boolean',
145
+ },
146
+ sharedBundleMergeThreshold: {
147
+ type: 'number',
148
+ },
149
+ },
150
+ additionalProperties: false,
151
+ };
152
+ async function loadBundlerConfig(config, options, logger) {
153
+ let conf;
154
+ if ((0, feature_flags_1.getFeatureFlag)('resolveBundlerConfigFromCwd')) {
155
+ conf = await config.getConfigFrom(`${process.cwd()}/index`, [], {
156
+ packageKey: '@atlaspack/bundler-default',
157
+ });
158
+ }
159
+ else {
160
+ conf = await config.getConfig([], {
161
+ packageKey: '@atlaspack/bundler-default',
162
+ });
163
+ }
164
+ if (!conf) {
165
+ const modDefault = {
166
+ ...HTTP_OPTIONS['2'],
167
+ projectRoot: options.projectRoot,
168
+ };
169
+ // @ts-expect-error TS2322
170
+ return modDefault;
171
+ }
172
+ (0, assert_1.default)(conf?.contents != null);
173
+ let modeConfig = resolveModeConfig(conf.contents, options.mode);
174
+ // minBundles will be ignored if shared bundles are disabled
175
+ if (modeConfig.minBundles != null &&
176
+ modeConfig.disableSharedBundles === true) {
177
+ logger.warn({
178
+ origin: '@atlaspack/bundler-default',
179
+ message: `The value of "${modeConfig.minBundles}" set for minBundles will not be used as shared bundles have been disabled`,
180
+ });
181
+ }
182
+ // minBundleSize will be ignored if shared bundles are disabled
183
+ if (modeConfig.minBundleSize != null &&
184
+ modeConfig.disableSharedBundles === true) {
185
+ logger.warn({
186
+ origin: '@atlaspack/bundler-default',
187
+ message: `The value of "${modeConfig.minBundleSize}" set for minBundleSize will not be used as shared bundles have been disabled`,
188
+ });
189
+ }
190
+ // maxParallelRequests will be ignored if shared bundles are disabled
191
+ if (modeConfig.maxParallelRequests != null &&
192
+ modeConfig.disableSharedBundles === true) {
193
+ logger.warn({
194
+ origin: '@atlaspack/bundler-default',
195
+ message: `The value of "${modeConfig.maxParallelRequests}" set for maxParallelRequests will not be used as shared bundles have been disabled`,
196
+ });
197
+ }
198
+ if (modeConfig.manualSharedBundles) {
199
+ let nameArray = modeConfig.manualSharedBundles.map((a) => a.name);
200
+ let nameSet = new Set(nameArray);
201
+ (0, assert_1.default)(nameSet.size == nameArray.length, 'The name field must be unique for property manualSharedBundles');
202
+ }
203
+ utils_1.validateSchema.diagnostic(CONFIG_SCHEMA, {
204
+ data: modeConfig,
205
+ source: () => options.inputFS.readFileSync(conf.filePath, 'utf8'),
206
+ filePath: conf.filePath,
207
+ prependKey: `/${(0, diagnostic_1.encodeJSONKeyComponent)('@atlaspack/bundler-default')}`,
208
+ }, '@atlaspack/bundler-default', 'Invalid config for @atlaspack/bundler-default');
209
+ let http = modeConfig.http ?? 2;
210
+ // @ts-expect-error TS7053
211
+ let defaults = HTTP_OPTIONS[http];
212
+ return {
213
+ minBundles: modeConfig.minBundles ?? defaults.minBundles,
214
+ minBundleSize: modeConfig.minBundleSize ?? defaults.minBundleSize,
215
+ sharedBundleMerge: modeConfig.sharedBundleMerge ?? defaults.sharedBundleMerge,
216
+ asyncBundleMerge: modeConfig.asyncBundleMerge,
217
+ maxParallelRequests: modeConfig.maxParallelRequests ?? defaults.maxParallelRequests,
218
+ projectRoot: options.projectRoot,
219
+ disableSharedBundles: modeConfig.disableSharedBundles ?? defaults.disableSharedBundles,
220
+ manualSharedBundles: modeConfig.manualSharedBundles ?? defaults.manualSharedBundles,
221
+ loadConditionalBundlesInParallel: modeConfig.loadConditionalBundlesInParallel,
222
+ };
223
+ }