Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,112 @@
|
||||
# Bundle Budgets
|
||||
|
||||
**Status:** active
|
||||
**Owner:** web platform
|
||||
**Related:** [`scripts/bundle-budgets.json`](../../scripts/bundle-budgets.json), [`scripts/verify-bundle-size.mjs`](../../scripts/verify-bundle-size.mjs), `docs/improvement-plan/10-compliance-polish/bundle-budget.md`
|
||||
|
||||
## Why
|
||||
|
||||
Web bundle growth is silent unless we measure it. The boilerplate tracks two complementary signals:
|
||||
|
||||
1. **Group budgets** (`BUDGETS` constant in `verify-bundle-size.mjs`): coarse caps for `root` / `initial_payload_js` / `vendor` / `route` / `other` / `css` / `total_js` / `manifest_js`. Hard CI gate — failure exits 1.
|
||||
2. **Per-route + per-vendor-chunk budgets** (`scripts/bundle-budgets.json`): fine-grained gzip limits per route and per vendor chunk. **Non-blocking** — surfaces as a PR-comment with a Δ-from-main column. Reviewers decide whether growth is justified.
|
||||
|
||||
The per-route layer was added in task #71 (Phase 10) to make day-to-day creep visible without blocking legitimate feature work.
|
||||
|
||||
`initial_payload_js` is a dedicated hard signal for the boot asset aggregate the current parser can identify without reading Rollup/Vite dependency graphs: `entry.client-*`, `root-*`, and `manifest-*`. It intentionally excludes route, vendor, and component chunks so lazy feature payloads cannot hide a shell regression in `total_js`.
|
||||
|
||||
## Config schema
|
||||
|
||||
`scripts/bundle-budgets.json` validated by `scripts/bundle-budgets.schema.json`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"default": { "limitKb": 200, "warningKb": 180 },
|
||||
"routes": {
|
||||
"app/$tenant/dashboard": { "limitKb": 250, "warningKb": 225 },
|
||||
"auth/magic-link": { "limitKb": 80, "warningKb": 75 }
|
||||
},
|
||||
"vendorChunks": {
|
||||
"vendor-react": { "limitKb": 150, "warningKb": 135 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- All sizes are **gzip kilobytes** (the transport-relevant number).
|
||||
- `limitKb` is the hard cap; assets above this report `[EXCEED]` in CI output and the PR comment.
|
||||
- `warningKb` is the soft threshold; between `warningKb` and `limitKb` assets report `[WARN]`.
|
||||
- Routes use React Router v7 route ids in either dot- or slash-form (`auth/magic-link` and `auth.magic-link` both match).
|
||||
- Unmatched routes fall back to `default`.
|
||||
- Unmatched vendor chunks (anything starting with `vendor-` or `chunk-`) are reported under the chunk basename and also fall back to `default`.
|
||||
|
||||
## Local workflow
|
||||
|
||||
```bash
|
||||
# Build apps/web normally and print the per-route table:
|
||||
pnpm --filter @toolctrl/web build
|
||||
node scripts/verify-bundle-size.mjs
|
||||
|
||||
# Only the per-route table (skips group checks):
|
||||
node scripts/verify-bundle-size.mjs --per-route-only
|
||||
|
||||
# Machine-readable JSON (used by the PR-comment workflow):
|
||||
node scripts/verify-bundle-size.mjs --json > pr-bundle-report.json
|
||||
```
|
||||
|
||||
`--json` emits only the non-blocking per-route/per-vendor report and exits with
|
||||
code 0 when the report was produced. Keep the hard group-budget gate on the
|
||||
plain `node scripts/verify-bundle-size.mjs` path so redirected JSON never mixes
|
||||
machine-readable output with human diagnostics.
|
||||
|
||||
### Visualizer (treemap)
|
||||
|
||||
To inspect why a chunk is big:
|
||||
|
||||
```bash
|
||||
pnpm --filter @toolctrl/web run bundle:visualize
|
||||
open apps/web/build/stats.html
|
||||
```
|
||||
|
||||
The visualizer is gated on `ANALYZE_BUNDLE=true` so the regular production build keeps its current footprint. It uses `rollup-plugin-visualizer` (treemap, gzip + brotli sizes per chunk).
|
||||
|
||||
**Security note:** `stats.html` contains source-map references and should never be deployed publicly. Treat it as a developer-only artifact.
|
||||
|
||||
## PR-comment workflow
|
||||
|
||||
`.github/workflows/bundle-budget.yml` (and `.gitea/workflows/bundle-budget.yml`) run on every PR that touches `apps/web/**`, `packages/**`, or the budget config / verifier. They:
|
||||
|
||||
1. Build `@toolctrl/web`.
|
||||
2. Run `verify-bundle-size.mjs --json`.
|
||||
3. Download the latest `bundle-baseline-main` artifact (captured by `bundle-budget-baseline.yml` on `main` pushes).
|
||||
4. Render a sticky markdown comment via `scripts/render-bundle-budget-comment.mjs`.
|
||||
5. Upsert the comment using its `<!-- bundle-budget-report -->` marker (GitHub: `marocchino/sticky-pull-request-comment`; Gitea: `scripts/post-bundle-budget-gitea-comment.mjs`).
|
||||
|
||||
The comment renders rows in severity order (`[EXCEED]` → `[WARN]` → unbudgeted → ok), with columns: status / kind / route or chunk key / current gzip size / configured limit / Δ from main. Δ is `—` if no baseline is available.
|
||||
|
||||
### When the baseline is missing
|
||||
|
||||
The first PR after a fresh checkout (or after a baseline-artifact expiry) sees `Δ from main = —` for every row. This is expected — the next push to `main` re-captures the baseline.
|
||||
|
||||
## When to raise a limit
|
||||
|
||||
1. Confirm the growth is intentional (a new feature shipped, a dependency landed) — not a regression from accidental imports.
|
||||
2. Open the PR; the bundle-budget comment shows the Δ. Reviewers and the PR author should agree the new floor is acceptable.
|
||||
3. Edit `scripts/bundle-budgets.json` in the **same PR** that ships the growth. Bump `warningKb` to ≈ (current gzip + 5 KB) and `limitKb` to ≈ (current gzip + 20 KB).
|
||||
4. Call the change out in the PR description so the new floor is reviewable.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **Don't disable a budget by raising it to 9999 KB.** The point is to surface drift. If a route is legitimately huge, raise the limit to a tight new floor, not infinity.
|
||||
- **Don't lazy-load critical-path code.** Bundle splits help non-critical routes, but the initial payload (`entry.client-*` / `root-*` / `manifest-*`) still has its own `initial_payload_js` hard group budget that must stay tight.
|
||||
- **Don't bundle-split shared deps.** Splitting `react` into many chunks just shifts cost into chunk boot-up. Keep `vendor-react`, `vendor-core-utils` etc. as single chunks; the manualChunks mapping in `apps/web/app/utils/build/manualChunks.ts` is the source of truth.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **PR comment never shows up:** check the `Bundle Budget` workflow run — the `Build web` step may have failed (e.g. server-only import in a client export). The comment job only runs if all upstream steps succeed.
|
||||
- **`vendor-monaco-...` reports as `vendor-monaco-...-` with default budget:** rollup occasionally emits chunk hashes ending in `-`; the baseline normalizer skips that suffix and the asset falls back to `default`. Either rename via `manualChunks.ts` or add an explicit budget entry.
|
||||
- **CI run is much slower than `pnpm typecheck`:** the workflow does a full `pnpm --filter @toolctrl/web build`. That's the expected cost. If it ever exceeds ~30 minutes, profile with `ANALYZE_BUNDLE=true` locally first.
|
||||
|
||||
## Rollback
|
||||
|
||||
- If the per-route budget surfaces too many false alarms, lower the warning thresholds in `scripts/bundle-budgets.json` and ship a follow-up.
|
||||
- If the entire PR-comment flow is broken, set `if: false` on the `measure` job in `bundle-budget.yml` — no other CI stage depends on it.
|
||||
Reference in new issue
Block a user