Modules & Bundlingmedium

Tree Shaking

Tree shaking is dead code elimination based on ES module static analysis. Bundlers remove exports that are never imported, reducing bundle size. It only works with ESM, not CommonJS.

Memory anchor

Tree shaking is shaking a Christmas tree after the holidays — dead ornaments (unused exports) fall off, live ones (used imports) stay attached. Only works if the ornaments are on static hooks (ESM). Tangled wire lights (CJS) mean everything stays.

Expected depth

Tree shaking relies on ESM's static import/export statements — the bundler can determine at build time which exports are used. Side-effectful modules cannot be tree-shaken unless marked 'sideEffects: false' in package.json. Named exports tree-shake better than re-exported namespace objects. Bundlers mark unused exports as 'dead code' then remove them in the minification phase (e.g., Terser). Conditional exports and computed property access on namespaces prevent tree shaking.

Deep — senior internals

True tree shaking requires both static export analysis AND side-effect awareness. A module with side effects (e.g., attaches to window, modifies global state) must be included even if none of its exports are used — sideEffects field in package.json tells bundlers which files are safe to exclude. Rollup pioneered tree shaking; Webpack added it in v2. Scope hoisting (module concatenation in Webpack/Rollup) inlines small modules into the bundle's outer scope, reducing function call overhead and enabling better minification (shorter variable names, more inlining). CSS modules and CSS-in-JS also support tree shaking via the same static analysis principle.

🎤Interview-ready answer

Tree shaking removes unused exports at build time using ESM's static import/export analysis — bundlers know at compile time exactly which exports are used. It requires ESM (not CJS), and libraries must mark side-effect-free files with 'sideEffects: false' in package.json. The practical impact: importing { debounce } from 'lodash-es' includes only debounce; importing from 'lodash' (CJS) includes the entire library.

Common trap

Using barrel files (index.ts that re-exports everything) can break tree shaking if the bundler can't statically resolve which exports are used — especially with namespace re-exports (export * from './utils'). Some bundlers handle this well; others include the entire barrel. Profile your bundle with source-map-explorer or Bundle Analyzer before assuming tree shaking worked.

Related concepts