Articles

What Is Tree Shaking? JavaScript Dead-Code Elimination

Tree shaking removes unused exports from a JavaScript bundle at build time, shrinking file size by relying on ES module static structure.

Takina Takina · · 4 min read
Laptop screen showing JavaScript code in blue light

Tree shaking is a build-time optimization that removes code from a JavaScript bundle if it’s never actually used, based on which exports a module imports and calls. The name comes from the mental image of shaking a module dependency tree so that dead branches — unused exports — fall away, leaving only the code your app actually reaches.

Why bundlers need it

Modern apps pull in libraries that export far more than any single app uses. A utility library might export two hundred functions; a typical app imports five. Without tree shaking, bundling that library means shipping all two hundred functions to the browser, even though 195 of them are never called. Tree shaking lets the bundler ship only the five.

This matters directly for Core Web Vitals: a smaller JavaScript bundle means less code to download, parse, and execute before the page becomes interactive.

Why it depends on ES modules

Tree shaking works reliably with ES modules (import/export) but not with CommonJS (require/module.exports), and the reason is structural, not arbitrary.

ES module imports and exports are static — they’re declared at the top level of a file, with names that are known before any code runs. A bundler like Rollup, esbuild, or webpack can read the whole module graph, see exactly which named exports are imported, and trace whether each one is used before deciding it’s dead.

CommonJS imports are dynamicrequire() is a function call that can happen conditionally, inside a loop, or with a computed path. module.exports can be reassigned at runtime. A bundler can’t safely prove a given export is unused when the require pattern isn’t fixed until the code actually runs, so it has to keep the whole module.

This is one of the practical reasons libraries publish both a CommonJS build (for older Node tooling) and an ES module build (for tree-shakeable bundling) — check a package’s "module" or "exports" field to see which one your bundler picks up.

What actually gets shaken

Tree shaking removes:

  • Unused named exports from a module you only partially import (import { format } from 'date-lib' drops everything else the library exports).
  • Code paths that are provably unreachable, like an if (false) block after a build-time constant is inlined.
  • Entire dependencies, if none of their exports are ever imported anywhere in the app.

It generally does not remove:

  • Code with side effects the bundler can’t prove are safe to drop — a module that runs window.foo = 'bar' at the top level has to stay, because removing it changes behavior even if nothing imports from it.
  • Default exports of large objects, since importing one property off a default-exported object doesn’t tell the bundler which of the object’s other properties are unused.

Because of the side-effect caveat, many packages declare "sideEffects": false in their package.json to tell bundlers it’s safe to drop unused parts of the package entirely — a signal worth checking if a dependency isn’t shrinking the way you’d expect.

Tree shaking in practice

A few patterns make a real difference in bundle size:

  • Prefer named imports over namespace imports. import { debounce } from 'lodash-es' is shakeable; import _ from 'lodash' (the CommonJS build) pulls in the whole library. See debounce vs throttle for what that particular utility does.
  • Avoid re-export barrels that break static analysis. A single index.js that does export * from './a'; export * from './b' can still be shaken by good bundlers, but deeply nested barrel files sometimes defeat the analysis and pull in more than expected.
  • Check your bundler’s output. Tools like Vite (built on Rollup and esbuild) and webpack both offer bundle analyzer plugins that visualize what actually made it into the final file — the only reliable way to confirm tree shaking is working as expected.

Tree shaking vs minification

These are often confused because they both shrink output, but they operate at different levels:

Tree shakingMinification
RemovesUnused exports/modulesRenames variables, strips whitespace/comments
Operates onThe module dependency graphIndividual files, post-bundling
Depends onStatic ES module structureNothing structural — works on any valid JS

Both run in the same build pipeline, usually tree shaking first (to decide what code exists) and minification last (to compress what’s left).

The takeaway

Tree shaking is a build step that walks the static import/export graph of your ES modules and discards anything unreachable, which is why it requires ES modules rather than CommonJS to work reliably. The practical payoff is a smaller bundle shipped to the browser — check your bundler’s "sideEffects" handling and prefer named imports from ES-module builds if you want tree shaking to actually earn its keep.

Takina Takina · · 4 min read

requestIdleCallback Explained

requestIdleCallback runs low-priority JavaScript when the browser is idle, without blocking rendering, input, or the main thread.

#JavaScript #Performance #Web Development
Takina Takina · · 5 min read

Finding and Fixing Memory Leaks in JavaScript

A JavaScript memory leak happens when a reference outlives its usefulness and the garbage collector can't reclaim it. Common causes and how to find them.

#JavaScript #Web Development #Performance