dev-docs/bundle-size.md
static_bundle_size.yml runs on every PR via preactjs/[email protected]. 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).
Fork PRs:
pull_requestruns triggered from a fork receive a read-onlyGITHUB_TOKEN, socompressed-size-actioncannot 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 apull_request_target+workflow_runrelay pattern so the comment is posted from a trusted context without exposing write tokens to fork code.
Key facts:
.size-limit.json at all.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.packages/react-core/dist/index.mjs (+1.2 kB gzip).The copilotchat-import-size job in static_bundle_size.yml measures what an app
importing { CopilotChat } from @copilotkit/react-core/v2 bundles, via
packages/react-core/scripts/measure-copilotchat.mjs (run locally with
pnpm --filter @copilotkit/react-core size:headline). It drives esbuild
directly — bundling { CopilotChat } minified, with react/react-dom external
and CSS/fonts stubbed to empty (we measure JS) — and writes the total gzipped
JS to the GitHub job summary.
This is a relative regression signal, not a production figure. Its absolute
value (currently ~3 MB gzip) is an esbuild number; a real consumer bundler
(Vite/Next/webpack) splits eager-vs-lazy differently and reports different
absolutes — the Notion "Header Embed Bundle Readout" measured ~386 kB main
initial JS under Vite, with the shiki/mermaid language packs as separate
generated chunks. The script's worth is consistency: the same measurement
every PR, so a change that grows CopilotChat's JS shows up, and the number
collapses once OSS-122 moves the language packs to a CDN. A faithful production
headline (real Next 15 fixture + @next/bundle-analyzer) is OSS-122 Phase 0.
Why a custom script and not size-limit: CopilotChat pulls katex's CSS, whose
url() font refs crash @size-limit/esbuild (which exposes no loader hook).
Driving esbuild directly lets us stub the CSS/font assets.
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:
pnpm --filter <pkg> size
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.
Node version requirement:
[email protected]requires Node 20, 22, or 24+ (^20 || ^22 || >=24). Runningpnpm --filter <pkg> sizeon Node 18 will produce anEBADENGINEerror.
.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.
Only bundled packages support local size tracking. For unbundled packages, CI covers all chunk files via the glob; no local config is needed.
To add a measurement to a bundled package:
.size-limit.json:
{ "name": "my-package: MyExport", "path": "dist/index.mjs", "gzip": true }
pnpm --filter <pkg> buildpnpm --filter <pkg> size.size-limit.json.Note: named entries appear in local size-limit output only. CI PR comments report by file path from the glob, not by these names.
Bundled vs. unbundled packages:
@size-limit/filereports 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/fileonly counts the barrel file — the CIcompressed-size-actionglob covers all chunks correctly regardless.
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.
Once OSS-122 has reduced the baseline:
"limit" fields to each .size-limit.json entry.Do not add "limit" fields before OSS-122 lands.