How Tree Shaking Removes Unused JavaScript Exports

Tree shaking is a build-time dead-code elimination technique that removes unreferenced JavaScript exports from final production bundles. By leveraging the static structure of ECMAScript Modules (ESM), modern bundlers like Webpack, Rollup, and esbuild trace the flow of imported and exported identifiers across a codebase. This allows them to construct an exact dependency graph, determine which exports are unreachable from the application entry point, evaluate potential side effects, and discard unused code to minimize file size.

Static Structure: ESM vs. CommonJS

Tree shaking relies entirely on the static syntax of ES modules (import and export). Unlike CommonJS, where require() calls can be executed conditionally, dynamically constructed, or placed inside runtime functions, ESM statements must sit at the top-level scope and cannot change at runtime.

Because imports and exports are completely deterministic, a bundler does not need to execute the JavaScript to understand the dependency relationships. It can inspect the code purely through static analysis before compilation.

Abstract Syntax Tree (AST) Parsing

When a bundler processes an ES module, it parses the source code into an Abstract Syntax Tree (AST). The AST provides a structured representation of the code’s syntax:

  1. Identifying Declarations: The parser scans for nodes such as ExportNamedDeclaration, ExportDefaultDeclaration, and ImportDeclaration.
  2. Cataloging Identifiers: Each exported variable, function, or class is registered with its identifier name and associated scope.
  3. Tracking Usage: Import statements explicitly declare which identifiers are pulled from dependent modules, mapping precise connections between files.

Constructing the Dependency Graph

Using the parsed ASTs, the bundler builds a global module graph starting at the application entry point:

Because ESM creates “live bindings” (read-only references to the exported values rather than copies of values), the bundler can guarantee that an unflagged export is safe to isolate from the downstream consumer.

Side-Effect Analysis

An unimported export might still execute code if the module contains top-level side effects (such as modifying global objects, attaching event listeners, or executing functions during file evaluation).

Bundlers analyze module code for purity: - Automatic Detection: Bundlers inspect whether unreferenced code performs actions with observable side effects. If an unreferenced function or variable is pure, it can be dropped. - Package Hints: Developers use the "sideEffects": false flag (or an array of specific file paths) in package.json to explicitly tell bundlers that unimported exports can be dropped without executing the module’s top-level code.

Final Dead Code Elimination

Once the graph is marked, the bundler strips unreferenced export statements from the AST. If an entire module’s exports are unreferenced and the module contains no side effects, the entire module is excluded from the compilation.

Finally, a minifier (such as Terser or esbuild) performs a second pass to remove any leftover local declarations or unreferenced variables within functions, producing a lean, production-ready bundle.