Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code-splitting separate exports from a package to different bundles

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.

like image 889
Undistraction Avatar asked Jun 04 '20 18:06

Undistraction


People also ask

Does code splitting reduce bundle size?

Reduce your bundle size by code-splitting Instead of keeping all your code in the one bundle, you can split it up into multiple bundles to be loaded separately. This is known as code-splitting. If your app has multiple pages, an easy candidate for code-splitting is to split up the code by each page.

What is a purpose of code splitting?

Code splitting allows you to break up the monolithic bundle into various smaller bundles. You can then load the bundles in parallel or implement lazy loading, delaying download of certain code until a user needs it.

Can webpack split code into separate files?

Code splitting is one of the most compelling features of webpack. This feature allows you to split your code into various bundles which can then be loaded on demand or in parallel.

What can you use to handle code splitting?

Code-Splitting is a feature supported by bundlers like Webpack, Rollup and Browserify (via factor-bundle) which can create multiple bundles that can be dynamically loaded at runtime.


1 Answers

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

  1. The basic one is to use 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[\\\/]/,
        },
      },
    },
  },
  ...
};
  1. Make a chunk for each form inside the @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

like image 166
felixmosh Avatar answered Oct 22 '22 01:10

felixmosh