Pruning the Bloat: Mastering Tree Shaking in Rollup
Stop shipping unused code. Learn how Rollup's tree shaking uses ESM static analysis to prune dead code and how to avoid the common 'side effect' pitfalls that break production apps.
21 Feb 2026, 11:37 UTC

The Cost of the "Utility Library"
Many developers build internal utility libraries that grow over time. You start with a few date helpers, and two years later, you have a 50KB library containing everything from string manipulation to complex API wrappers. The problem arises when a consumer only needs a single 2KB function but ends up importing the entire 50KB bundle into their production application.
The solution is tree shaking—the process of removing "dead code" (unused exports) from the final bundle. In Rollup, this isn't a post-processing step handled by a minifier; it is a core part of how Rollup analyzes the dependency graph using ES Modules (ESM).
How Rollup Identifies Dead Code
Tree shaking relies on the static nature of import and export statements. Unlike CommonJS (require), which can be called conditionally inside an if statement, ESM imports must happen at the top level. This allows Rollup to build a complete map of the code before any of it actually runs.
When Rollup processes your entry point, it marks every function, class, or variable that is explicitly imported. Anything not marked as "used" is excluded from the final output. If you export ten functions from a file but only import one, the other nine simply never make it into the generated JavaScript file.
The Side Effect Trap
The biggest risk with tree shaking is the side effect. A side effect occurs when a module does something other than exporting values—for example, modifying a global variable, adding a prototype to a built-in object, or initializing a polyfill.
If Rollup sees a module that doesn't export anything used by the app, it might remove the entire module. However, if that module was intended to set window.globalConfig = { ... }, removing it will break the application. To prevent this, Rollup provides the moduleSideEffects configuration option.
Configuring Side Effects
You can tell Rollup which files are "pure" (contain no side effects) or which specific files must be preserved regardless of whether their exports are used.
// rollup.config.js
export default {
input: 'src/index.js',
output: { file: 'dist/bundle.js', format: 'esm' },
treeshake: {
moduleSideEffects: ['src/polyfills.js', 'src/global-init.js']
}
};
Worked Example: Utility Pruning
Consider a math library with two functions. We want to ensure that importing only one does not include the other.
File: src/math-utils.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
File: src/main.js (Entry Point)
import { add } from './math-utils.js';
console.log(add(5, 5));
Execution:
Run the build command from your terminal (ensure rollup is installed in your project):
npx rollup src/main.js --file dist/bundle.js --format esm
Verification:
Open dist/bundle.js. You will find the add function, but the subtract function will be entirely absent from the code. If you were to change the import to import * as Math from './math-utils.js', Rollup would be forced to include both functions because the entire module object is being requested.
Trade-offs and Limitations
Tree shaking is not a magic bullet. Its effectiveness is limited by how you write your code:
- CommonJS Modules: Modules using
module.exportsare significantly harder to tree shake because their exports are dynamic. For maximum efficiency, always author libraries in ESM. - Object Mutation: If you export a single large object containing multiple methods (e.g.,
export default { add, subtract }), Rollup cannot shake individual methods off that object. You must use named exports. - Dynamic Imports: Using
import()for code splitting can sometimes obscure the static analysis, though Rollup handles this better than most.
Actionable Checklist for Lean Bundles
To ensure your library is fully tree-shakable, follow these rules:
- Use
export const func = ...instead ofexport default { func }. - Avoid top-level code that modifies globals unless explicitly marked in
moduleSideEffects. - Verify your output by searching for a known unused function name in the final
dist/bundle. - Avoid using
require()in any part of the dependency chain.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.