Tree Shaking is a mechanism for removing unused code (dead code elimination) at the application build stage. Tree Shaking analyzes the static structure of ES modules and excludes exported functions, classes, and variables that are not imported anywhere. According to Webpack Documentation, proper Tree Shaking configuration can reduce bundle size by 30–60% without changing application functionality.
Key Takeaways
Tree Shaking is a code optimization technique that excludes unused modules and functions from the final bundle. The term was introduced by the Rollup team in 2015 and metaphorically describes the process: the dependency tree is shaken, and unused branches fall off. Unlike manual optimization, Tree Shaking runs automatically at build time.
Tree Shaking only works with ES modules (ECMAScript Modules), where dependencies are determined statically through import and export. CommonJS (require/module.exports) does not support Tree Shaking because require executes dynamically — the bundler cannot determine in advance which functions are actually used. Modern libraries (Lodash, Moment.js, RxJS) ship ES versions to support Tree Shaking.
Rollup was the first bundler to implement Tree Shaking in 2015. Unlike Webpack, Rollup was designed from the ground up for ES modules and performs more aggressive dead code removal. Rollup analyzes not only individual exports but entire modules: if a module has no side effects and none of its exports are used, Rollup excludes the entire module from the bundle.
Rollup is especially effective for libraries and SDKs where every kilobyte matters. The Vue.js framework uses Rollup for building its production version. React switched to Rollup in 2020. For applications, Webpack is more commonly used due to its richer plugin ecosystem (Hot Module Replacement, code splitting, CSS modules), but for maximum Tree Shaking when building libraries, Rollup remains the industry standard.
Savings from Tree Shaking strongly depend on project architecture. In a React application using the Ant Design library, Tree Shaking can remove up to 70% of UI component code. In a project where all imports are specific and targeted, the savings amount to 5–15%. According to Webpack research, average savings are 30–40% of bundle size.
| Dead Code Type | Example | Tree Shaking Detection |
|---|---|---|
| Unused export | export function unusedHelper() | Yes |
| Unused import | import { unused } from "lib" | Yes |
| Dead condition branch | if (false) { ... } | No (removed by minifier) |
| Uncalled function after DCE | function a(){} a() where a is not called | Partially |
Tree Shaking mechanism is based on a dependency graph that the bundler builds from all import/export statements in the project. In the first stage, the bundler traverses all files from the entry point and collects the module tree. In the second stage, it analyzes which exports from each module are actually imported in other modules.
For each module, Webpack or Rollup marks exports as used or unused. Unused exports are excluded from the bundle. However, the module itself remains in the bundle if at least one of its exports is used. A module can be completely excluded only through the sideEffects flag or if the module contains no side effects at all.
// utils.js — module with functions
export function formatDate(date) {
return date.toISOString().slice(0, 10);
}
export function formatCurrency(amount) {
return "$" + amount.toFixed(2);
}
export function slugify(text) {
return text.toLowerCase().replace(/\s+/g, "-");
}// app.js — entry point
import { formatDate } from "./utils";
const today = formatDate(new Date());
console.log(today);// After Tree Shaking — only formatDate in the bundle
function formatDate(date) {
return date.toISOString().slice(0, 10);
}
const today = formatDate(new Date());
console.log(today);Tree Shaking excluded formatCurrency and slugify from the final bundle because they are not imported in app.js. The size of the utils.js module decreased from 3 functions to 1. If utils.js contains side effects (e.g., global initialization), Tree Shaking cannot remove even unused exports.
Webpack includes built-in Tree Shaking support through the TerserPlugin in production mode. To enable Tree Shaking, two conditions are sufficient: mode is set to production (mode: "production") and modules use ES syntax (import/export). Webpack automatically marks unused exports and passes them to Terser for removal.
Additional usedExports: true configuration in optimization.webpack.config.js enables detailed analysis of export usage within a module. This option determines which exports are actually used and which are only exported (provided). The combination of usedExports and Terser gives maximum dead code removal efficiency.
// webpack.config.js — Tree Shaking configuration
module.exports = {
mode: "production",
entry: "./src/app.js",
output: {
filename: "bundle.js",
},
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules\/(?!(my-lib)\/).*/,
use: {
loader: "babel-loader",
options: {
presets: [
["@babel/preset-env", { modules: false }],
],
},
},
},
],
},
};The key parameter is modules: false in @babel/preset-env. By default, Babel transforms ES modules into CommonJS, which kills Tree Shaking. modules: false prevents Babel from transforming import/export, preserving ES syntax for Webpack. concatenateModules additionally merges modules into a shared scope, reducing the number of IIFEs and decreasing bundle size.
Side effects are actions a module performs when imported that are not related to exported values: global styles (import "./styles.css"), polyfills (import "core-js/stable"), global variable initialization, or Service Worker registration. If a module contains side effects, the bundler cannot safely remove it from the bundle even if none of its exports are used.
The sideEffects flag in package.json tells the bundler which modules in the package have no side effects. For a package where all modules are pure (only function exports), you should specify "sideEffects": false. For packages with CSS or polyfills — an array of paths to files with side effects: "sideEffects": ["*.css"]. Without this flag, Tree Shaking will not remove even unused functions.
To check whether a module has side effects, ask this question: will this import perform any actions not related to exporting values? import "./styles.css" adds CSS to the DOM — that is a side effect. import { throttle } from "lodash-es" has no side effects — it only makes the throttle function available. Polyfills (import "core-js/stable") have side effects — they modify global prototypes.
For your own modules, it is recommended to: extract styles and polyfills into separate entry points, separate pure utilities (functions without side effects) from modules with side effects (initialization, logging, Service Worker registration). In the top-level project package.json, specify "sideEffects": false only if all modules are pure. If there are styles, specify "sideEffects": ["*.css"] precisely.
{
"name": "my-ui-lib",
"version": "2.1.0",
"sideEffects": [
"*.css",
"polyfills.js"
],
"module": "dist/index.esm.js",
"main": "dist/index.cjs.js"
}"sideEffects": ["*.css", "polyfills.js"] means: all CSS files have side effects (they cannot be removed) and polyfills.js does too. All other JS files in the package are pure — they can be safely shaken. The module field specifies the path to the ES version of the package that the bundler should use instead of the CommonJS version (main) for Tree Shaking.
React Native with Metro Bundler supports a limited version of Tree Shaking. Metro does not perform full static analysis of used exports (usedExports) like Webpack. Instead, Metro relies on Terser to remove unused parts of modules during minification. The effectiveness of this approach is lower than full Tree Shaking in Webpack.
For maximum optimization of React Native projects, it is recommended to: use libraries with ES modules (module field in package.json), add babel-plugin-transform-remove-console for removing debug code, and configure Metro transformer.minifierConfig for Terser. Additionally, Ram Bundle (splitting the bundle into modules) reduces loading of unused screens.
Frequently Asked Questions
CommonJS (require/module.exports) does not support static analysis — require can be called dynamically inside conditions and functions. The bundler cannot determine which parts of the module are actually used. Only ES modules with static import/export enable Tree Shaking.
TypeScript is fully compatible with Tree Shaking provided that tsconfig.json is configured for ES modules: "module": "esnext". The TypeScript compiler must preserve import/export without converting them to CommonJS. Babel with @babel/preset-typescript and modules: false also correctly passes ES modules to Webpack.
Webpack Bundle Analyzer is a plugin that visualizes the bundle composition as an interactive diagram. If a library is present in the bundle but its functions are not used, Tree Shaking did not work. You can also analyze the output file: find unused exports in the bundle text via grep.
Lodash v4 is distributed as a CommonJS package. For Tree Shaking, you need to use lodash-es — the ES version of the library. Replace import throttle from "lodash/throttle" with import { throttle } from "lodash-es" and configure resolve.alias in Webpack to replace lodash with lodash-es.
Tree Shaking slightly increases build time (by 5–15%) because it adds a dependency graph analysis and used export marking stage. In development mode, Tree Shaking is usually disabled for speed. In production, the additional time is justified by a significant reduction in bundle size.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also