Skip to content

Commit

Permalink
[ST-5243] Make splitChunks configurable (#167)
Browse files Browse the repository at this point in the history
* [ST-5243] Make splitChunks configurable

* Add tests

---------

Co-authored-by: James ODonnell <james.odonnell@skyscanner.net>
  • Loading branch information
james-od and James ODonnell authored Oct 12, 2023
1 parent ccba8c3 commit 91c961e
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/react-scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ npm start
- `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.
- `enableAutomaticChunking`: Boolean, opt in to automatic chunking of vendor, common and app code.
- `vendorsChunkRegex`: String, Regex for picking what goes into the `vendors` chunk. See `cacheGroups` in webpack docs. Dependent on `enableAutomaticChunking` being enabled
- `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.
- `amdExcludes`: Array of module names to exclude from AMD parsing. Incldues `["lodash"]` by default.
- `externals`: exposing the Webpack config to modify externals, see [docs](https://webpack.js.org/configuration/externals/).
- `ssrExternals`: Similar to above, but for `ssr.js` only.
Expand Down
1 change: 1 addition & 0 deletions packages/react-scripts/backpack-addons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Our react scripts fork includes a number of custom configuration items in order
| **ssrExternals** | The same as above `externals` except used for server side rendering only in **ssr.js** | **{}** |
| **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** |
| **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. | |
| **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. | |
| **sassFunctions** | This function encodes svg content into `base64` when there is a `bpk-icon` in the.scss file. | |

## How to add new feature
Expand Down
99 changes: 83 additions & 16 deletions packages/react-scripts/backpack-addons/splitChunks.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,93 @@
/**
* Defines a webpack splitChunks configuration, optionally based on consumer configuration.
*
* For automatic configuration set enableAutomaticChunking and optionally provide a vendorsChunkRegex string, e.g:
*
* // package.json
* ...
* "backpack-react-scripts": {
* ...
* "enableAutomaticChunking": true,
* "vendorsChunkRegex": "...",
* ...
* }
* ...
*
* For custom configuration disable enableAutomaticChunking and provide a configuration object, e.g:
*
* // package.json
* ...
* "backpack-react-scripts": {
* ...
* "enableAutomaticChunking": false,
* "splitChunksConfig": {
* "chunks": "all",
* ...
* "cacheGroups": {
* "vendors": {
* "test": "..."
* },
* "customChunk": {
* "test": "..."
* "priority": 100,
* "chunks": "all",
* "name": "customChunk",
* },
* },
* ...
* }
* ...
*
* References:
* https://webpack.js.org/plugins/split-chunks-plugin/#optimizationsplitchunks
* https://webpack.js.org/plugins/split-chunks-plugin/#splitchunkscachegroups
* https://twitter.com/wSokra/status/969633336732905474
* https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
*/

'use strict';

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

module.exports = (isEnvDevelopment) => {
// Automatically split vendor and commons
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
return {
splitChunks: bpkReactScriptsConfig.enableAutomaticChunking
? {
module.exports = isEnvDevelopment => {
let splitChunksConfig = {};

// If opted in to automatic chunking, apply default configuration
if (bpkReactScriptsConfig.enableAutomaticChunking) {
splitChunksConfig = {
chunks: 'all',
name: isEnvDevelopment,
cacheGroups: bpkReactScriptsConfig.vendorsChunkRegex
? {
vendors: {
test: new RegExp(bpkReactScriptsConfig.vendorsChunkRegex)
},
}
: {},
cacheGroups: {},
};
// Apply vendorsChunkRegex if provided
if (bpkReactScriptsConfig.vendorsChunkRegex) {
splitChunksConfig.cacheGroups = {
vendors: {
// Regexes are passed as strings in package.json config, but need constructed here.
test: new RegExp(bpkReactScriptsConfig.vendorsChunkRegex),
},
};
}
}
// If not opted in to automatic chunking, use custom configuration - if defined.
else if (bpkReactScriptsConfig.splitChunksConfig) {
splitChunksConfig = {
...bpkReactScriptsConfig.splitChunksConfig,
name: isEnvDevelopment,
};
if (splitChunksConfig.cacheGroups) {
// Regexes are passed as strings in package.json config, but need constructed here.
for (let cacheGroup of Object.keys(splitChunksConfig.cacheGroups)) {
splitChunksConfig.cacheGroups[cacheGroup].test = new RegExp(
splitChunksConfig.cacheGroups[cacheGroup].test
);
}
: {}
}
}
};

return {
splitChunks: splitChunksConfig,
};
};
234 changes: 234 additions & 0 deletions packages/react-scripts/backpack-addons/splitChunks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
'use strict';

jest.mock('../config/paths', () => ({
appPackageJson: './test/mockPackage.json',
}));

describe('splitChunks', () => {
const mockData = {
name: 'test',
version: '1.0.0',
'backpack-react-scripts': {},
};

let isEnvDevelopment = true;

beforeEach(() => {
jest.resetModules();
});

test('should return default if no config defined', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({ splitChunks: {} });
});

test('should apply basic defaults if automatic chunking enabled without vendors regex', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: true,
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({
splitChunks: { chunks: 'all', name: true, cacheGroups: {} },
});
});

test('should return empty if automatic chunking false and no other config is defined', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: false,
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({ splitChunks: {} });
});

test('should apply basic defaults and cacheGroup with vendors RegExp when automatic chunking enabled and vendors regex provided', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: true,
vendorsChunkRegex: '[\\/]node_modules[\\/]',
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({
splitChunks: {
chunks: 'all',
name: true,
cacheGroups: { vendors: { test: expect.any(RegExp) } },
},
});
});

test('should return empty when automatic chunking disabled and vendors regex provided', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: false,
vendorsChunkRegex: '[\\/]node_modules[\\/]',
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({ splitChunks: {} });
});

test('should ignore custom config when automatic chunking enabled and splitChunksConfig is also defined', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: true,
splitChunksConfig: {
cacheGroups: {
vendors: {
test: '[\\/]node_modules[\\/]',
},
someCustomChunk: {
test: '[\\/]some_regex[\\/]',
priority: 100,
chunks: 'all',
name: 'someCustomChunk',
},
},
},
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({
splitChunks: { chunks: 'all', name: true, cacheGroups: {} },
});
});

test('should not ignore custom config when automatic chunking disabled and splitChunksConfig is defined', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: false,
splitChunksConfig: {
chunks: 'all',
cacheGroups: {
vendors: {
test: '[\\/]node_modules[\\/]',
},
},
},
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({
splitChunks: {
chunks: 'all',
name: true,
cacheGroups: {
vendors: {
test: expect.any(RegExp),
},
},
},
});
});

test('should apply only the name field when splitChunks is empty', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: false,
splitChunksConfig: {},
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({ splitChunks: { name: true } });
});

test('should apply Regexes when multiple cacheGroups are applied', () => {
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: false,
splitChunksConfig: {
chunks: 'all',
cacheGroups: {
vendors: {
test: '[\\/]node_modules[\\/]',
},
someCustomChunk: {
test: '[\\/]some_regex[\\/]',
priority: 100,
chunks: 'all',
name: 'someCustomChunk',
},
},
},
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({
splitChunks: {
chunks: 'all',
name: true,
cacheGroups: {
vendors: {
test: expect.any(RegExp),
},
someCustomChunk: {
test: expect.any(RegExp),
priority: 100,
chunks: 'all',
name: 'someCustomChunk',
},
},
},
});
});

test('should apply isEnvDevelopment boolean as name value', () => {
let isEnvDevelopment = false;
jest.doMock('./test/mockPackage.json', () => ({
...mockData,
'backpack-react-scripts': {
enableAutomaticChunking: true,
},
}));
const splitChunks = require('../backpack-addons/splitChunks');

let res = splitChunks(isEnvDevelopment);

expect(res).toEqual({
splitChunks: { chunks: 'all', name: false, cacheGroups: {} },
});
});
});
3 changes: 3 additions & 0 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"utils",
"backpack-addons"
],
"scripts": {
"test:addons": "jest --testPathPattern=backpack-addons"
},
"bin": {
"react-scripts": "./bin/react-scripts.js"
},
Expand Down

0 comments on commit 91c961e

Please sign in to comment.