Performanceadvanced
Bundle Size Optimization and Tree Shaking
Techniques bundlers use to eliminate unused code from a final JavaScript bundle, relying heavily on ES modules' static import/export structure.
Tree shaking removes exports nothing imports. It depends on ES modules being statically analysable.
Tree shaking is like a moving company that, instead of packing an entire warehouse's full inventory onto the truck because you rented space there, only loads the specific labeled boxes you've actually indicated you need at your new place, based on a complete, itemized list you provided upfront (the static import graph) — nothing gets shipped that wasn't explicitly itemized as needed.
Key Concepts
1
// utils.js
export function used() {}
export function unused() {}2
// main.js
import { used } from './utils.js';
// unused is not in the bundle3
Why CommonJS cannot be shaken.
const { used } = require('./utils'); // resolved at runtime4
require can be conditional, computed, or wrapped. The bundler cannot prove what is needed, so it includes
everything.
require
5
That is the practical argument for ES modules, beyond syntax.
6
Side effects block it. A module that does something at import time cannot be removed even if nothing
uses its exports:
7
// analytics.js
setupTracking(); // a side effect8
"sideEffects": false // in package.json -- I have none
"sideEffects": ["*.css"] // except these9
Marking this correctly is what lets a library be shaken properly, and getting it wrong is why some libraries
ship entirely regardless of what you import.
10
Import shape matters.
import _ from 'lodash'; // the whole library
import { debounce } from 'lodash'; // often still the whole
import debounce from 'lodash/debounce'; // just this
import { debounce } from 'lodash-es'; // shakeable11
lodash is CommonJS; lodash-es is modules. The same library, with very different results.
lodashlodash-es
12
Measure before optimising.
webpack-bundle-analyzer
rollup-plugin-visualizer
source-map-explorer13
The output almost always shows one or two dependencies dominating, and the fix is replacing or splitting
those rather than trimming application code.
14
The usual large offenders. A full date library where a few functions would do, an icon set imported
wholesale, a charting library used for one chart, and a polyfill bundle for browsers you no longer support.
15
What else reduces the download.
minification standard
compression Brotli over gzip
modern syntax do not transpile for browsers you do not support
code splitting covered separately16
And the number worth tracking. Bundle size in CI, with a budget that fails the build. Without it, size
grows a little with every feature and nobody notices until the page is slow.
17
What the interviewer is probing.1. "What does tree shaking require?" Probing: the precondition. Stalls: "A modern bundler."
Moves up: ES modules with static imports, so unused exports can be proven unreachable — CommonJS
resolves at runtime and cannot be shaken.
18
2. "What is a side effect in this context?" Probing: the blocker. Stalls: "A bug." *Moves
up:* module-level code that runs on import, such as a polyfill; without sideEffects: false the
bundler must keep it.
19
3. "How do you find what is making a bundle big?" Probing: the method. Stalls: "Guess."
Moves up: a bundle analyser on the build stats — the answer is almost always one or two
dependencies.
20
4. "What import style defeats tree shaking?" Probing: the habit. Stalls: "None." Moves up:
importing a whole namespace or a CommonJS build, which pulls everything in regardless of use.