|
| 1 | +# Bundle Size Tracking |
| 2 | + |
| 3 | +## How it works — two tiers |
| 4 | + |
| 5 | +### Tier 1: CI (compressed-size-action) |
| 6 | + |
| 7 | +`static_bundle_size.yml` runs on every PR via `preactjs/compressed-size-action@v2.9.1`. It scans a glob (`packages/{...}/dist/**/*.{mjs,js,cjs}`), computes the gzip size of each matched file (the action's default compression; the workflow sets no `compression` input), and posts a PR comment showing per-file diffs. It has **no hard-fail** (Phase 1). |
| 8 | + |
| 9 | +> **Fork PRs:** `pull_request` runs triggered from a fork receive a read-only `GITHUB_TOKEN`, so `compressed-size-action` cannot post or update the PR comment — it prints the size report to the job logs instead. The measurement still runs; only the comment is unavailable. This is an accepted Phase 1 limitation (the report is informational and there is no hard-fail). If the PR comment ever becomes a required signal, switch to a `pull_request_target` + `workflow_run` relay pattern so the comment is posted from a trusted context without exposing write tokens to fork code. |
| 10 | +
|
| 11 | +Key facts: |
| 12 | + |
| 13 | +- Reports by **file path**, not by named entry — it does not read `.size-limit.json` at all. |
| 14 | +- The action runs `build-script: build` (the root `build` script — `nx run-many -t build` over all `packages/**`) on both the PR branch and the base branch, then measures only the files matched by the `pattern` glob. The root `build` script is used (rather than a bundle-size-specific one) because the action must build the base branch too, and `build` exists on every branch. No separate build step is needed before the workflow triggers — the action handles both builds. |
| 15 | +- PR comments show paths like `packages/react-core/dist/index.mjs (+1.2 kB gzip)`. |
| 16 | + |
| 17 | +### The CopilotChat regression signal (job summary, not the PR comment) |
| 18 | + |
| 19 | +The `copilotchat-import-size` job in `static_bundle_size.yml` measures what an app |
| 20 | +importing `{ CopilotChat }` from `@copilotkit/react-core/v2` bundles, via |
| 21 | +`packages/react-core/scripts/measure-copilotchat.mjs` (run locally with |
| 22 | +`pnpm --filter @copilotkit/react-core size:headline`). It drives `esbuild` |
| 23 | +directly — bundling `{ CopilotChat }` minified, with `react`/`react-dom` external |
| 24 | +and CSS/fonts stubbed to `empty` (we measure JS) — and writes the total gzipped |
| 25 | +JS to the GitHub **job summary**. |
| 26 | + |
| 27 | +**This is a _relative_ regression signal, not a production figure.** Its absolute |
| 28 | +value (currently ~3 MB gzip) is an esbuild number; a real consumer bundler |
| 29 | +(Vite/Next/webpack) splits eager-vs-lazy differently and reports different |
| 30 | +absolutes — the Notion "Header Embed Bundle Readout" measured ~386 kB _main |
| 31 | +initial JS_ under Vite, with the shiki/mermaid language packs as separate |
| 32 | +generated chunks. The script's worth is **consistency**: the same measurement |
| 33 | +every PR, so a change that grows CopilotChat's JS shows up, and the number |
| 34 | +collapses once OSS-122 moves the language packs to a CDN. A faithful _production_ |
| 35 | +headline (real Next 15 fixture + `@next/bundle-analyzer`) is OSS-122 Phase 0. |
| 36 | + |
| 37 | +Why a custom script and not `size-limit`: CopilotChat pulls `katex`'s CSS, whose |
| 38 | +`url()` font refs crash `@size-limit/esbuild` (which exposes no loader hook). |
| 39 | +Driving esbuild directly lets us stub the CSS/font assets. |
| 40 | + |
| 41 | +### Tier 2: Local dev (size-limit) |
| 42 | + |
| 43 | +The four **bundled** packages (`core`, `react-core`, `react-ui`, `react-textarea`) each have a `.size-limit.json` at their root listing one or more named entries pointing at `dist/` paths. Run locally via: |
| 44 | + |
| 45 | +``` |
| 46 | +pnpm --filter <pkg> size |
| 47 | +``` |
| 48 | + |
| 49 | +The five unbundled packages (`shared`, `runtime-client-gql`, `web-inspector`, `voice`, `a2ui-renderer`) have no `.size-limit.json` and no `size` script — their sizes are tracked by the CI glob only. |
| 50 | + |
| 51 | +> **Node version requirement:** `size-limit@12.1.0` requires Node 20, 22, or 24+ (`^20 || ^22 || >=24`). Running `pnpm --filter <pkg> size` on Node 18 will produce an `EBADENGINE` error. |
| 52 | +
|
| 53 | +## Where configuration lives |
| 54 | + |
| 55 | +`.size-limit.json` files live at the root of each bundled package (`core`, `react-core`, `react-ui`, `react-textarea`) and are used exclusively by the local `size` script. They are not read by CI. |
| 56 | + |
| 57 | +## Adding a new measurement |
| 58 | + |
| 59 | +Only bundled packages support local size tracking. For unbundled packages, CI covers all chunk files via the glob; no local config is needed. |
| 60 | + |
| 61 | +To add a measurement to a bundled package: |
| 62 | + |
| 63 | +1. Add an entry to the package's `.size-limit.json`: |
| 64 | + ```json |
| 65 | + { "name": "my-package: MyExport", "path": "dist/index.mjs", "gzip": true } |
| 66 | + ``` |
| 67 | +2. Build the package first: `pnpm --filter <pkg> build` |
| 68 | +3. Run locally: `pnpm --filter <pkg> size` |
| 69 | +4. Commit the updated `.size-limit.json`. |
| 70 | + |
| 71 | +Note: named entries appear in **local** size-limit output only. CI PR comments report by file path from the glob, not by these names. |
| 72 | + |
| 73 | +> **Bundled vs. unbundled packages:** `@size-limit/file` reports accurate sizes for bundled packages (those that build a single-file bundle). For unbundled packages (those that emit re-export barrels with separate chunk files), `@size-limit/file` only counts the barrel file — the CI `compressed-size-action` glob covers all chunks correctly regardless. |
| 74 | +
|
| 75 | +## CI behavior (Phase 1 — current) |
| 76 | + |
| 77 | +`static_bundle_size.yml` posts a comment with per-file gzip diffs on every PR. It has **no hard-fail**. Sizes today reflect pre-OSS-122 bloat; adding budget limits now would either lock in that bloat permanently or fail immediately on every PR. Neither is useful. |
| 78 | + |
| 79 | +## Phase 2 — after OSS-122 (separate ticket, blocked) |
| 80 | + |
| 81 | +Once OSS-122 has reduced the baseline: |
| 82 | + |
| 83 | +1. Add `"limit"` fields to each `.size-limit.json` entry. |
| 84 | +2. Add a size-limit step to the CI workflow (currently the workflow has no size-limit step — Phase 2 adds one, it does not flip an existing step). |
| 85 | +3. PRs that regress past a limit will fail CI. |
| 86 | + |
| 87 | +Do not add `"limit"` fields before OSS-122 lands. |
0 commit comments