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

ConditionWhy it mattersHow it breaks
ESM moduleExports are static and analysableA require in the middle of the file
sideEffects: falseAllows dropping the whole fileA polyfill imported for its effect
Named exportsThe bundler knows what is usedOne 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.