Taming Bundle Bloat: Mastering Tree Shaking in Rollup
Learn how Rollup's tree shaking removes unused code through static analysis of ES Modules and how to avoid common pitfalls like side effects and CommonJS dependencies.
04 Jun 2026, 15:49 UTC

The Cost of Unused Code
Modern JavaScript development relies heavily on modular libraries. However, importing a single utility function from a large library often drags in hundreds of lines of unused code. This increases the bundle size, slows down page loads, and forces the browser to parse JavaScript that will never execute. The goal is to ensure that only the code actually called in your application reaches the production environment.
Rollup solves this through Tree Shaking—a form of dead-code elimination. Unlike traditional minifiers that remove unreachable code within a function, Rollup uses static analysis of ES Modules (ESM) to determine which exports are never imported by any other module, discarding them entirely during the bundling phase.
How Rollup Identifies Dead Code
Rollup employs a "live code inclusion" strategy. Instead of searching for code to delete, it starts at the entry point and marks every function, variable, and class that is explicitly used. Anything left unmarked at the end of the analysis is omitted from the final bundle.
This process depends entirely on the static nature of ESM. Because import and export statements must happen at the top level of a module, Rollup can determine the dependency graph without actually running the code. This is why ESM is superior to CommonJS (module.exports and require) for optimization; CommonJS allows dynamic requires inside conditionals, which makes it impossible for a bundler to know for certain if a piece of code will be needed.
The Side-Effect Hurdle
The biggest challenge to effective tree shaking is the side effect. A side effect occurs when a module does something other than exporting values—such as modifying a global variable, adding a listener to the window object, or initializing a polyfill.
If Rollup encounters a module that it suspects has side effects, it will include that module in the bundle even if none of its exports are used. This is a safety mechanism to prevent the bundler from accidentally breaking your application by removing code that performs critical setup tasks.
Practical Example: Optimizing a Utility Library
Consider a scenario where you have a math utility library and a main application file. To ensure tree shaking works, you must use named exports.
The Library (mathUtils.js)
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b; // This should be shaken out
The Application (main.js)
import { add } from './mathUtils.js';
console.log(add(5, 10));
The Configuration (rollup.config.js)
Run this configuration using the Rollup CLI. Ensure you have rollup installed in your project directory.
export default {
input: 'main.js',
output: {
file: 'bundle.js',
format: 'esm'
},
treeshake: true // Enabled by default, but can be tuned
};
Verification: Run npx rollup -c. Open bundle.js and search for the string "multiply". If tree shaking is working, the multiply function will be entirely absent from the output, while add remains.
Trade-offs and Limitations
Tree shaking is not a magic bullet. There are specific patterns that will defeat the analyzer:
- CommonJS Dependencies: If you import a library written in CommonJS, Rollup cannot statically analyze it. You will need
@rollup/plugin-commonjsto wrap these modules, but the resulting tree shaking is often less efficient. - Dynamic Imports: Using
import()as a function tells Rollup that the module might be needed at runtime, which often prevents the removal of exports within that module. - Object Mutation: If you export a large object containing multiple functions (e.g.,
export default { add, subtract }), Rollup often treats the entire object as a single entity. If you use one property, the whole object is kept. Always prefer named exports.
Actionable Checklist for Smaller Bundles
To maximize the effectiveness of Rollup's tree shaking, follow these rules:
- Use ESM exclusively: Avoid
requireandmodule.exportsin your source code. - Prefer named exports: Use
export const func = ...instead ofexport default { ... }. - Audit side effects: If you are publishing a library, add
"sideEffects": falseto yourpackage.json. This explicitly tells Rollup and other bundlers that your modules do not modify global state, allowing them to be more aggressive in removing unused code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.