What’s up with tree shaking?

krakenimages-liT5AlTmC8I-unsplash.jpg

Me when I found out how tree shaking really works

I’m willing to bet you have a lot of “dead” code in your webpack bundles. I used to think unused code was magically excluded by webpack but it turned out I was wrong. Oh the woe for slow websites around the world. Here is how it all started…

The Discovery #

I put a website into Page Speed Insights today and one of the opportunities to make my site faster was to reduce unused Javascript. It appeared that around half of my Javascript was unused. If this Javascript is unused, why is it being bundled into my application?

A few years ago I heard the term tree-shaking, which was the removal of unused code from a bundle. The big hype I inferred was that Webpack 4 would tree-shake while bundling, thus eliminating unused code and making bundles significantly smaller. Was this true? Not really, for two reasons:

  1. Packages have to explicitly specify if they can be shook and most don’t
  2. webpack can only tree shake import/export statements but not require

This means that every time you npm install and include a new library, chances are high you’ll be bloating your bundle with dead code as well. The documented way you specify if your code can be shook by webpack is by adding the following property to your package.json:

{
  ...
  "sideEffects": false,
}

Lodash specifies this in their package.json, for example, so when you import _.filter, none of the other 100+ utility functions will be in your bundle. A big savings. However, many popular React libraries I used did not specify that they could be shook.

My personal story #

I use react-burger-menu. It’s a popular project with 4.5k stars on Github. There are 10 varieties of the menu (slide, stack, bubble, push, etc) and you import only the variety you want. I like slide so I imported it into my project:

import { slide as Menu } from 'react-burger-menu'

But because react-burger-menu does not have a sideEffects property in its package.json, when webpack runs it will bundle all 10 varieties into my project. Wow. In no company I ever worked for was this ever mentioned, even when we were doing “performance” weeks. (To think back to those large React app bundles we created that were probably filled with unused code. Oh the load time! Oh the parse time!)

So I forked the library and added the sideEffects property to the package.json, but when I installed my version the bundle size stayed the same. What happened? The entry point for the package is:

{
  ...
  "main": "lib/BurgerMenu.js",
}

When I looked inside this file I found that the code was using require instead of import and thus webpack could not tree shake. I went in and changed the require‘s to import’s and like magic my bundle size went down by 7% and the website worked same as before.

Screenshot 2021-10-02 at 17.05.17.png

The before:

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports['default'] = {
  slide: require('./menus/slide'),
  stack: require('./menus/stack'),
  // other exports omitted for brevity...
  reveal: require('./menus/reveal')
};
module.exports = exports['default'];

and after:

export * as slide from './menus/slide';
export * as stack from './menus/stack';
// other exports omitted for brevity...
export * as reveal from './menus/reveal';

See the commit.

Why do we live in a world like this? #

It’s a complicated topic. Many libraries bundle their code for web browsers as well as Node.js. Older version of Node.js did not understand import/export syntax and not every project uses webpack so it’s best to stick with require’s for maximum compatibility. I’m sure a lot of developers also assume that dead code is magically excluded and fewer have read the official webpack tree-shaking documentation, which I highly recommend for a more thorough explanation.

The main takeaway is this: if you’re using create-react-app or webpack, make sure your plugins are not including unused code in your bundle.

If you have questions or want to share some victories, you can find me on Twitter

 
12
Kudos
 
12
Kudos

Now read this

McCully Basin traverse

The Wallowa Mountains After some Googling, I discovered that Wing Ridge and McCully Basin are the two most popular destinations for backcountry skiing out of Joseph. Since we love slogs, McCully Basin was the obvious choice. The plan was... Continue →