ShopperCove
Menu
All writingBlogTopicsCategoriesAboutRSS
Blog
Categories
Observability & SRE62All categories
About

Plate 89

  1. Blog

Is webpack-bundle-analyzer Accurate? A 2.3x Size Gap

webpack-bundle-analyzer 5.4.0 vs source-map-explorer 2.5.3 on one webpack 5.111 production build (148,569 B): date-fns 44,313 B vs 19,196 B (2.3x) because of module concatenation estimates; lodash 70,136 vs 70,312 B. CLI medians 313 ms vs 224 ms (JSON).

Aditya Challa·5 October 2026·5 min read

Hands-on
On this page
  1. What I tested
  2. Why do the two tools report different sizes?
  3. Can you make webpack-bundle-analyzer accurate?
  4. Which bundle analyzer should you use?
  5. Sources
  6. Related

I ran webpack-bundle-analyzer 5.4.0 and source-map-explorer 2.5.3 on the same webpack 5.111 production build, and they disagreed on date-fns by 2.3x: 44,313 bytes in the analyzer versus 19,196 bytes in source-map-explorer. Lodash matched within 176 bytes, so the gap was not random; it came from webpack's module concatenation, which forces the analyzer to estimate sizes inside the merged module.

Short answer: use source-map-explorer when you need to know which package actually owns the bytes in a minified, scope-hoisted bundle. Keep webpack-bundle-analyzer for chunk-level views, gzip numbers and quick treemaps, and read its numbers inside a "(concatenated)" box as estimates. Speed is not the deciding factor here: source-map-explorer took a median 224 ms for JSON output and the analyzer CLI took 313 ms. No affiliate links in this post.

What I tested

  • Machine: 8 vCPU Intel Xeon / 15 GB RAM Linux cloud box, Node 20.19.2, tested 5 Oct 2026 (about 23:36 to 23:42 IST). Shared box; runs interleaved after one warmup each.
  • Fixture: /workspace/bench-sme-wba/ with one entry, 120 small feature modules that each import format and addDays from date-fns 4.4.0, plus a default lodash 4.18.1 import. webpack 5.111.1 + webpack-cli 7.2.3, mode: 'production', devtool: 'source-map'. Output: main.js 148,569 B and main.js.map 930,179 B; stats.json 3.78 MB.
  • Tools: source-map-explorer 2.5.3 reading main.js + its map; webpack-bundle-analyzer 5.4.0 reading stats.json + dist/, both as CLIs. I also built once with BundleAnalyzerPlugin in static mode.
  • Timing: seven interleaved CLI wall clocks per variant via process.hrtime around the full command; five interleaved webpack builds per build variant.
  • Findings: source-map-explorer JSON median 224.3 ms, HTML 243.8 ms. webpack-bundle-analyzer JSON median 312.6 ms, static HTML 327.3 ms. Build medians: plain 3.49 s, --json stats 3.13 s, with plugin 3.34 s (all inside run-to-run noise on this box).
  • One surprise: source-map-explorer 2.5.3 exited 1 on its default run ("generated column Infinity on line 2"). It only worked with --no-border-checks.
  • Not tested: Vite or Rollup output, multi-chunk apps with dynamic imports, devtool variants other than source-map, the analyzer's server mode, Windows/macOS.

Why do the two tools report different sizes?

Module groupsource-map-explorer bytesAnalyzer parsed bytesGap
lodash70,31270,136about equal
date-fns (37 files)19,19644,313analyzer 2.3x higher
my src (121 files)58,25833,840analyzer 42% lower
whole bundle148,569148,569same total

Lodash is a single CommonJS module, so webpack kept it as its own module and both tools saw the same bytes. Date-fns and my source files were scope-hoisted into one module the analyzer labels "index.js + 157 modules (concatenated)". Its JSON report marks all 158 inner entries with inaccurateSizes: true. The numbers fit a proportional split: date-fns is 102,305 of the module's 180,547 source bytes, and 57% of the 78,237 minified bytes is about 44,330, within 20 bytes of what the analyzer showed. Date-fns source is verbose (JSDoc, long names), so it got credit for bytes that minification had already removed. source-map-explorer walks the source map byte by byte, so it does not need to guess.

For the bundler side of this, see the Rspack vs webpack post and the webpack 5.111 ESM output post.

Can you make webpack-bundle-analyzer accurate?

Yes, at a cost. I rebuilt with optimization.concatenateModules: false. The analyzer then reported date-fns at 22,174 bytes and flagged zero inaccurate entries, close to source-map-explorer's 19,966 bytes for the same build. But the bundle grew from 148,569 to 163,525 bytes (+10%), because scope hoisting is a real size win. Do this in a throwaway "analyze" build, not in the build you ship.

source-map-explorer also has a --gzip flag. With it (plus --no-border-checks), it reported a 31,346-byte gzip total, the same total the analyzer showed, and put date-fns at 9,008 gzip bytes.

Which bundle analyzer should you use?

SituationMy pick
"Which npm package is making my bundle big?" in a production webpack buildsource-map-explorer
You need chunk-level treemaps, gzip sizes per chunk, or a CI HTML artifact from the webpack buildwebpack-bundle-analyzer (plugin or CLI)
Your bundler is not webpack (Vite, Rollup, esbuild) and emits source mapssource-map-explorer (analyzer needs webpack stats)
You want exact per-module numbers from the analyzerSeparate analyze build with concatenateModules: false
No source maps in production and you will not turn them onwebpack-bundle-analyzer

Bottom line: on one webpack 5 build, the two tools agreed on the total and on lodash, but date-fns was 44 KB in one and 19 KB in the other. Who should not bother with source-map-explorer: teams whose big wins are whole chunks or duplicate vendor copies, which the analyzer shows well. To shrink what these tools find, the terser vs esbuild/swc minify post and the knip vs depcheck post cover minifiers and unused dependencies; for a CI byte budget, see size-limit vs bundlesize.

How this was made: I generated the fixture, built it with webpack, ran both analyzers on the ShopperCove test box, timed interleaved runs and kept the JSON; the write-up was drafted with AI help and checked against that output.

Sources

  • https://github.com/danvk/source-map-explorer
  • https://www.npmjs.com/package/source-map-explorer
  • https://github.com/webpack/webpack-bundle-analyzer
  • https://www.npmjs.com/package/webpack-bundle-analyzer
  • https://webpack.js.org/configuration/optimization/#optimizationconcatenatemodules
  • https://webpack.js.org/plugins/module-concatenation-plugin/

Related

  • https://www.shoppercove.com/blog/rspack-vs-webpack
  • https://www.shoppercove.com/blog/webpack-5-111-esm-output-stable-september-2026
  • https://www.shoppercove.com/blog/terser-vs-esbuild-swc-minify
  • https://www.shoppercove.com/blog/size-limit-vs-bundlesize
  • https://www.shoppercove.com/blog/knip-vs-depcheck
  • https://www.shoppercove.com/blog/rolldown-vs-rollup
  • https://www.shoppercove.com/blog/madge-vs-dependency-cruiser
  • https://www.shoppercove.com/blog/vite-8-3-2-renderbuilturl-bundled-dev-sourcemaps-october-2026
webpackbundle sizesource-map-explorerwebpack-bundle-analyzerscope hoistingperformance optimization

Lab evidence

What I found running this

Hands-on on ShopperCove box 5 Oct 2026 ~23:36-23:42 IST (8 vCPU Intel Xeon / 15 GB shared Linux, Node 20.19.2). Fixture: 1 entry + 120 feature modules importing date-fns 4.4.0 format/addDays + lodash 4.18.1 default import; webpack 5.111.1, webpack-cli 7.2.3, production, devtool source-map. main.js 148569 B, map 930179 B, stats.json 3775203 B. Tools: source-map-explorer 2.5.3 (default run exit 1 'generated column Infinity'; needs --no-border-checks), webpack-bundle-analyzer 5.4.0 CLI + plugin. 1 warmup, 7 interleaved CLI runs: sme json 224.3 ms, sme html 243.8 ms, wba json 312.6 ms, wba static 327.3 ms. 5 builds: plain 3490 ms, --json 3129 ms, plugin 3343 ms (noise). Attribution concat on: sme lodash 70312 / date-fns 19196 / src 58258; wba parsed lodash 70136 / date-fns 44313 / src 33840 (158 leaves inaccurateSizes:true, proportional to stat size). concatenateModules:false: bundle 163525 B (+10%), wba date-fns 22174, sme 19966, 0 inaccurate. sme --gzip total 31346 = wba gzip total. Raw: /workspace/bench-sme-wba/res-sme-wba.json. Not tested: Vite/Rollup output, multi-chunk, other devtools, server mode, Windows/macOS. No affiliate.

Notes when a lab post goes up

Occasional email for new hands-on reviews. No sequence and no sponsors.

Related links

  • Plate 66

    Jotai vs Zustand: 0.15 ms vs 0.17 ms per Update

    5 Oct 2026

  • Plate 38

    TanStack Query vs SWR: 10.4 KB vs 5.6 KB Gzip

    5 Oct 2026

  • Plate 60

    Zustand vs Redux Toolkit: 0.14 ms vs 0.68 ms per Update

    zustand 5.0.15 vs @reduxjs/toolkit 2.13.0 + react-redux 9.3.0 on React 19.3.0 (jsdom, NODE_ENV=production). 1,000 selector-subscribed rows, 300 flushSync updates x 7 trials, 2 runs: per-update median 0.137/0.139 ms vs 0.676/0.694 ms (~5x); 1 row re-rendered per update in both. Store-only: 1,376,191 vs 6,502 updates/s; Immer autoFreeze off lifts RTK to 386,966. Gzip (React external): 387 B vs 9,708 B. Fixture /workspace/bench-zustand-rtk/.

    5 Oct 2026

On this page

  1. What I tested
  2. Why do the two tools report different sizes?
  3. Can you make webpack-bundle-analyzer accurate?
  4. Which bundle analyzer should you use?
  5. Sources
  6. Related
All writingBlogCategoriesTopicsAboutPrivacyRSS

© 2026 ShopperCove