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).
5 min read
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.
/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.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.process.hrtime around the full command; five interleaved webpack builds per build variant.--json stats 3.13 s, with plugin 3.34 s (all inside run-to-run noise on this box).--no-border-checks.devtool variants other than source-map, the analyzer's server mode, Windows/macOS.| Module group | source-map-explorer bytes | Analyzer parsed bytes | Gap |
|---|---|---|---|
| lodash | 70,312 | 70,136 | about equal |
| date-fns (37 files) | 19,196 | 44,313 | analyzer 2.3x higher |
| my src (121 files) | 58,258 | 33,840 | analyzer 42% lower |
| whole bundle | 148,569 | 148,569 | same 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.
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.
| Situation | My pick |
|---|---|
| "Which npm package is making my bundle big?" in a production webpack build | source-map-explorer |
| You need chunk-level treemaps, gzip sizes per chunk, or a CI HTML artifact from the webpack build | webpack-bundle-analyzer (plugin or CLI) |
| Your bundler is not webpack (Vite, Rollup, esbuild) and emits source maps | source-map-explorer (analyzer needs webpack stats) |
| You want exact per-module numbers from the analyzer | Separate analyze build with concatenateModules: false |
| No source maps in production and you will not turn them on | webpack-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.
Lab evidence
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.