Suggest an editImprove this articleRefine the answer for “Tree shaking”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Tree shaking is the analysis of module imports and exports, followed by the removal of everything that is never used.** The bundler takes the entry point, builds a dependency graph and drops whatever is unreachable from that root: a function, class or variable that nobody imports simply never makes it into the bundle. It only works with ES Modules, because `import`/`export` are static and analysable at build time, while a `require()` with a computed path cannot be predicted. Code with side effects is left alone, which is why libraries mark themselves with `"sideEffects": false` in `package.json`. ```javascript // math.js exports add and multiply import { add } from './math.js'; // multiply never reaches the bundle ``` **Key point:** ESM plus no side effects, and the bundler shakes everything unreachable off the dependency tree.Shown above the full answer for quick recall.Answer (EN)Image**Tree shaking is the process of analysing module imports and exports and removing the ones that are not used in the final code.** Put simply: if a function, class or variable is never called and never imported, the bundler just throws it out of the resulting bundle. ## Theory ### TL;DR - The name is a metaphor: the tree is the project's dependency graph, and shaking is knocking the unused branches off it. - The bundler takes the entry points, builds an import graph and removes everything unreachable from those roots. - It only works with ES Modules, because `import`/`export` are static; CommonJS and dynamic `require()` break the analysis. - Code with side effects is not removed, even when its exports are needed nowhere. - The `sideEffects` field in `package.json` tells the bundler it may prune modules more aggressively. - Tooling: Rollup, Webpack (version 2 and newer, in `production` mode), ESBuild, Vite. ### Quick example ```javascript // math.js export function add(a, b) { return a + b; } export function multiply(a, b) { return a * b; } // app.js import { add } from './math.js'; console.log(add(2, 3)); ``` Without tree shaking, both `add` and `multiply` end up in the bundle, even though `multiply` is never used. A modern bundler (Rollup, Webpack 5 and newer, ESBuild, Vite) will see that `multiply` is imported nowhere, so it can be safely removed. What is left in the resulting bundle is only: ```javascript function add(a,b){return a+b} console.log(add(2,3)) ``` ### How the bundler knows what it may remove Tree shaking is dependency graph analysis in three steps: 1. Find the "roots", the modules the application actually uses (entry points). 2. Build the import graph. 3. Remove everything unreachable from those roots. Two neighbouring terms are worth separating. Tree shaking is the logical analysis of imports and exports at the module level. Dead code elimination (DCE) is the follow-up analysis at the expression level, usually done by the minifier (Terser, UglifyJS). The chain looks like this: ```text Webpack / Rollup -> mark the unused exports | v Terser -> physically cuts the code out ``` ### Why ES Modules are required Tree shaking works only with ES Modules (ESM), because they are static: ```javascript // Works: ESM import { add } from './math.js'; // Does not work: CommonJS, build-time analysis is impossible const math = require('./math'); ``` The bundler has to see every import and export at compile time, not at run time. A dynamic `require()` with a variable in the path breaks the optimisation, because there is no way to predict what will actually be imported. ### Side effects and the sideEffects field Tree shaking does not remove code with side effects, that is code that may change global state: ```javascript // utils.js console.log('module loaded'); // a side effect export const a = 1; export const b = 2; ``` Even if you import neither `a` nor `b`, the bundler will not remove the module, because it sees a side effect: a `console.log` call, a change to a global variable and so on. A package can state explicitly that its code has no side effects, and then tree shaking works more aggressively: ```json { "name": "my-lib", "sideEffects": false } ``` or selectively, listing only the files that really do have effects: ```json { "sideEffects": [ "*.css", "./polyfills.js" ] } ``` ### Configuration in bundlers **Webpack.** The `production` mode enables tree shaking together with minification: ```javascript // webpack.config.js module.exports = { mode: 'production', // enables tree shaking and minification optimization: { usedExports: true, // marks the used exports } }; ``` If you look into bundle-analyzer, you will see `/* unused harmony export */` comments, which is tree shaking in action. **Rollup.** It was built around tree shaking from the start, so it does the job best: ```javascript // rollup.config.js export default { input: 'src/index.js', output: { file: 'dist/bundle.js', format: 'esm' } }; ``` Rollup removes not only unused exports but whole chains of functions when nobody calls them. **Vite and ESBuild.** They always work with ESM and support tree shaking out of the box, with nothing to configure. The main thing is to avoid CommonJS and hidden side effects. Summary: | What | Description | | --- | --- | | **Tree shaking** | Removal of unused imports and exports | | **Works only with** | ES Modules (`import` / `export`) | | **Does not remove** | Code with side effects | | **Configuration** | `sideEffects: false` in `package.json`, `mode: production` | | **Tooling** | Webpack ≥ 2, Rollup, ESBuild, Vite | | **Goal** | Shrink the bundle without deleting code by hand | The main idea: the bundler builds a "dependency tree" and shakes off everything that is not used, hence the name tree shaking. ### Common mistakes The most common reasons tree shaking fails to kick in: | Reason | Why | | --- | --- | | CommonJS is used (`require`) | there is no static dependency analysis | | Importing the whole module (`import * as m`) or `require(variable)` | the bundler cannot predict what exactly is imported | | Side effects in modules | they cannot be removed safely | | Plugins or transpilation break ESM | for instance Babel turns `import/export` into `require` | | `mode: 'production'` is not enabled in Webpack | optimisations are off in dev mode | A few more things worth remembering: - **Expecting tree shaking to shrink a dev build.** Bundle size should only ever be measured on a production build. - **Setting `"sideEffects": false` blindly.** If the package has CSS imports, polyfills or global helper registration, the bundler will drop them and the app breaks in production although everything worked locally. - **Confusing tree shaking with code splitting.** The first removes unused code, the second splits the used code into separate chunks; they are different optimisations.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.