Tree shaking and sideEffects

webpack · Advanced · 12 min

Unused library code is often the largest part of a bundle.

Tree shaking removes exports nobody imports. In webpack it is a cooperation of several steps, all enabled in production mode:

  • optimization.usedExports: webpack marks which exports of each module are used.
  • The minifier (Terser) then deletes the unused, now-unreferenced code.
  • optimization.sideEffects: if a package declares "sideEffects": false in its package.json, webpack can skip entire modules whose exports are unused, even if they were imported through a re-exporting index file.
  • optimization.concatenateModules (scope hoisting) merges modules into one scope, making the output smaller and faster.

What breaks it:

  • CommonJS (require, module.exports): webpack 5 can analyze some patterns, but ESM is far more reliable. Make sure Babel does not convert ESM to CommonJS (modules: false in preset-env; the default with babel-loader is to keep ESM).
  • Side effects at module top level (polyfills, global CSS imports, registering things). If you mark a package "sideEffects": false, list files that do have side effects: "sideEffects": ["*.css", "./src/polyfills.js"], or your CSS disappears.
  • Importing a whole namespace and using it dynamically (lib[name]).

Mark pure function calls with /*#__PURE__*/ so minifiers can drop them when unused. Verify with the bundle analyzer, or by setting usedExports with mode: "development" and looking for /* unused harmony export */ comments.

Example

// src/math.js
export const add = (a, b) => a + b;
export const heavyStats = (xs) => xs.reduce((s, x) => s + x * x, 0); // never imported

// src/index.js
import { add } from "./math.js";
console.log(add(2, 3));

// package.json of the app or a library
//   "sideEffects": ["*.css", "./src/polyfills.js"]

// $ npx webpack --mode development --optimization-used-exports
// dist/main.js contains:  /* unused harmony export heavyStats */
// $ npx webpack --mode production
// heavyStats is gone from dist/main.js

Remember

  • Tree shaking needs ES modules
  • usedExports marks, the minifier removes
  • sideEffects: false lets webpack skip whole modules
  • List CSS and polyfills in sideEffects so they survive

Watch out for

  • Setting "sideEffects": false in an app that imports global CSS, silently dropping the styles.

Check yourself

  1. Why can `"sideEffects": false` make global CSS imports disappear?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: webpack.js.org/guides/tree-shaking/