Removing Dead Code with JS Tree Shaking Guide
A practical walkthrough for finding and removing dead JavaScript code from your project using tree shaking. Configure your bundler, audit your dependencies, and verify the results.
Removing dead JavaScript code with tree shaking is a practical skill that measurably improves your application's load time. While the tree shaking concept explains how it works, this guide walks through the actual steps: configuring your bundler, auditing your dependencies, and verifying the production output.
Think of this as a checklist. You run through it once, measure the improvement, and then repeat whenever you add new dependencies or notice bundle size creeping up.
Step 1: Switch to Production Mode
Tree shaking only runs in production builds. Development builds prioritize fast rebuilds over optimization, so they include all code. Verify you are building in production mode before measuring results.
For Webpack, set the mode explicitly:
// webpack.config.js
module.exports = {
mode: "production",
};For Vite, run the build command which automatically uses production mode. For Rollup, the default behavior is to tree shake regardless, but production plugins like terser perform additional dead code removal.
Step 2: Add sideEffects to package.json
Open your project's package.json and add the sideEffects field. This is the single most impactful configuration change for tree shaking. Start by setting it to false, which tells the bundler every module in your project is safe to remove if its exports are unused:
{
"name": "my-app",
"sideEffects": false
}Then list any files that must be excluded because they have genuine side effects. CSS files are the most common example:
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.js"
]
}If you are unsure which files have side effects, start with only CSS exclusions and test thoroughly. Add more exclusions as you discover broken functionality.
Step 3: Audit Your Dependencies
Open node_modules and identify the largest libraries. A single monolithic library can dominate your bundle size. Common offenders and their tree-shakeable alternatives:
| Heavy Library | Tree-Shakeable Alternative |
|---|---|
| lodash (full) | lodash-es (ES module version) |
| moment.js | date-fns or luxon |
| axios (full) | ky or native fetch |
| underscore | lodash-es |
For lodash specifically, even importing a single function with CommonJS pulls in the entire library. Switching to the ES module version lets the bundler extract only the functions you actually call:
// Before: imports entire lodash
import _ from "lodash";
_.debounce(fn, 300);
// After: imports only debounce
import { debounce } from "lodash-es";
debounce(fn, 300);Run a bundle analysis after switching and verify the library's contribution shrinks significantly.
Step 4: Replace Barrel Files with Explicit Exports
Barrel files that use wildcard re-exports prevent tree shaking. The bundler cannot determine which exports are used, so it includes everything. Find barrel files in your project -- typically index.js files that aggregate exports from sibling modules -- and convert them to explicit re-exports.
Before, a barrel file that blocks tree shaking:
// utils/index.js - forces all exports into the bundle
export * from "./dates.js";
export * from "./strings.js";
export * from "./numbers.js";After replacing with explicit re-exports, the bundler can trace exactly which functions are imported and can safely remove the rest:
// utils/index.js - bundler can trace usage
export { formatDate, parseDate } from "./dates.js";
export { capitalize, truncate } from "./strings.js";
export { round, clamp } from "./numbers.js";The extra typing is worth the bundle size savings. For large utility folders, this change alone can reduce the final output by a noticeable amount.
Step 5: Verify with Bundle Analysis
After each change, build in production mode and analyze the output. The goal is to confirm that unused modules disappeared and that no new problems appeared. Use your bundler's analysis tool to generate a visualization, then compare it to the baseline from before your changes.
A practical workflow: take a screenshot of the treemap before starting, then another after each major change. The visual difference makes the impact immediately clear and helps catch regressions where a configuration change accidentally includes more code.
Step 6: Test Your Application
Tree shaking can break functionality if it removes code that has side effects. After enabling tree shaking, test every critical path in your application. Pay special attention to third-party integrations, analytics scripts, and polyfills -- these often have side effects that bundlers cannot detect through static analysis.
If a feature breaks, identify which dependency is missing, add it to the sideEffects exclusion list, rebuild, and verify the fix.
For bundle analysis configuration, the advanced code splitting guide includes tooling setup details.
Rune AI
Key Insights
- Enable production mode and set sideEffects in package.json as the first two steps.
- Audit your node_modules for large libraries that might offer tree-shakeable alternatives.
- Replace barrel files that use export * with explicit re-exports.
- Verify results with bundle analysis tools after every change.
- Test functionality after enabling tree shaking to catch missing side effects.
Frequently Asked Questions
How much can tree shaking reduce my bundle size?
Why is my tree shaking not working even with ES modules?
Can tree shaking break my application?
Conclusion
Tree shaking is not a one-time configuration change. It is an ongoing practice of auditing imports, configuring sideEffects correctly, and verifying the production output. Start by running your bundler in production mode with the sideEffects flag enabled, then systematically identify and address the largest remaining dependencies. Each iteration should measurably shrink your bundle.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.