I have a Gatsby site that consumes a number of packages. One of those packages is published from our monorepo: @example/forms
. That package contains a number of named exports, one for each form component that we use on our site. There are quite a large number of forms and some are relatively complex multistep forms of a non-trivial size.
Currently, Gatsby/Webpack does a good job of treeshaking, and does produce a large number of bundles, some common bundles and one for each page of the site, containing any local assets or components that are only used on that page. However, all the components from @example/forms
are being added to the commons
bundle, despite the fact that most are used on only a single page. This is causing unnecessary bloat of the commons
bundle which is loaded on every page.
If feels like it should be possible for the individual components within @example/forms
to be split out into the page-specific bundles, but I'm not sure if I'm hoping for too much. For example, if Form A is only used on page 4, I'd like Form A to only be added to the bundle for page 4.
Is this something that is supported, and if so, what could be preventing this from happening.
@example/forms
is transpiled with ES6 exports left intact, and sideEffects
is set to false
in its package.json
.
Its main
file is index.js
which (re)exports all the form components from their own files as separate named exports:
export {default as FormA} from './forms/formA'
export {default as FormB} from './forms/formB'
...
Another thing that might be relevant is that all the exports from @example/forms
are used within the Gatsby site, just on separate pages. It appears that perhaps tree-shaking cannot be used across bundles, i.e. tree shaking is performed first, then what is left is split into bundles. Using that logic, @example/forms
would have been used on multiple pages and would be moved to commons. However this is definitely not optimal, and hopefully isn't what is happening.
Tree-shaking process is part of the minification step which is really at late stage, since that it is not possible to reverse the order, first tree-shake then chunk split.
But you can split your forms lib before hand using some heuristics
splitChunks
in order to split this @example/forms
module into a separate chunk as whole, this will decrease the commons bloat & on the first form usage all forms will be loaded.// webpack.config.js
module.exports = {
...
optimization: {
splitChunks: {
cacheGroups: {
formComponents: {
chunks: 'all',
enforce: true,
minChunks: 1,
name: 'form-components',
priority: 10,
test: /[\\\/]node_modules[\\\/]@example[\\\/]forms[\\\/]/,
},
},
},
},
...
};
@example/forms
module based on some heuristics, in my example I'm "grouping" all items based on the form path.// webpack.config.js
module.exports = {
...
optimization: {
splitChunks: {
cacheGroups: {
formComponents: {
chunks: 'all',
minChunks: 1,
name(module) {
const libPath = path.resolve(path.join('node_modules/@example/forms'))
const folderBasedChunk = path.relative(libPath, module.context);
const hash = crypto.createHash('sha1');
hash.update(folderBasedChunk)
return hash.digest('hex').substring(0, 8)
},
priority: 10,
reuseExistingChunk: true,
enforce: true,
test: /[\\\/]node_modules[\\\/]@example[\\\/]forms[\\\/]src[\\\/]forms[\\\/]/,
},
},
},
...
};
You can checkout a small example that I've created that mimics the scenario. https://github.com/felixmosh/webpack-module-split-example
Hope this helps