Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

How to Make Your React Component Library Tree Shakeable

November 11, 2021
reactrollupwebpack

These days, it is pretty common for companies to build their own reusable components that can be used across several apps. In this post, we’ll learn how to configure a reusable component library so that it can be tree-shaken by the apps that use it.

How to make a reusable react component library tree shakeable

What is tree shaking and why?

Tree shaking is a process that bundlers like webpack and rollup go through to remove unused code.

Tree shaking means that only components from our library used in the app are included in the app bundle. If not tree shaken, all the components will be included even if they are not used. So, tree shaking reduces the size of the app bundle and increases the app’s performance because less time is spent downloading, decompressing, parsing, and executing it.

It is important to note that it is the consuming app doing the tree shaking and not the component library. Therefore, we need to ensure our component library’s structure, format, and settings will allow tree shaking.

Include native module format

We are using rollup to bundle the component library. If we include a bundle for ESM format, the library will have a much better chance of being tree shaken than the CommonJS (CJS) format or other formats. This is because the unused exports can be detected easier because they are static.

Here’s how to configure rollup to include an ESM bundle:

export default {
  ...,
  output: [
    ...,
    {
      ...,
      format: 'esm',      ...,
    },
  ],
  ...

Inform app bundlers that there are no side effects

There are cases when bundlers are unsure whether code is unused. For example, importing a component’s styles could mean that the button styles impact other parts of the app.

Import "./button.css"

The sideEffects field in package.json informs the bunder that all files in the component library are pure and free from side effects:

{
  "name": "my-react-library",
  "version": "0.0.1",
  "main": "dist/index.cjs.js",
  "sideEffects": false,  ...
}

Preserve module structure

Rollup will bundle into a single file by default. A single file means that the bunder can’t use the sideEffects flag to skip modules.

The module structure can be preserved using the preserveModules field in the rollup config:

export default {
  ...,
  output: [
    ...,
    {
      format: 'esm',
      sourcemap: true,
      dir: 'dist',      preserveModules: true,    },
  ],
  ...

Note that the dir field needs to be supplied rather than file because many files will now be bundled rather than one. The value of the dir field is the folder where the bundled files are placed.

Inform app bundlers that a native module bundle is available

Specify the entry point to the module bundle using the module field in package.json:

{
  "name": "my-react-library",
  "version": "0.0.1",
  "main": "dist/index.cjs.js",
  "module": "dist/src/index.js",  "sideEffects": false,  
}

The app bundler will then use the ESM bundle rather than the bundle specified by the main field.

Transpile the components with the latest babel preset

Babel can mark parts of React code as pure with a /*#__PURE__*/ marker as shown below:

function Button(_a) {
  var children = _a.children;
  return /*#__PURE__*/React__default.createElement("button", null, children);
}

These markers help bundlers determine that the component is pure and hence tree shakeable.

So, make sure the component library build includes babel with the latest preset. For example, here is babel in the rollup config:

...
import { babel } from "@rollup/plugin-babel";
export default {
  ...,
  plugins: [
    ...,
    babel({      babelHelpers: "runtime",      exclude: /node_modules/,      extensions: [".js", ".ts", ".tsx"],    }),    ...
  ]
};

Here’s the babel config:

{
  "presets": [
    ["@babel/env", { "modules": false }],
    "@babel/typescript",
    [
      "@babel/preset-react",
      {
        "runtime": "automatic"
      }
    ]
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime", { "useESModules": true }]
  ]
}

The { "modules": false } setting for "@babel/env" is important to preserve the ESM format.

Ensure third party libraries are tree shakeable

When building a component library, if leveraging 3rd party libraries, ensure they are tree shakeable. For example, you can carry out the following steps to quickly check whether a library is tree shakeable:

  • Create an app using Create React App.
  • Build the app and note down the file sizes.
  • Install the 3rd party library.
  • Reference a small part of the 3rd party library in the app.
  • Build the app and compare the bundle file sizes. Hopefully, the bundle size hasn’t increased much. If the bundle size has increased significantly, the 3rd party library may not be tree shaking.
  • Reference a little more of the 3rd party library in the app.
  • Build the app and compare the bundle file sizes. If the bundle size hasn’t changed, the 3rd party library may not be tree shaking.

Wrap up

In summary, to ensure a React component library is tree shakeable:

  • Include ESM format and preserve that format.
  • Use the module package.json field to let bundlers know the components are in ESM format.
  • Use the sideEffects package.json flag to let bundlers know the components are pure.
  • Use the latest babel preset to ensure the latest tree shaking markers are set.
  • Test that third-party libraries are tree shakeable before you leverage them.

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Find out more