Tree Shaking

How to tame the devDependencies of your project?

Tree shaking is a feature enabled by the ES2015 module definition. The idea is that given it's possible to analyze the module definition statically without running it, webpack can tell which parts of the code are being used and which are not. It's possible to verify this behavior by expanding the application and adding code there that should be eliminated.

Starting from webpack 5, tree shaking has been improved and it works in cases where it didn't work before, including nesting and CommonJS.

Demonstrating tree shaking#

To shake code, you have to define a module and use only a part of its code:

src/shake.js

const shake = () => console.log("shake");
const bake = () => console.log("bake");

export { shake, bake };

To make sure you use a part of the code, alter the application entry point:

src/index.js

...
import { bake } from "./shake";

bake();

If you build the project again (npm run build) and examine the build (dist/main.js), it should contain console.log("bake"), but miss console.log("shake"). That's tree shaking in action.

To understand which exports are being shaked out, set `stats.usedExports` field to `true` in webpack configuration.
For tree shaking to work with TypeScript, you have to set `compilerOptions.module` to `es2015` or equivalent. The idea is to retain ES2015 module definitions for webpack to process as it needs the information for tree shaking.

Tree shaking on package level#

The same idea works with dependencies that use the ES2015 module definition. Given the related packaging, standards are still emerging, you have to be careful when consuming such packages. Webpack tries to resolve package.json module field for this reason.

For tools like webpack to allow tree shake npm packages, you should generate a build that has transpiled everything else except the ES2015 module definitions and then point to it through package.json module field. In Babel terms, you have to let webpack to manage ES2015 modules by setting "modules": false.

Another important point is to set "sideEffects": false to state that when the code is executing, it doesn't modify anything outside of its own scope. The property also accepts an array of file paths if you want to be more specific. The Stack Overflow question related to this explains in detail why.

Tree shaking with external packages#

To get most out of tree shaking with external packages, you have to use babel-plugin-transform-imports to rewrite imports so that they work with webpack's tree shaking logic. See webpack issue #2867 for more information.

It's possible to force "sideEffects": false at webpack configuration by setting up a loader definition with test: path.resolve(__dirname, "node_modules/package") and sideEffects: false fields.

[SurviveJS - Maintenance](https://survivejs.com/maintenance/packaging/building/) delves deeper to the topic from the package point of view.

Conclusion#

Tree shaking is a potentially powerful technique. For the source to benefit from tree shaking, npm packages have to be implemented using the ES2015 module syntax, and they have to expose the ES2015 version through package.json module field tools like webpack can pick up.

To recap:

  • Tree shaking drops unused pieces of code based on static code analysis. Webpack performs this process for you as it traverses the dependency graph.
  • To benefit from tree shaking, you have to use ES2015 module definition.
  • As a package author, you can provide a version of your package that contains ES2015 modules, while the rest has been transpiled to ES5. It's important to set "sideEffects": false as after that webpack knows it's safe to tree shake the package.

You'll learn how to manage environment variables using webpack in the next chapter.

Previous chapter
Minifying

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?