You import a single function from a library that offers two hundred. The other hundred and ninety-nine have no reason to travel to the visitor's browser.
Tree shaking exists for exactly that: shake the dependency tree and let the dead branches fall.
Definition
Tree shaking is the removal, at build time, of exported code no file imports. A Bundler builds the import graph, marks what is actually reached, then writes the final file without the rest.
// utils.js
export function formatPrice(value) {
return value.toFixed(2) + " EUR";
}
export function generateInvoicePdf(order) {
// 40 KB of dependencies pulled in here
return order;
}
// main.js
import { formatPrice } from './utils.js';
console.log(formatPrice(12.5)); // 12.50 EUR
// In the produced file: generateInvoicePdf is gone,
// and its 40 KB of dependencies with it.The win rarely comes from your own code. It comes from dependencies, where a badly aimed import drags a whole library along for three useful lines.
Why it only works with ES modules
A ES module declares its imports and exports in a fixed way, at the top of the file, with no conditions. The tool can therefore decide what is used before anything runs.
With CommonJS, require() is an ordinary call whose path can be computed and whose result can be modified along the way. No reliable analysis is possible, so nothing is removed. It is the strongest technical argument for ES modules in new code.
An import of the form import * as everything from './utils.js' followed by a dynamic property access makes analysis impossible: the bundler cannot know which property will be read, so it keeps all of them.
What blocks the pruning
- Side effects: a module that modifies a global object or installs a listener while loading cannot safely be removed, even when none of its exports are used.
- Doubt: when unsure, a bundler keeps. Its priority is breaking nothing, not saving a kilobyte.
- A missing declaration: the
"sideEffects": falsefield of a package.json announces that a package can be pruned safely. Without it, the tool stays cautious.
Frequently asked questions
Do I have to turn tree shaking on?
It is already on in the production configuration of the usual tools. The real question is which conditions must be met for it to bite: ES modules, named imports, and dependencies published in an analyzable format.
How do I check that it worked?
By looking at the bundle composition report every bundler can produce. It shows the real weight of each module in the final file. Searching the minified file for a function name proves nothing, since internal names have changed.
Why does a library stay whole in my bundle?
Three main causes: it is published in CommonJS only, it is imported wholesale rather than through named exports, or it runs code while loading. Many packages offer an ES module build: that is the one to aim for.