Why is my React component library not tree-shakable?

You took a step in the right direction. Tree shaking works on file boundaries, it drops files that are not used inside your import path and has no side effects.

Your first example couldn't utilize tree shaking because your whole package was bundled inside a single file.

Your second example bundles into multiple files but your main bundle (with the index.js input) is still a huge file that bundles everything together instead of leaving the imports inside it alone. (This is just an assumption based on your posted build process, please check your index.js bundle to verify this).

You have to either:

  • define the dependencies of index.js as externals. Try adding the whole /src directory as external.

    OR

  • Use babel instead of rollup for your build process to leave exports and imports intact.

If you have questions like this just check the material UI build process and final build folder (npm i @material-ui/core and look into the node_modules). That's a nice inspiration for the solution.


I found one possible solution to my problem. It has nothing to do with tree-shaking though. I’m simply splitting the library into several independent chunks by making use of a rather new feature of rollup (I had to upgrade a bunch of dependencies in order for it to work) and providing an object, mapping names to entry points, to the input property of the rollup configuration. It looks like this:

input: {
    index: 'src/index.js',
    theme: 'src/Theme',
    badge: 'src/components/Badge',
    contentCard: 'src/components/ContentCard',
    card: 'src/elements/Card',
    icon: 'src/elements/Icon',
    ...

Here is rollup’s documentation for it: https://rollupjs.org/guide/en/#input

The output is set to a directory:

output: [
  {
    dir: 'dist/es',
    format: 'es',
  },
],

Then I declare the entry point in my package.json as follows:

"module": "dist/es/index.js",

In my test app I import the components as if nothing changed:

import React from 'react';
import { Theme, Badge } from 'my-react-component-library'

That seems to work so far, though it’s again not tree-shaking and I would still like to know how to make my component library tree-shakable.

UPDATE:

Turns out tree shaking worked all the time! Here is what was “wrong” with the library:

  1. The Icon component imported all icons so that all svg files ended up in the bundle as soon as you used at least one icon or a component that uses an icon.
  2. The Theme component inlined a font as a base-64 string into the bundle.

I resolved the first issue by dynamically importing each icon when needed and the second issue by reducing the MAX_INLINE_FILE_SIZE_KB parameter for rollup-plugin-url in order to split out the font and have it loaded as an asset.

So, here is my advice for anybody who like me starts believing that tree-shaking doesn’t work, just because the bundle is ridiculously large: Double-check your bundle analysis report (i.e. using source-map-explorer), look for the big guys and double-check your imports.