Performance
Tree shaking: how it really works
Tree shaking drops unused code only when modules are ESM and side-effect free. A test with esbuild: 145 bytes against 59.
Key points
- Tree shaking drops exports nobody imports, not code you actually use.
- It works on ESM modules and on those declared free of side effects.
- In our esbuild 0.21.5 fixture the output drops from 145 to 59 bytes with sideEffects: false.
- A CommonJS module or a polyfill imported for its effect is never dropped.
Tree shaking drops code that is exported and never imported: it works on ESM modules and leaves alone anything the bundler cannot prove free of side effects. In our test the difference is 145 bytes against 59.
The three conditions
| Condition | Why it matters | How it breaks |
|---|---|---|
| ESM module | Exports are static and analysable | A require in the middle of the file |
| sideEffects: false | Allows dropping the whole file | A polyfill imported for its effect |
| Named exports | The bundler knows what is used | One default export holding everything |
When one of the three is missing, the bundler keeps the code to be safe. That is not laziness: a module can register a custom element or touch window as soon as it is imported.
Reproducing the test
npx esbuild entry.js --bundle --minify --format=esm --outfile=out.js && wc -c out.js
The command needs Node and npx, plus an entry.js that imports a single export. Add sideEffects: false to the library package.json and run it again: when the file shrinks, the unused module was kept for its effects.
What stays out
- CommonJS: the bundler cannot drop an export read at runtime.
- Classes with effects in the constructor: the code stays because the bundler does not execute it.
- Import for effect on purpose: stylesheets, polyfills and custom element registration must not be dropped.
The limitation to name
Tree shaking does not shrink the code you really use: when a component imports the entire icon set, the weight stays. The difference between 145 and 59 bytes shows on a module, not on an application; on large projects the gain comes from packages with named exports and from watching what enters the graph.
Where to continue
The EneUI catalogue indexes 111,985 components, 12,808 icons and the record 3D carousel: a component that imports the whole icon set loses nothing at build time. For the single import that grows the bundle read one import that grows the bundle, for render cost the React profiler, and for the measured comparison of packages MUI alternatives 2026.
Frequently asked questions
Are tree shaking and minify the same?
No. Minify shortens names and removes whitespace, tree shaking removes declarations that are never used before minification runs.
Why does my library not lose weight?
Because it is CommonJS or because its package.json does not declare sideEffects: false. In both cases the bundler keeps the code to be safe.
Can sideEffects: false break something?
Yes, when a module imported for its effect gets dropped. In that case list the files with effects in an array instead of using false.
Does tree shaking work on CSS?
No, stylesheets are imports for effect. For CSS you use a plugin that removes unused rules, with partial results.
Where does the gap between 145 and 59 bytes come from?
From a module exported and never imported. With sideEffects: false it disappears; without the field the bundler keeps it because it registers something on window.