How Tree Shaking Works in JavaScript Bundlers

Tree shaking is a dead-code elimination technique used by modern JavaScript bundlers to remove unused functions, objects, and variables from the final production bundle. By leveraging the static structure of ES modules (import and export), bundlers analyze dependency graphs at build time, trace which exports are actively consumed, and discard everything else. This article explains the technical mechanics behind tree shaking, including static analysis, dependency graph traversal, side-effect detection, and final code elimination.

The Foundation: Static ES Modules vs. Dynamic CommonJS

Tree shaking relies entirely on ECMAScript Modules (ESM). Unlike CommonJS modules, which use dynamic require() statements that can be executed conditionally inside functions or loops, ESM statements (import and export) are completely static:

Because the module structure cannot change at runtime, bundlers like Webpack, Rollup, Vite, and esbuild can reliably build a static Abstract Syntax Tree (AST) representing every relationship between files.

Step-by-Step: How Bundlers Eliminate Unused Code

1. AST Parsing and Dependency Graph Construction

The bundler starts at the application’s entry point (e.g., index.js) and parses every imported file into an Abstract Syntax Tree (AST). It maps out every export provided by a module and every import requested by consumer modules, generating a comprehensive dependency graph.

2. Export Usage Marking

Once the graph is constructed, the bundler traverses it to mark which exports are actively referenced.

3. Handling Side Effects

Code with “side effects” executes behavior outside its own local scope when imported, such as modifying global prototypes, configuring window event listeners, or executing top-level functions.

If a file has side effects, a bundler cannot safely delete unused exports without risking runtime breakages. Bundlers manage this through:

4. Dead Code Elimination (DCE) via Minification

Modern bundlers typically perform tree shaking in two stages: 1. The Bundler Stage: Marks unused exports and strips the export keyword from them, effectively turning them into unused local variables. 2. The Minifier Stage: Tools like Terser, esbuild, or SWC run Dead Code Elimination algorithms over the transformed code, physically deleting any unreferenced variables, uncalled functions, and unreachable branches from the output file.

Writing Tree-Shakable JavaScript

To ensure bundlers can effectively tree-shake your code: