Skip to content

feat(scripts): add a client-bundle measurement so regressions are visible - #6135

Closed
waleedlatif1 wants to merge 1 commit into
stagingfrom
perf/measure-client-bundle
Closed

feat(scripts): add a client-bundle measurement so regressions are visible#6135
waleedlatif1 wants to merge 1 commit into
stagingfrom
perf/measure-client-bundle

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

We ship JavaScript to users and nothing measures it. This adds bun run measure:bundle, and its first use already produced a decisive result: the tool and block registries are ~52% of all client JS.

Why this didn't exist

Every instrument you'd reach for is missing:

instrument status
First Load JS in build output 0 matches — Turbopack's route table prints only Revalidate/Expire
app-build-manifest.json (route→chunk map) not emitted
route-shaped static/chunks/app/** absent — chunks are flat and content-hashed (1ep92zb9a-584.js)
@next/bundle-analyzer not installed, and it's a webpack plugin

So a bundle regression was invisible until someone noticed a slow page.

What it measures

Total client JS plus the largest chunks. --json writes a baseline; --baseline <file> fails on a >2% regression, so it can become a CI gate once a baseline is agreed.

Deliberately not per-route. That needs either a manifest Turbopack doesn't emit, or a browser loading each route. Inventing an attribution from flat chunk names would produce a confident number that is wrong — worse than no number.

.map files are excluded, so a source-map change can't masquerade as a bundle regression.

First result: the registry premise, measured

Two production builds — one normal, one with the existing SIM_DEV_MINIMAL_REGISTRY alias forced on:

client JS chunks
baseline 110.08 MB 553
registries aliased away 52.96 MB 553
−57.12 MB (−51.9%)

The four 15.52 MB chunks in the baseline vanish completely — that's the registry graph duplicated across four entry points.

How to read that number honestly

  • It's an upper bound for both registries together (@/tools/registry and @/blocks/registry-maps), not tools alone, and not a promise. A metadata split would still ship params/outputs for referenced tools.
  • It's total client JS, not per-route First Load. Users download the chunks their route needs, not all 110 MB. "52% of client JS" is not "52% faster page load."
  • What it does establish: the registry is roughly half of everything we ship, measured rather than inferred from an import-graph tracer. The prior 11.8 MB/route figure came from a static tracer; this is a real build.

Why this matters beyond one project

The registry split was scoped on an unverified premise. This makes the premise checkable, gives the work a before/after, and leaves behind a regression guard that outlives it.

Type of Change

  • Improvement (tooling / observability)

Testing

Run against two real production builds (numbers above). biome check clean. No dependencies added. next.config.ts was temporarily modified for the second build and restored — the diff is only the new script and its package.json entry.

…ible

Nothing measured the JavaScript we ship. Turbopack's route table prints only
Revalidate/Expire — no `First Load JS` — and it emits flat, content-hashed
chunk names with no app-build-manifest.json, so there is no route->chunk
mapping to attribute bytes with. @next/bundle-analyzer is a webpack plugin
and is not installed. A bundle regression was therefore invisible until a
user noticed a slow page.

`bun run measure:bundle` reports total client JS and the largest chunks;
`--json` emits a baseline and `--baseline <file>` fails on a >2% regression,
so this can become a CI gate once a baseline is agreed.

Deliberately not per-route. That needs either a manifest Turbopack does not
emit or a browser loading each route; inventing an attribution from flat
chunk names would produce a confident number that is wrong.

First use already paid for it. Building twice — once normally, once with the
existing SIM_DEV_MINIMAL_REGISTRY alias forced on — measures what the tool
and block registries cost the client:

  baseline                 110.08 MB across 553 chunks
  registries aliased away   52.96 MB across 553 chunks   (-51.9%)

The four 15.52 MB chunks in the baseline disappear entirely; they are the
registry graph duplicated across four entry points. That is an upper bound
for both registries together, not a promise — a metadata split would still
ship params/outputs for referenced tools — but it establishes that the
registry is roughly half of all client JS, measured rather than inferred
from an import-graph tracer.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner July 31, 2026 21:34
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Building Building Preview Jul 31, 2026 9:34pm

Request Review

@cursor

cursor Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Dev-only observability script and package.json script entry; no runtime, auth, or deploy behavior changes.

Overview
Adds bun run measure:bundle, wired to a new scripts/measure-client-bundle.ts, so total client JS from a production apps/sim build can be tracked without Turbopack per-route stats or bundle-analyzer.

The script walks apps/sim/.next/static/chunks, sums .js payload (source maps excluded), prints total size and the 15 largest chunks, and supports --json for baselines and --baseline <file> to fail when total bytes grow more than 2% over a saved report. It intentionally reports aggregate client JS only, not per-route First Load attribution.

Reviewed by Cursor Bugbot for commit 32afd8d. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a client-bundle measurement command.

  • Measures total JavaScript and reports the 15 largest chunks from the Sim production build.
  • Supports JSON report generation and comparison against a baseline with a 2% regression tolerance.
  • Exposes the utility through the measure:bundle package script.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking CLI composability issue where JSON output bypasses a requested baseline check.

Bundle measurement works for the documented standalone modes, but combining machine-readable output with regression enforcement exits before performing the comparison.

Files Needing Attention: scripts/measure-client-bundle.ts

Important Files Changed

Filename Overview
scripts/measure-client-bundle.ts Adds bundle traversal, reporting, and baseline enforcement; combined JSON and baseline flags silently skip enforcement.
package.json Adds the measure:bundle script entry with no issue identified.

Reviews (1): Last reviewed commit: "feat(scripts): add a client-bundle measu..." | Re-trigger Greptile

Comment on lines +88 to +91
if (args.includes('--json')) {
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 JSON output bypasses baseline checks

When the script receives both --json and --baseline, this branch returns before parsing the baseline or enforcing the 2% threshold, so a bundle regression exits successfully without comparison.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Closing — this measures but doesn't fix, and as written it isn't wired into CI with a committed baseline, so in practice nobody would run it. That makes it tooling that implies we measure bundles when we don't.

The measurement already paid for itself as a one-off: 110.08 MB -> 52.96 MB with the registries aliased away (~52% of all client JS), which confirmed the premise from a real build rather than an import-graph tracer. That number is captured; the script can be reproduced locally when the split needs a before/after.

If we want an actual regression guard later, the right shape is a committed baseline plus a CI step — a deliberate decision, not a side effect of a scoping exercise.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 32afd8d. Configure here.


const __filename = fileURLToPath(import.meta.url)
const rootDir = path.resolve(path.dirname(__filename), '..')
const chunkDir = path.join(rootDir, 'apps/sim/.next/static/chunks')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misses immutable chunk directory

Medium Severity

chunkDir is hardcoded to apps/sim/.next/static/chunks, but the current build also emits a parallel static/immutable/chunks tree (including app/, pages/, and turbopack/). Client JS under that prefix is never counted, so totals and the baseline gate can miss real payload growth.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 32afd8d. Configure here.

@waleedlatif1
waleedlatif1 deleted the perf/measure-client-bundle branch August 1, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant