Tree Shaking Unused Code with Rollup: A Deep‑Dive Guide

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Introduction

Modern JavaScript apps often pull in large libraries and frameworks, but shipping every export—even those never used—bloats bundles and slows page loads. Tree shaking is a build‑time optimization that prunes “dead” exports (functions, classes, modules you never reference) from your final output. Rollup, built around ES modules’ static analysis, excels at precise dead‑code elimination. In this guide, you’ll learn:

  • How tree shaking works under the hood in Rollup
  • Project setup for optimal elimination of unused code
  • Common pitfalls that prevent effective shaking
  • Advanced techniques: manual chunks, code splitting, minification
  • Integrating CSS pruning for a completely lean deliverable

Let’s transform your codebase into a razor‑sharp, high‑performance bundle.

1. How Tree Shaking Works in Rollup

1.1 ES Modules Enable Static Analysis

  • Static Imports/Exports: import {foo} from 'lib', export function bar()
  • No Side‑Effect Calls: Rollup can see exactly which exports are used; it doesn’t bundle the rest
  • Dead Code Elimination: Unused exports never enter the dependency graph

1.2 Comparison to CommonJS

FeatureCommonJS (require)ES Modules (import)
Static AnalysisLimited (dynamic at runtime)Fully static at build time
Tree ShakingPoor—most require bundlers include everythingExcellent—Rollup prunes unused
File Formatmodule.exportsexport, export default

2. Setting Up Rollup for Tree Shaking

2.1 Initialize Your Project

bashCopyEditmkdir rollup-shake-demo
cd rollup-shake-demo
npm init -y
npm install rollup @rollup/plugin-node-resolve --save-dev

Directory structure:

pgsqlCopyEditrollup-shake-demo/
├── src/
│   ├── index.js
│   ├── utils.js
│   └── feature.js
└── rollup.config.js

2.2 Example Source with Unused Code

jsCopyEdit// src/utils.js
export function usedHelper() {
  console.log('Used!');
}
export function unusedHelper() {
  console.log('Unused!');
}

// src/feature.js
export function feature() {
  console.log('Feature code');
}

// src/index.js
import { usedHelper } from './utils.js';
import { feature } from './feature.js';

usedHelper();
feature();

unusedHelper is never imported → candidate for removal.

2.3 Basic Rollup Configuration

jsCopyEdit// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve';

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/bundle.js',
    format: 'es',
    sourcemap: true
  },
  plugins: [
    nodeResolve()
  ]
};

Run:

bashCopyEditnpx rollup -c

Inspect dist/bundle.js: only usedHelper and feature should appear.

3. Ensuring Effective Tree Shaking

3.1 Use ES Module Syntax Throughout

  • Avoid require() / module.exports
  • Prefer import / export

3.2 Mark Side‑Effect‑Free Packages

In your package.json:

jsoncCopyEdit{
  "sideEffects": false
}

For external CJS packages, configure Rollup’s CommonJS plugin:

jsCopyEditimport commonjs from '@rollup/plugin-commonjs';

plugins: [
  commonjs({ include: 'node_modules/**', ignore: [] })
]

3.3 Avoid Re‑Exports of Everything

jsCopyEdit// bundle-utils.js — bad
export * from './utils.js';

Instead, explicitly import only needed exports in your entry:

jsCopyEditimport { usedHelper } from './utils.js';

4. Analyzing and Troubleshooting

4.1 Generate a Bundle Manifest

Add a plugin to dump bundle info:

jsCopyEdit// rollup.config.js additions
import fs from 'fs';

plugins: [
  // ... other plugins ...
  {
    name: 'meta-dump',
    generateBundle(options, bundle) {
      fs.writeFileSync('dist/meta.json', JSON.stringify(bundle, null, 2));
    }
  }
]

Inspect dist/meta.json to see which modules remained.

4.2 Common Pitfalls

SymptomCauseRemedy
Unused exports still in bundleUsing CommonJS or dynamic importsConvert to ES modules; avoid require
Entire library includedLibrary not marked sideEffects: falseAdd package.json flag or plugin config
Re‑exporting pulls in dead codeUsing export *Import only needed symbols

5. Advanced Techniques

5.1 Code Splitting & Manual Chunks

jsCopyEdit// rollup.config.js
export default {
  input: { main: 'src/index.js' },
  output: {
    dir: 'dist',
    format: 'es',
    manualChunks(id) {
      if (id.includes('node_modules')) return 'vendor';
      if (id.includes('src/feature.js')) return 'feature';
    }
  }
};

Loads vendor, feature, and main separately, and each is tree‑shaken individually.

5.2 Minification with Terser

bashCopyEditnpm install rollup-plugin-terser --save-dev
jsCopyEditimport { terser } from 'rollup-plugin-terser';

plugins: [
  // ...other plugins...
  terser({
    module: true,
    compress: {
      passes: 2,
      pure_getters: true
    },
    mangle: true
  })
]

Minification removes unreachable code within used modules (dead branches).

5.3 CSS Dead‑Code Elimination

If using CSS imports in JS, combine Rollup with PurgeCSS or PostCSS:

bashCopyEditnpm install postcss rollup-plugin-postcss @fullhuman/postcss-purgecss --save-dev
jsCopyEditimport postcss from 'rollup-plugin-postcss';
import purgecss from '@fullhuman/postcss-purgecss';

plugins: [
  postcss({
    extract: true,
    plugins: [
      purgecss({
        content: ['./src/**/*.js', './public/index.html']
      })
    ]
  })
]

Removes unused selectors, mirroring JS tree shaking.

6. Best Practices

  1. Always author libraries in ES modules to maximize static analysis.
  2. Set "sideEffects": false in package.json for your own code.
  3. Explicitly import only what you need—avoid wildcard re‑exports.
  4. Analyze builds regularly with metafile dumps and bundle analyzer plugins.
  5. Combine tree shaking with minification for deep dead‑code removal.
  6. Split code logically into chunks for on‑demand loading.

Conclusion

Rollup’s tree shaking, powered by ES module static analysis, lets you ship only the code your application uses—trimming unused exports and dependencies from your bundles. By:

  • Authoring with ES import/export
  • Marking packages side‑effect‑free
  • Avoiding CommonJS patterns and wildcard re‑exports
  • Employing code splitting, minification, and CSS pruning

—you’ll deliver lightning‑fast, minimal payloads to your users. Integrate these strategies into your build pipeline today and watch your bundle sizes—and load times—plummet.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts