Rollup Tree-Shaking: Configuring sideEffects for Smaller Bundles
Rollup's tree-shaking only removes dead code when packages correctly declare sideEffects in package.json. Learn how to configure it, verify the pruning, and avoid common pitfalls that leave unused functions in your production bundle.
31 Aug 2025, 05:48 UTC

The Problem: Dead Code That Refuses to Die
You've switched to ES modules, enabled treeshake: true in your Rollup config, and yet the production bundle still contains functions you never import. The culprit is often a missing or incorrect sideEffects field in package.json—either in your own package or in a dependency. Without that hint, Rollup must assume every imported module could mutate global state, so it keeps the code "just in case."
How Rollup Decides What to Drop
When treeshake: true (the default in production), Rollup builds a module graph and marks exports as used or unused based on static import/export statements. If an imported module declares "sideEffects": false, Rollup knows it can safely discard any export that isn't reachable from an entry point. If the field is missing or true, the entire module is retained—even if you only use one helper function.
CommonJS (require) modules are always treated as side-effectful because static analysis can't reliably trace their exports. That's why migrating dependencies to ESM is a prerequisite for aggressive tree-shaking.
Worked Example: A Utility Library
Imagine a small internal package @company/utils with three files:
// src/math.js
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// src/strings.js
export function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); }
// src/index.js (barrel)
export * from './math';
export * from './strings';
Your app only imports { add } from @company/utils. Without sideEffects, Rollup keeps multiply and capitalize because the barrel file re-exports everything.
Step 1: Declare no side effects
In @company/utils/package.json:
{
"name": "@company/utils",
"type": "module",
"exports": "./src/index.js",
"sideEffects": false
}
"sideEffects": false tells Rollup (and webpack) that importing this package never mutates global scope, writes to disk, or performs I/O. Pure functions only.
Step 2: Verify the pruning
Run two builds and compare sizes:
# Build with tree-shaking (default)
rollup -c --environment NODE_ENV:production
# Build without tree-shaking for comparison
rollup -c --environment NODE_ENV:production --no-treeshake
Then inspect the output:
npx source-map-explorer dist/main.js
# or
gzip-size dist/main.js
With sideEffects: false, the multiply and capitalize functions should disappear from the bundle. The stats.json generated by rollup --config --plugin=@rollup/plugin-json will show "removedExports": ["multiply", "capitalize"] for that module.
When sideEffects Must Be an Array
Some packages contain a mix of pure modules and modules with real side effects (polyfills, CSS imports, global registration). In that case, list the safe entry points:
"sideEffects": [
"./src/polyfill.js",
"*.css"
]
Rollup will still tree-shake the other modules. Glob patterns are supported; paths are relative to package.json.
Trade-offs and Gotchas
- Over-declaring
falsebreaks code that relies on module-level side effects (e.g., a constructor that registers itself in a global registry). The symptom is a runtimeundefinedor missing feature with no build error. - Under-declaring (omitting the field) leaves dead code in the bundle, increasing download and parse time.
- Dynamic imports (
import()) andeval-based execution bypass static analysis. Rollup cannot prove those paths are safe, so it retains the whole module graph reachable from them. - Transitive dependencies without
sideEffectsforce Rollup to keep their entire sub-tree. Upgrading those deps or forking with a correctedpackage.jsonis often the only fix.
Quick Verification Checklist
- Ensure
"type": "module"or.mjsextension so Rollup parses ES syntax. - Set
treeshake: { moduleSideEffects: 'no-external' }inrollup.config.jsto avoid assuming side effects on external deps that do declaresideEffects: false. - Run
rollup -c --plugin=@rollup/plugin-analyzerto see a summary of removed vs. retained exports. - Compare gzipped bundle sizes before/after the
sideEffectschange; expect 10–30% reduction for typical utility libraries.
Closing Action
Audit your package.json files today. Add "sideEffects": false to every pure ESM package you control, and file PRs against dependencies that are missing it. Then run the two-build comparison above—if the numbers don't move, you've found a module that still leaks side effects or a CommonJS dependency blocking the optimization.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.