DEV Community

Cover image for How to write a tree-shakable component library

How to write a tree-shakable component library

Lukas Bombach on February 06, 2020

tl;dr if you just want to cut to the chase, the final product can be seen, cloned and forked right here on GitHub: https://github.com/LukasBomb...
Collapse
 
dance2die profile image
Sung M. Kim

Thanks for the post, Lukas.

If I am not mistaken, this is because the ESM syntax allows bundlers to statically check which parts of your code are used and which are not, which is harder with require because it can be used in more dynamic ways,...

Wouldn't ESM have the same problem because of dynamic import()?

I am not sure if tree-shaking doesn't work if dynamic import is used.

Collapse
 
lukasbombach profile image
Lukas Bombach

My original source for this is this

exploringjs.com/es6/ch_modules.htm...

which is linked here

webpack.js.org/guides/tree-shaking/

If I try to understand it, I would guess that there is a difference when you do a static import like this

import x from "y";

and when you do a dynamic import like this

import(something)

This might seem like nothing, but I guess that when you do static code analysis and all you have is a string, you can see 100% by the syntax if it is a static import or a dynamic one. And the static one can be tree-shaken and I guess the dynamic one can't.

That's my guess at least.

Collapse
 
dance2die profile image
Sung M. Kim

You are right.

I dug around a bit, and the Webpack author commented that with dynamic import, tree shaking is now performed.

github.com/webpack/webpack.js.org/...

Collapse
 
masood1 profile image
masood hussain

Hai Lukas,
Thank you for writeup,
i was using cra-bundle-analyser NPM package and trying to analyse build file, look like their is no tree shaking, it is generating one file index.cjs.js and importing it, size of the imported file is same despite of one component import or N, it is importing complete file,

can you please tell me how did you verify tree-shaking

Collapse
 
iwarner profile image
Ian Warner

Question.
Is it important to export each component as default

We use named exports
ie. export const Address

And globals
export * from './address/address'

Also we use babel directly instead of rollup
"build": "npm run clean && BABEL_ENV=esm babel components --out-dir esm --source-maps false"

esm: {
  presets: [
    [
      '@babel/preset-env',
      {
        modules: false
      }
    ]
  ],
  plugins: [
    [
      '@babel/plugin-transform-runtime',
      {
        useESModules: true
      }
    ]
  ],
  ignore: ['**/__tests__', '**/__mocks__', '**/Storyshots.test.js']
}
Enter fullscreen mode Exit fullscreen mode

However when we consume the library it does not do the tree shaking

Collapse
 
squgeim profile image
Shreya Dahal

I have been trying to build a react component library that exports ESM. I tried your config and it does work when I run the next app in dev mode next dev, but when I try to build it in production mode next build, it fails. It gives me the Invalid hook call error (even if I don't use hooks in the library).

Any idea what's happening?

Collapse
 
uriklar profile image
Uri Klar

Thanks for the great write up! I've followed your suggestions but i'm seeing my app build size is the same no matter how many components I import from my ui lib.
Any ideas what could be missing?
Thanks

Collapse
 
lihue profile image
Linus Hüsler

Thank you for this post.
I am struggling with the following problem: One of my components (e.g. Link from ui-library) uses moment as external dependency. As a result, my app contains the whole moment.js even if the app does not use the corresponding component at all.
Do you know how I could solve this?
Thanks for your support.

Collapse
 
lukasbombach profile image
Lukas Bombach • Edited

Hey Linus, I think what you need is the external setting of Rollup.

rollupjs.org/guide/en/#core-functi...

// rollup.config.js
export default {
  entry: 'src/index.js',
  dest: 'bundle.js',
  format: 'cjs',
  external: [ 'moment' ] // <-- add moment to your "external" modules
};
Collapse
 
michaelbayday profile image
Michael Dinh

This is not tree shaking, you will need to preserveModules and set output.dir within rollup to achieve true treeshaking

Collapse
 
uriklar profile image
Uri Klar

@michaelbayday can you expand on this please? I've followed this guide and indeed my library isn't being tree shaken.. Thanks!

Collapse
 
shanazkapil profile image
Kapil Shanaz

@lukasbombach
can we bundle pages using rollup?

Collapse
 
adrianrivers profile image
VanillaGorilla

WTH Lukas, this article was just recommended to me by a colleague, small world haha. Danke schön.

Collapse
 
lukasbombach profile image
Lukas Bombach

Honestly, this is just my way of reaching out to you! Glad it finally worked!

Collapse
 
ajnior profile image
Agnaldo Cardoso Junior

Thanks for the post, Lukas!

Collapse
 
truedrug profile image
Sushree

Isn't listing 'sideEffects: false' mandatory in package.json of ui-library. I think you are using webpack 4