Tree Shaking vs Minification: Reducing JavaScript Bundle Size
What Is Tree Shaking?
Tree shaking is a dead code elimination technique that removes exports your application never uses. The name comes from the mental image of shaking a tree and letting the dead leaves fall off.
Unlike minification — which shortens code but keeps all of it — tree shaking actually deletes unused functions, classes, and variables from your bundle. The two techniques work together: tree shaking removes dead code, then minification compresses what remains.
Why ES Modules Matter
Tree shaking depends on ES module syntax (import and export). The reason is static analysis.
// ES modules — statically analyzable
import { debounce } from 'lodash-es';
export function formatDate(date) { ... }
// CommonJS — dynamic and unpredictable
const { debounce } = require('lodash');
module.exports = { formatDate: function(date) { ... } };
ES module imports are resolved at compile time. The bundler can see exactly which exports a module provides and which ones your code imports. If you import debounce but not throttle, the bundler knows throttle is safe to drop.
CommonJS require() calls are dynamic — the argument can be a variable, a conditional, or a computed string. The bundler cannot reliably determine what gets used, so it keeps everything.
Rule: If you want tree shaking to work, use import/export throughout your codebase.
How Tree Shaking Works
The process follows three steps:
- Build the dependency graph — The bundler starts at your entry point and follows every
importstatement to build a complete map of modules and their exports. - Mark used exports — For each module, the bundler checks which exports are actually referenced by other modules in the graph. Unreferenced exports get marked as dead.
- Eliminate dead code — The bundler removes the marked exports from the output. A minifier then cleans up any orphaned code left behind.
// utils.js — three exports
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
export function divide(a, b) { return a / b; }
// app.js — only one import
import { add } from './utils.js';
console.log(add(2, 3));
The bundler includes add and removes multiply and divide — they are never imported.
Tree Shaking vs Dead Code Elimination
These terms overlap but are not identical:
| Tree Shaking | Dead Code Elimination | |
|---|---|---|
| Scope | Module-level exports | Any unreachable code |
| Mechanism | Unused export removal | Unreachable branch removal |
| Example | Removing an exported function you never import | Removing code inside if (false) { ... } |
Tree shaking operates on the module graph. Dead code elimination operates within individual functions and blocks. Both reduce size, and modern bundlers apply both automatically.
Side Effects and Package.json
Some modules execute code simply by being imported — even if you never use their exports. These are side effects.
// side-effect.js
window.myGlobal = 'hello';
Importing this file changes global state. A bundler must keep it, even if no export is used.
To help bundlers optimize correctly, library authors add a sideEffects field to package.json:
{
"name": "my-library",
"sideEffects": false
}
This declaration tells the bundler: "No file in this package has side effects. Drop any unused exports freely."
If only certain files have side effects:
{
"sideEffects": ["./src/polyfills.js", "./src/global-setup.js"]
}
For consumers: Check whether your dependencies declare sideEffects: false. Libraries like lodash-es, date-fns, and ramda do — and that is why they tree-shake well.
Bundler Configuration
Webpack
// webpack.config.js
module.exports = {
mode: 'production', // enables tree shaking + Terser
optimization: {
usedExports: true, // mark unused exports
minimize: true, // run Terser
concatenateModules: true, // scope hoisting
},
};
Webpack's tree shaking activates in production mode by default. The usedExports flag tells Webpack to track which exports are used and drop the rest during minification.
Rollup
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'esm',
},
treeshake: {
moduleSideEffects: false, // assume no side effects
},
};
Rollup was built for ES modules and tree shakes by default. It often produces smaller bundles than Webpack for library code because its scope hoisting is more aggressive.
Vite
// vite.config.js
export default {
build: {
rollupOptions: {
treeshake: true, // default in production
},
},
};
Vite uses Rollup for production builds, so tree shaking works out of the box. No extra configuration needed for most projects.
Tree Shaking and Minification Together
These two techniques are complementary, not competing:
- Tree shaking runs first — it removes entire unused modules and exports
- Minification runs second — it compresses the remaining code by shortening names and removing whitespace
The order matters. If you minify first, variable names get shorter but the dead code is still there. Tree shaking then removes the dead code, but the minifier has already wasted effort on code that gets deleted.
In practice, you do not control this order — your bundler does. Just make sure both are enabled in production mode.
Common Pitfalls
- Importing entire libraries —
import _ from 'lodash'imports everything and defeats tree shaking. Useimport { debounce } from 'lodash-es'instead - Re-exporting everything —
export * from './module'forces the bundler to keep all exports because it cannot know which ones downstream consumers need - Dynamic imports —
import('./module')creates a separate chunk; the bundler cannot tree-shake across dynamic boundaries - CommonJS dependencies — If a dependency uses
module.exports, the bundler may conservatively include the entire module - Missing sideEffects field — Without
sideEffects: falseinpackage.json, bundlers must assume every file might have side effects
Key Takeaways
- Tree shaking removes unused ES module exports; minification compresses what remains
- Static
import/exportsyntax is required — CommonJS defeats tree shaking - Declare
sideEffects: falsein your package.json to help bundlers optimize aggressively - Webpack, Rollup, and Vite all support tree shaking in production mode by default
- Import only what you need: named imports over namespace imports
Try It Yourself
Test the impact of tree shaking by minifying your JavaScript with our free JS Minifier. See how much smaller your code becomes when unused functions are stripped before compression.
Related Guides
- JavaScript Minification Guide — How whitespace removal and variable mangling compress your code
- JavaScript Performance Optimization — Loading, parsing, and execution strategies for faster pages
- CSS Minification Guide — Apply the same dead-code and compression principles to stylesheets