ESC

Type to search the knowledge base.

Tree Shaking Basics

Dead-code elimination for ES modules: sideEffects, named imports, and why some libraries won’t shake.

intermediate3 min read
  • performance
  • tree-shaking
  • bundling
  • esm
  • javascript

Tree shaking removes unused exports from the production bundle when the bundler can prove code is dead. It relies on static ES module structure. CommonJS and side-effecty modules shake poorly.

Docs: webpack tree shaking, Rollup, web.dev reduce JS.

What works

// library
export function used() {}
export function unused() {}

// app
import { used } from 'lib';
used();
// unused() dropped in production if lib is ESM and pure

Conditions:

  1. ESM import/export (not dynamic CJS require graphs).
  2. Bundler production mode + minify.
  3. Module marked free of surprising side effects (or side effects are known).

package.json sideEffects

{
  "name": "my-lib",
  "sideEffects": false
}

Or list CSS/polyfill files that must be kept:

"sideEffects": ["*.css", "./src/polyfills.js"]

Wrong sideEffects: false can drop needed CSS imports or polyfills.

Named imports vs barrels

// may pull large index that re-exports everything depending on tooling
import { Button } from 'huge-ui';

// often better when library supports it
import Button from 'huge-ui/Button';

Deep imports help when the package entry is a non-shaking barrel. Verify with a bundle analyzer.

Libraries that resist shaking

  • Moment locales (import specific locales)
  • Lodash full package (lodash vs lodash-es / per-method packages)
  • Modules that mutate globals at import time
import debounce from 'lodash-es/debounce';

Dynamic import is not tree shaking

Code splitting loads a chunk on demand; tree shaking removes dead code from a chunk. You need both — code splitting.

Verify

  1. Analyzer: is the dead module gone?
  2. Compare production build sizes.
  3. Don’t judge by dev builds (often unshaken for DX).

Interview out-loud

“Tree shaking drops unused ESM exports when modules are side-effect free. I use named/ESM builds, configure sideEffects, avoid CJS barrels, and confirm with a bundle analyzer — not by reading node_modules hope.”

How this shows up in interviews

Be ready to define the metric or technique in one sentence, name one measurement approach (DevTools panel, web-vitals, or headers), and cite a concrete fix you would try first. Walk through a before/after: what the waterfall or flame chart showed, what you changed, and which percentile moved. Mention a tradeoff (complexity, caching correctness, or third-party business constraints) so the answer doesn’t sound like a blog checklist.

Production guardrails

Ship behind a flag when the change is risky, watch field p75 for the affected template for at least a few days, and keep a rollback path. Pair lab verification (throttled Performance/Network) with RUM so you don’t celebrate a Lighthouse-only win. Document the owner of any ongoing budget or third-party exception.

Further depth

Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.

Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.

Further reading

Related guides