Frontend Performance/Shipping Less JavaScript
Lesson 17 of 20 · Episode 17

Bundle Analyzer

Open up the bundle as a treemap, find the modules eating your budget, and decide what to cut or split.

Bundle analysisTreemapBudgets
Watch on YouTube ↗

Tree shaking and splitting only help if you know what's actually in your bundle. A bundle analyzer turns the build into a picture — a treemap where every box is a module sized by its weight — so the bloat you never suspected becomes impossible to miss. This is how a 142 KB bundle becomes 61 KB without removing a single feature.

The tool

Seeing inside the build

Your bundler already emits a stats file describing every module it included and how big each is. A bundle analyzer reads that and draws an interactive treemap. Because the step is bundler-specific, each has its own: webpack-bundle-analyzer, Vite's rollup-plugin-visualizer, and so on — same idea everywhere.

It exists to answer the questions you ask during a performance audit:

See it

Explore a real bundle

Here's a tiny To-Do app that imported moment (for a date) and lodash (for one sum). The treemap shows the truth: your code is a sliver, and the dependencies dwarf it. Click boxes to inspect them, then hit Apply fixes and watch the bloat collapse:

InteractiveA bundle treemap (box size = weight)
Total (gzip):142 KB
moment locales · moment.js
Every language locale moment ships by default — you only need English. IgnorePlugin removes all of this.
moment ships every locale; lodash came in whole for one function. Each box is sized by its real cost — the fixes shrink them dramatically.
Read the right number

Stat vs parsed vs gzipped

Analyzers report three sizes, and confusing them leads to wrong conclusions:

Tip
For download time, look at gzipped. For main-thread/parse cost, look at parsed. Toggle the formats in the demo — the same module can look very different depending on which you read.
The usual offenders

The two classic culprits

moment.js shipping every locale

moment bundles all its language locales by default — often more weight than the library itself, for translations you'll never use. Strip them (or switch to a lighter date lib):

drop moment locales with Webpack
const webpack = require("webpack");
plugins: [
  // Don't bundle any moment locale files
  new webpack.IgnorePlugin({ resourceRegExp: /./locale$/, contextRegExp: /moment$/ }),
];

lodash imported whole

Plain lodash isn't tree-shakable, so import _ from "lodash" drags in everything for one function. Import the single function, or use the ESM build:

import only what you use
// ❌ pulls in all of lodash (~24 KB gzipped)
import _ from "lodash";
_.sum(nums);

// ✓ just the one function (~0.2 KB)
import sum from "lodash/sum";
// …or the tree-shakable ESM build:
import { sum } from "lodash-es";
Key idea
These two one-line changes took the demo bundle from 142 KB → 61 KB gzipped — moment 74→18, lodash 24→0.2 — with the app behaving identically. That's the whole point: the analyzer tells you where to look.
Q1Multiple choice
Which size best represents the real download cost of a bundle?
Q2Multiple choice
The treemap shows moment.js is huge. The most likely reason?
Q3Multiple choice
You used one lodash function but the whole library is in the bundle. Best fix?
Key takeaways
  • A bundle analyzer visualizes the build as a treemap — box size = module weight.
  • It answers the audit questions: why is it big, my code vs deps, did tree-shaking work, anything duplicated.
  • Read gzipped for download cost, parsed for parse/CPU cost.
  • Classic culprits: moment locales and whole-lodash imports.
  • Targeted fixes from one look can halve a bundle with zero feature loss.
← Previous
16. Tree Shaking
Next →
18. BundlePhobia