How to Compare Two webpack Builds with Statoscope

This guide extends Webpack Bundle Analysis Techniques within JavaScript Bundle Optimization & Code Splitting. Most bundle tools answer "what is in this build?". The more useful question during development is "what changed between these two builds, and why?". Statoscope is a toolkit built around webpack's stats JSON that answers that directly: it diffs two stats files to show added and removed modules, changed chunk compositions, package version changes and duplicate packages, and it can validate builds against rules in CI.

That makes it the natural tool for investigating a failed size budget ("the landing chunk grew 40KB — from what?"), reviewing a dependency upgrade ("did upgrading the date library change our bundle?") and enforcing structural rules that size checks cannot express ("no package may appear in more than one version").

Statoscope diff workflow Steps from generating webpack stats for two builds to a diff report and CI validation. Statoscope diff workflow Base stats.json main branch build Head stats.json PR build Diff modules, chunks, packages Validate rules for CI Report HTML + PR comment

Rapid Diagnosis

  • Do you produce stats JSON in CI? Without it, build comparisons are guesswork.
  • Is the stats output complete? Statoscope needs module, chunk and reason information; minimal stats presets omit it.
  • Do you know what changed in the last size regression? If answering takes more than a few minutes, a diff tool pays for itself.
  • Do you have structural rules? Rules like "no duplicate React" or "entry chunk under N modules" need a validator, not a byte budget.

Root Cause Analysis: Why Regressions Go Unexplained

1. Size tools report totals, not causes. A byte count rising does not say which module or dependency caused it.

2. Dependency changes hide in lockfiles. A minor upgrade of a transitive dependency can pull in new modules; nobody reviews lockfile diffs in detail.

3. Chunk composition shifts silently. A module moving from a lazy chunk to the entry looks like a size increase in one place and a decrease in another; totals hide it.

4. Duplicates creep in. Two versions of a package resolve after an upgrade, doubling its contribution.

What a Statoscope diff found in a +43KB regression Bar chart attributing a 43 kilobyte entry chunk increase to its causes as shown by a build diff. What a Statoscope diff found in a +43KB regression date-fns duplicated (v2 + v3) 21KB chart module moved into entry 18KB New feature code 6KB Removed dead code -2KB

Step-by-Step Workflow

1. Emit full stats from both builds

javascript
// webpack.config.js — with @statoscope/webpack-plugin
const StatoscopeWebpackPlugin = require('@statoscope/webpack-plugin').default;
module.exports = {
  plugins: [new StatoscopeWebpackPlugin({ saveStatsTo: 'dist/stats.json', saveReportTo: 'dist/statoscope.html', open: false })],
};
// trade-off: full stats files can be tens of megabytes for large apps and add
// build time. Generate them in CI, not in every local build.

Expected outcome: a stats.json and an HTML report for each build.

2. Generate a diff report

bash
npx @statoscope/cli generate -i base/stats.json -i head/stats.json -o diff.html
# Open diff.html and choose the "Diff" view: added/removed modules, changed
# chunks, package version changes and duplicates are listed separately.
# trade-off: comparing builds with different webpack configs (e.g. dev vs prod)
# produces noise. Always diff like with like.

Expected outcome: a concrete list of what changed, with sizes.

3. Encode rules with the validator

javascript
// statoscope.config.js
module.exports = {
  validate: {
    plugins: ['@statoscope/webpack'],
    rules: {
      '@statoscope/webpack/no-packages-dups': ['error', { exclude: ['tslib'] }],
      '@statoscope/webpack/restricted-packages': ['error', ['moment', 'lodash']],
      '@statoscope/webpack/entry-download-size-limits': ['error', { global: { maxSize: 180 * 1024 } }],
      '@statoscope/webpack/diff-entry-download-size-limits': ['error', { global: { maxSizeDiff: 10 * 1024 } }],
    },
  },
};
// trade-off: rule names and options evolve between Statoscope versions — pin
// the version and check the rule docs when upgrading.
bash
npx @statoscope/cli validate --input head/stats.json --reference base/stats.json

Expected outcome: CI fails on duplicates, banned packages, oversized entries or large entry growth, with messages that name the cause.

4. Add the diff to pull requests

Upload diff.html as a CI artefact and link it from the size comment, so reviewers can drill into causes when a number looks wrong.

Structural rules size budgets cannot express Examples of Statoscope validation rules and the regressions they catch that a byte budget would miss. Structural rules size budgets cannot express Rule Catches Budget alone? no-packages-dups two versions of a library only if total grows enough restricted-packages banned heavy libs reappearing no diff-entry-download-size-limits growth per PR partly entry-download-size-limits absolute entry size yes

Verification

Introduce a known change on a branch — add a dependency to the entry — and confirm the diff names it with the right size and that the validator fails if the rule is set. Then verify against a no-op change that the diff is empty; if not, your builds are non-deterministic (unstable IDs or timestamps), which is worth fixing for caching reasons too.

Worked Example: A Silent Duplicate

After a routine npm update, a team's entry chunk grew by 21KB without any code change. Size checks flagged the growth but not the cause. The Statoscope diff showed a new package entry, date-fns@2.30.0, alongside the existing date-fns@3.6.0: a component library dependency had pinned v2. The fix was a package-manager override forcing v3 (after checking compatibility), and the team added no-packages-dups to CI so the next duplicate would fail with a message naming the package instead of an unexplained byte count.

Common Mistakes

  • Using stats: 'minimal'. Statoscope needs reasons, modules and chunk data; use the plugin or a verbose stats preset.
  • Diffing builds from different environments. Development and production stats differ wildly.
  • Treating every diff entry as a problem. New feature code is expected; focus on duplicates, moved modules and dependency changes.
  • Not pinning tool versions. Validator rules change between releases.

Edge Cases When Diffing Builds

Non-deterministic builds. If a no-op change produces a non-empty diff, your build embeds timestamps, random IDs or environment values. Fix that first; otherwise every diff is noise and caching suffers too.

Different dependency trees. Comparing builds produced from different lockfiles shows every transitive change. That is useful for reviewing dependency upgrades, but for code reviews make sure both builds use the same lockfile except for intentional changes.

Large monorepos. Stats for monorepos with many entry points can be very large. Diff per application rather than for the whole repository, and generate stats only for applications affected by a pull request.

Module concatenation. Concatenated modules appear as a group in stats; changes inside them are attributed to the group root. Disable concatenation in an analysis-only build if you need module-level precision.

FAQ

Does Statoscope work with Rspack or Vite?

Rspack produces webpack-compatible stats, so Statoscope works with it. Vite/Rollup do not produce webpack stats; use rollup-plugin-visualizer's raw output or the build manifest for diffs instead.

How is this different from webpack-bundle-analyzer?

webpack-bundle-analyzer visualises one build's composition. Statoscope can do that too, but its strengths are comparing builds, explaining why modules are included, finding duplicates and validating rules — the tasks that matter when investigating changes.

Should the HTML report be public?

No. It reveals your dependency list and file structure. Keep it as a CI artefact accessible to your team.

Can Statoscope explain why a module is in a chunk?

Yes. Its module view shows the reasons — which modules import it and through which chunk — which is the fastest way to find the import that moved a heavy dependency into the entry chunk. Follow the reasons upward until you reach your own source.

Is it worth running on every pull request?

Validation, yes — it is fast and deterministic. Full HTML diff reports are heavier; generate them on every pull request if CI time allows, or only when the size check reports a change above a threshold.

What is the quickest win once Statoscope is set up?

Turn on the duplicate-packages rule. Duplicates are common after dependency updates, invisible in code review, and almost always fixable with a resolution override or a dependency upgrade — pure byte savings with no product change.

/html>