Skip to content

Commit 91c961e

Browse files
james-odJames ODonnell
andauthored
[ST-5243] Make splitChunks configurable (#167)
* [ST-5243] Make splitChunks configurable * Add tests --------- Co-authored-by: James ODonnell <[email protected]>
1 parent ccba8c3 commit 91c961e

File tree

5 files changed

+322
-16
lines changed

5 files changed

+322
-16
lines changed

packages/react-scripts/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ npm start
3030
- `babelIncludePrefixes`: An array of module name prefixes to opt into babel compilation, including local import module, e.g. `"../common"`. Includes `["@skyscanner/bpk-", "bpk-", "saddlebag-"]` by default.
3131
- `enableAutomaticChunking`: Boolean, opt in to automatic chunking of vendor, common and app code.
3232
- `vendorsChunkRegex`: String, Regex for picking what goes into the `vendors` chunk. See `cacheGroups` in webpack docs. Dependent on `enableAutomaticChunking` being enabled
33+
- `splitChunksConfig`: Object, mapping to the [structure in the webpack docs](https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks). Applied only if `enableAutomaticChunking` is false, ignores `vendorsChunkRegex` if defined.
3334
- `amdExcludes`: Array of module names to exclude from AMD parsing. Incldues `["lodash"]` by default.
3435
- `externals`: exposing the Webpack config to modify externals, see [docs](https://webpack.js.org/configuration/externals/).
3536
- `ssrExternals`: Similar to above, but for `ssr.js` only.

packages/react-scripts/backpack-addons/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Our react scripts fork includes a number of custom configuration items in order
1414
| **ssrExternals** | The same as above `externals` except used for server side rendering only in **ssr.js** | **{}** |
1515
| **enableAutomaticChunking** | Opts into automatic chunking of vender, common and app code.<br> When enabled the **splitChunks** plugin creates vender and common chunks which are split and when provided uses the `venderChunkRegex` to specify what is in each chunk.<br> When enabled **runtimeChunk** plugin creates a separate runtime chunk for projects to enable long term caching. | **false** |
1616
| **vendorsChunkRegex** | Regex for picking what goes into the vendors chunk. Requires enableAutomaticChunking to be enabled.<br> See [cacheGroups](https://webpack.js.org/plugins/split-chunks-plugin/#splitchunkscachegroups) docs for further details. | |
17+
| **splitChunksConfig** | Object, mapping to the [structure in the webpack docs](https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks).<br> Applied only if `enableAutomaticChunking` is false, ignores `vendorsChunkRegex` if defined. | |
1718
| **sassFunctions** | This function encodes svg content into `base64` when there is a `bpk-icon` in the.scss file. | |
1819

1920
## How to add new feature
Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,93 @@
1+
/**
2+
* Defines a webpack splitChunks configuration, optionally based on consumer configuration.
3+
*
4+
* For automatic configuration set enableAutomaticChunking and optionally provide a vendorsChunkRegex string, e.g:
5+
*
6+
* // package.json
7+
* ...
8+
* "backpack-react-scripts": {
9+
* ...
10+
* "enableAutomaticChunking": true,
11+
* "vendorsChunkRegex": "...",
12+
* ...
13+
* }
14+
* ...
15+
*
16+
* For custom configuration disable enableAutomaticChunking and provide a configuration object, e.g:
17+
*
18+
* // package.json
19+
* ...
20+
* "backpack-react-scripts": {
21+
* ...
22+
* "enableAutomaticChunking": false,
23+
* "splitChunksConfig": {
24+
* "chunks": "all",
25+
* ...
26+
* "cacheGroups": {
27+
* "vendors": {
28+
* "test": "..."
29+
* },
30+
* "customChunk": {
31+
* "test": "..."
32+
* "priority": 100,
33+
* "chunks": "all",
34+
* "name": "customChunk",
35+
* },
36+
* },
37+
* ...
38+
* }
39+
* ...
40+
*
41+
* References:
42+
* https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks
43+
* https://webpack.js.org/plugins/split-chunks-plugin/#splitchunkscachegroups
44+
* https://twitter.com/wSokra/status/969633336732905474
45+
* https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
46+
*/
47+
148
'use strict';
249

350
const paths = require('../config/paths');
451
const appPackageJson = require(paths.appPackageJson);
552
const bpkReactScriptsConfig = appPackageJson['backpack-react-scripts'] || {};
653

7-
module.exports = (isEnvDevelopment) => {
8-
// Automatically split vendor and commons
9-
// https://twitter.com/wSokra/status/969633336732905474
10-
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
11-
return {
12-
splitChunks: bpkReactScriptsConfig.enableAutomaticChunking
13-
? {
54+
module.exports = isEnvDevelopment => {
55+
let splitChunksConfig = {};
56+
57+
// If opted in to automatic chunking, apply default configuration
58+
if (bpkReactScriptsConfig.enableAutomaticChunking) {
59+
splitChunksConfig = {
1460
chunks: 'all',
1561
name: isEnvDevelopment,
16-
cacheGroups: bpkReactScriptsConfig.vendorsChunkRegex
17-
? {
18-
vendors: {
19-
test: new RegExp(bpkReactScriptsConfig.vendorsChunkRegex)
20-
},
21-
}
22-
: {},
62+
cacheGroups: {},
63+
};
64+
// Apply vendorsChunkRegex if provided
65+
if (bpkReactScriptsConfig.vendorsChunkRegex) {
66+
splitChunksConfig.cacheGroups = {
67+
vendors: {
68+
// Regexes are passed as strings in package.json config, but need constructed here.
69+
test: new RegExp(bpkReactScriptsConfig.vendorsChunkRegex),
70+
},
71+
};
72+
}
73+
}
74+
// If not opted in to automatic chunking, use custom configuration - if defined.
75+
else if (bpkReactScriptsConfig.splitChunksConfig) {
76+
splitChunksConfig = {
77+
...bpkReactScriptsConfig.splitChunksConfig,
78+
name: isEnvDevelopment,
79+
};
80+
if (splitChunksConfig.cacheGroups) {
81+
// Regexes are passed as strings in package.json config, but need constructed here.
82+
for (let cacheGroup of Object.keys(splitChunksConfig.cacheGroups)) {
83+
splitChunksConfig.cacheGroups[cacheGroup].test = new RegExp(
84+
splitChunksConfig.cacheGroups[cacheGroup].test
85+
);
2386
}
24-
: {}
87+
}
2588
}
26-
};
89+
90+
return {
91+
splitChunks: splitChunksConfig,
92+
};
93+
};
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
'use strict';
2+
3+
jest.mock('../config/paths', () => ({
4+
appPackageJson: './test/mockPackage.json',
5+
}));
6+
7+
describe('splitChunks', () => {
8+
const mockData = {
9+
name: 'test',
10+
version: '1.0.0',
11+
'backpack-react-scripts': {},
12+
};
13+
14+
let isEnvDevelopment = true;
15+
16+
beforeEach(() => {
17+
jest.resetModules();
18+
});
19+
20+
test('should return default if no config defined', () => {
21+
jest.doMock('./test/mockPackage.json', () => ({
22+
...mockData,
23+
'backpack-react-scripts': {},
24+
}));
25+
const splitChunks = require('../backpack-addons/splitChunks');
26+
27+
let res = splitChunks(isEnvDevelopment);
28+
29+
expect(res).toEqual({ splitChunks: {} });
30+
});
31+
32+
test('should apply basic defaults if automatic chunking enabled without vendors regex', () => {
33+
jest.doMock('./test/mockPackage.json', () => ({
34+
...mockData,
35+
'backpack-react-scripts': {
36+
enableAutomaticChunking: true,
37+
},
38+
}));
39+
const splitChunks = require('../backpack-addons/splitChunks');
40+
41+
let res = splitChunks(isEnvDevelopment);
42+
43+
expect(res).toEqual({
44+
splitChunks: { chunks: 'all', name: true, cacheGroups: {} },
45+
});
46+
});
47+
48+
test('should return empty if automatic chunking false and no other config is defined', () => {
49+
jest.doMock('./test/mockPackage.json', () => ({
50+
...mockData,
51+
'backpack-react-scripts': {
52+
enableAutomaticChunking: false,
53+
},
54+
}));
55+
const splitChunks = require('../backpack-addons/splitChunks');
56+
57+
let res = splitChunks(isEnvDevelopment);
58+
59+
expect(res).toEqual({ splitChunks: {} });
60+
});
61+
62+
test('should apply basic defaults and cacheGroup with vendors RegExp when automatic chunking enabled and vendors regex provided', () => {
63+
jest.doMock('./test/mockPackage.json', () => ({
64+
...mockData,
65+
'backpack-react-scripts': {
66+
enableAutomaticChunking: true,
67+
vendorsChunkRegex: '[\\/]node_modules[\\/]',
68+
},
69+
}));
70+
const splitChunks = require('../backpack-addons/splitChunks');
71+
72+
let res = splitChunks(isEnvDevelopment);
73+
74+
expect(res).toEqual({
75+
splitChunks: {
76+
chunks: 'all',
77+
name: true,
78+
cacheGroups: { vendors: { test: expect.any(RegExp) } },
79+
},
80+
});
81+
});
82+
83+
test('should return empty when automatic chunking disabled and vendors regex provided', () => {
84+
jest.doMock('./test/mockPackage.json', () => ({
85+
...mockData,
86+
'backpack-react-scripts': {
87+
enableAutomaticChunking: false,
88+
vendorsChunkRegex: '[\\/]node_modules[\\/]',
89+
},
90+
}));
91+
const splitChunks = require('../backpack-addons/splitChunks');
92+
93+
let res = splitChunks(isEnvDevelopment);
94+
95+
expect(res).toEqual({ splitChunks: {} });
96+
});
97+
98+
test('should ignore custom config when automatic chunking enabled and splitChunksConfig is also defined', () => {
99+
jest.doMock('./test/mockPackage.json', () => ({
100+
...mockData,
101+
'backpack-react-scripts': {
102+
enableAutomaticChunking: true,
103+
splitChunksConfig: {
104+
cacheGroups: {
105+
vendors: {
106+
test: '[\\/]node_modules[\\/]',
107+
},
108+
someCustomChunk: {
109+
test: '[\\/]some_regex[\\/]',
110+
priority: 100,
111+
chunks: 'all',
112+
name: 'someCustomChunk',
113+
},
114+
},
115+
},
116+
},
117+
}));
118+
const splitChunks = require('../backpack-addons/splitChunks');
119+
120+
let res = splitChunks(isEnvDevelopment);
121+
122+
expect(res).toEqual({
123+
splitChunks: { chunks: 'all', name: true, cacheGroups: {} },
124+
});
125+
});
126+
127+
test('should not ignore custom config when automatic chunking disabled and splitChunksConfig is defined', () => {
128+
jest.doMock('./test/mockPackage.json', () => ({
129+
...mockData,
130+
'backpack-react-scripts': {
131+
enableAutomaticChunking: false,
132+
splitChunksConfig: {
133+
chunks: 'all',
134+
cacheGroups: {
135+
vendors: {
136+
test: '[\\/]node_modules[\\/]',
137+
},
138+
},
139+
},
140+
},
141+
}));
142+
const splitChunks = require('../backpack-addons/splitChunks');
143+
144+
let res = splitChunks(isEnvDevelopment);
145+
146+
expect(res).toEqual({
147+
splitChunks: {
148+
chunks: 'all',
149+
name: true,
150+
cacheGroups: {
151+
vendors: {
152+
test: expect.any(RegExp),
153+
},
154+
},
155+
},
156+
});
157+
});
158+
159+
test('should apply only the name field when splitChunks is empty', () => {
160+
jest.doMock('./test/mockPackage.json', () => ({
161+
...mockData,
162+
'backpack-react-scripts': {
163+
enableAutomaticChunking: false,
164+
splitChunksConfig: {},
165+
},
166+
}));
167+
const splitChunks = require('../backpack-addons/splitChunks');
168+
169+
let res = splitChunks(isEnvDevelopment);
170+
171+
expect(res).toEqual({ splitChunks: { name: true } });
172+
});
173+
174+
test('should apply Regexes when multiple cacheGroups are applied', () => {
175+
jest.doMock('./test/mockPackage.json', () => ({
176+
...mockData,
177+
'backpack-react-scripts': {
178+
enableAutomaticChunking: false,
179+
splitChunksConfig: {
180+
chunks: 'all',
181+
cacheGroups: {
182+
vendors: {
183+
test: '[\\/]node_modules[\\/]',
184+
},
185+
someCustomChunk: {
186+
test: '[\\/]some_regex[\\/]',
187+
priority: 100,
188+
chunks: 'all',
189+
name: 'someCustomChunk',
190+
},
191+
},
192+
},
193+
},
194+
}));
195+
const splitChunks = require('../backpack-addons/splitChunks');
196+
197+
let res = splitChunks(isEnvDevelopment);
198+
199+
expect(res).toEqual({
200+
splitChunks: {
201+
chunks: 'all',
202+
name: true,
203+
cacheGroups: {
204+
vendors: {
205+
test: expect.any(RegExp),
206+
},
207+
someCustomChunk: {
208+
test: expect.any(RegExp),
209+
priority: 100,
210+
chunks: 'all',
211+
name: 'someCustomChunk',
212+
},
213+
},
214+
},
215+
});
216+
});
217+
218+
test('should apply isEnvDevelopment boolean as name value', () => {
219+
let isEnvDevelopment = false;
220+
jest.doMock('./test/mockPackage.json', () => ({
221+
...mockData,
222+
'backpack-react-scripts': {
223+
enableAutomaticChunking: true,
224+
},
225+
}));
226+
const splitChunks = require('../backpack-addons/splitChunks');
227+
228+
let res = splitChunks(isEnvDevelopment);
229+
230+
expect(res).toEqual({
231+
splitChunks: { chunks: 'all', name: false, cacheGroups: {} },
232+
});
233+
});
234+
});

packages/react-scripts/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
"utils",
2222
"backpack-addons"
2323
],
24+
"scripts": {
25+
"test:addons": "jest --testPathPattern=backpack-addons"
26+
},
2427
"bin": {
2528
"react-scripts": "./bin/react-scripts.js"
2629
},

0 commit comments

Comments
 (0)