Luoruize
Luoruize

Reputation: 688

Tree-shaking with rollup

I have a project in which I bundle a components library using Rollup (generating a bundle.esm.js file). These components are then used in another project, that generates web pages which use these components - each page is using different components. The problem is, that the entire components library is always bundled with the different page bundles, regardless of which components I'm using, unnecessarily increasing the bundle size.

This is my Rollup setup:

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import pkg from './package.json';

const extensions = [
  '.js', '.jsx', '.ts', '.tsx',
];
export default [
  {
    input: './src/base/index.ts',
    plugins: [
      peerDepsExternal(),
      resolve({ extensions }),
      babel({
        exclude: 'node_modules/**',
        extensions,
      }),
      commonjs(),
    ],
    output: [
      { file: pkg.main, format: 'cjs', sourcemap: true },
      { file: pkg.module, format: 'es', sourcemap: true },
    ],
    watch: {
      clearScreen: false,
    },
  },
];

I have "modules" set to false in webpack, as well.

Upvotes: 17

Views: 16392

Answers (2)

Juanma Menendez
Juanma Menendez

Reputation: 20199

As you don't know which components of your Component Library (CL) will be needed by the adopters repositories you need to export everything but in a way the adopters can execute a tree-shaking on your CL when they do their own build (and just include what they really need).

In a few words, you have to make your CL, tree-shakable. In order to achieve this, on your CL repo you have to:

  • Use bundlers that support tree-shaking (rollup, webpack, etc..)

  • Create the build for modules of type es/esm, NOT commonJS/cjs, etc..

  • Ensure no transpilers/compilers (babel,tsconfig, etc..) usually used as plugins, transform your ES module syntax to another module syntax. By the default, the behavior of the popular Babel preset @babel/preset-env may break this rule, see the documentation for more details.

// babelrc.json example that worked for me

[
  "@babel/preset-env",
  {
    "targets": ">0.2%, not dead, not op_mini all"
  }
],
  • In the codebase, you always have to use import/export (no require) syntax, and import specifically the things you need only.

    import arrayUtils from "array-utils"; //WRONG

    import { unique, implode, explode } from "array-utils"; //OK

  • Configure your sideEffects on the package.json.

    "sideEffects": ["**/*.css"], //example 1

    "sideEffects": false, //example 2

  • DO NOT create a single-bundle file but keep the files separated after your build process (official docs don't say this but was the only solution that worked for me)

// rollup.config.js example

const config = [
    {
        input: 'src/index.ts',
        output: [
            {
                format: 'esm', // set ES modules
                dir: 'lib', // indicate not create a single-file
                preserveModules: true, // indicate not create a single-file
                preserveModulesRoot: 'src', // optional but useful to create a more plain folder structure
                sourcemap: true, //optional
            },
        ],
        ... }]
  • Additionally, you may need to change your module entry point in order the adopters can directly access to the proper index.js file where you are exporting everthing:

// package.json example

   {
    ...
    "module": "lib/index.js", //set the entrypoint file
   }

Note: Remember that tree-shaking is executed by an adopter repository that has a build process that supports tree-shaking (eg: a CRA repo) and usually tree-shaking is just executed on prod mode (npm run build), no on dev mode. So be sure to properly test if this is working or not.

Upvotes: 15

Phạm Huy Phát
Phạm Huy Phát

Reputation: 1116

There are things you will need to do to achieve treeshakable code from both sides - the built package and the project using it.

From your code snippet, I see that you have not add flag preserveModules: true in the rollup config file to prevent the build output from bundling. Webpack can not treeshake a bundled file FYI.

export default {
  ...
  preserveModules: true,
  ...
}

On the side of the project that using it, you have to specify sideEffects in the package.json - read the doc to know how to config them. Beside that, the optimization in webpack has to has sideEffects: true, also read the doc here.

Hope this helps!

Upvotes: 11

Related Questions