How I fixed webpack tree shaking in 3 easy steps

Tree shaking is an important step in Webpack bundling to get rid of unused stuff.

Step 1

{
  "presets": [["@babel/env", {
    targets: {
      browsers: ['last 2 versions'],
    },
    // The magic sauce
    modules: false,
  }]]
}

The modules: false is what does it.

This tells babel to leave the imports and exports alone. Because all this tree shaking business is built upon "statically analyzing" you're dependency tree, which only works when ES modules are used. Commonjs modules that use therequire style for importing don't work with tree shaking

Note: that'll break jest probably, so make sure you turn modules back on in test.

"test": {
  "presets": ["@babel/env", {
    "targets": {
      "node": true
    },
    "modules": "commonjs",
  }],
},

ChakraUI's babel.config.js is also a pretty good example https://github.com/chakra-ui/chakra-ui/blob/develop/babel.config.js

Step 2

Make sure webpack is set to mode: production. This turns on a bunch of internal flags to make it all work.

Step 3

This one took me a while to figure out.

The code base I work in, not unlike lots and lots of others, does this kinda thing a lot via index.ts or index.js where we define the public API of a package...

export { default as Thing } from './thing';
export { default as Thing2 } from './thing2';
export { default as Thing3 } from './thing3';
export { default as Thing4 } from './thing4';

Here's the problem, webpack doesn't know whether or not any of those thing imports are used without a little extra help.

sideEffects: false is what you need.

.Throw that into the package.json of the libraries where those index's are used.

Webpack uses that flag internally to help the tree shaking actually work.

Magic.

HOWEVER.

The final thing with this though is, make sure your code actually doesn't have "side effects". Meaning, stuff like this isn't happening...

export const sideEffectyThing = window.localStorage.getItem('stuff');

This is bad because the moment you import this code, it calls something on the window. That's no bueno. Put that in a function.

export const getStuff = () => window.localStorage.getItem('stuff');

Another one, which is super common is...

import './component.scss';

In this case, you'll have to mark your module as sideEffect: ['*.scss']; . https://github.com/webpack/webpack/issues/6741#issuecomment-372720641