Skip to main content
Tree shaking eliminates dead code from your JavaScript bundles, significantly reducing bundle size.

What is Tree Shaking?

Tree shaking is a term for dead code elimination in JavaScript. It relies on ES2015 module syntax (import and export) to detect which exports are used and remove the rest.
Tree shaking only works with ES2015 module syntax (static import/export). It doesn’t work with CommonJS (require/module.exports).

Enable Tree Shaking

1

Use production mode

Tree shaking is automatically enabled in production mode:
module.exports = {
  mode: 'production'
};
2

Use ES2015 modules

Ensure your code uses ES2015 import/export syntax:Good:
// math.js
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

// main.js
import { square } from './math';
console.log(square(5)); // cube is tree-shaken
Bad:
// math.js
module.exports = {
  square: (x) => x * x,
  cube: (x) => x * x * x
};

// main.js
const { square } = require('./math');
// cube is NOT tree-shaken with CommonJS
3

Configure optimization

Explicitly enable tree shaking options:
module.exports = {
  optimization: {
    usedExports: true,
    minimize: true,
    sideEffects: true
  }
};

Side Effects

Side effects are code that affects the environment when imported, even if nothing is used from the module.

Mark Side-Effect-Free Packages

In package.json, declare which files have side effects:
{
  "name": "my-library",
  "sideEffects": false
}
Or specify files with side effects:
{
  "name": "my-library",
  "sideEffects": [
    "./src/polyfills.js",
    "*.css"
  ]
}
Incorrectly marking modules as side-effect-free can break your application. Only set "sideEffects": false if you’re certain the code has no side effects.

Examples of Side Effects

Has side effects:
// Modifies global state
import './polyfill.js';

// Modifies prototype
Array.prototype.myMethod = function() { };

// CSS imports
import './styles.css';
No side effects:
// Pure exports only
export function add(a, b) {
  return a + b;
}

export const PI = 3.14159;

Module Concatenation

Also known as “scope hoisting”, this optimization merges modules into a single scope:
module.exports = {
  optimization: {
    concatenateModules: true,
    usedExports: true,
    providedExports: true
  }
};
Module concatenation is automatically enabled in production mode and works best with ES2015 modules.

Best Practices

Use Named Exports

Named exports enable better tree shaking: Better:
// utils.js
export function formatDate(date) { }
export function formatCurrency(value) { }

// main.js
import { formatDate } from './utils';
// formatCurrency is tree-shaken
Worse:
// utils.js
export default {
  formatDate: (date) => { },
  formatCurrency: (value) => { }
};

// main.js
import utils from './utils';
utils.formatDate();
// formatCurrency is NOT tree-shaken

Avoid Barrel Exports with Side Effects

Problematic:
// index.js - barrel file
export * from './moduleA';
export * from './moduleB';
export * from './moduleC';
If any module has side effects, all might be included. Better:
// Import specific modules directly
import { funcA } from './moduleA';

Configure Babel Carefully

Ensure Babel doesn’t transform ES2015 modules to CommonJS:
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ]
  ]
}
If Babel transforms modules to CommonJS, tree shaking won’t work. Set "modules": false in your Babel config.

Analyze Bundle

Check what’s included in your bundles:
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

Library Authors

If you’re publishing a library:

Provide ES2015 Modules

{
  "name": "my-library",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "sideEffects": false
}

Document Side Effects

Clearly document any side effects in your library:
{
  "sideEffects": [
    "./src/register-global.js"
  ]
}

Optimization Flags

Complete configuration for maximum tree shaking:
module.exports = {
  mode: 'production',
  optimization: {
    // Identify used exports
    usedExports: true,
    
    // Identify provided exports
    providedExports: true,
    
    // Respect sideEffects flag
    sideEffects: true,
    
    // Enable scope hoisting
    concatenateModules: true,
    
    // Minimize code
    minimize: true,
    
    // Minifier options
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            dead_code: true,
            unused: true
          }
        }
      })
    ]
  }
};
All these optimizations are enabled by default when using mode: 'production'.

Common Issues

Issue: Code Not Being Tree Shaken

Check:
  1. Are you using ES2015 modules?
  2. Is mode set to 'production'?
  3. Is Babel transforming modules to CommonJS?
  4. Are there unintended side effects?

Issue: Application Breaking After Tree Shaking

Solution: Check sideEffects configuration - you may have marked code with side effects as side-effect-free.
{
  "sideEffects": [
    "*.css",
    "./src/polyfills.js"
  ]
}