mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
docs(bundle-size): correct the action pin, package inventory and guard coverage
The Tier 3 section shipped in this PR made claims the code contradicts:
- The action pin read v2.9.1; static_bundle_size.yml pins 2.10.0 (by SHA).
- assert-headless-purity.mjs asserts four targets — dist/v2/headless.{mjs,cjs}
AND dist/v2/context.{mjs,cjs} — not just the headless pair.
- The package inventory enumerated 9 packages while the workflow glob covers 10:
react-native was neither listed nor classified, and the "no size script" claim
was wrong for it (it ships size:headless).
- "Both directions of the #4893 regression" overclaimed. The purity script is a
substring scan of four emitted files with @copilotkit/core, @copilotkit/shared,
@ag-ui/*, rxjs, zod and uuid external, so it cannot see a heavy dep arriving
through an external edge; the RN test never resolves bare specifiers, so it
cannot enter node_modules. Both limits are now stated, along with the gap
neither guard covers.
- The heading said "two tiers" while three are documented.
Docs only — no change to the script or the workflow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+54
-15
@@ -1,10 +1,10 @@
|
||||
# Bundle Size Tracking
|
||||
|
||||
## How it works — two tiers
|
||||
## How it works — three tiers
|
||||
|
||||
### Tier 1: CI (compressed-size-action)
|
||||
|
||||
`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).
|
||||
`static_bundle_size.yml` runs on every PR via `preactjs/compressed-size-action` (pinned by commit SHA, currently `2.10.0`). 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_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.
|
||||
|
||||
@@ -14,10 +14,15 @@ Key facts:
|
||||
- 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.
|
||||
- PR comments show paths like `packages/react-core/dist/index.mjs (+1.2 kB gzip)`.
|
||||
|
||||
`react-native` joined the glob in the render-tool convergence (2026-08-06); its
|
||||
`dist/` was previously unmeasured. `pnpm --filter @copilotkit/react-native
|
||||
size:headless` gives a per-PR gzip signal for the headless entry, printed to the
|
||||
job summary. No `limit` fields — see Phase 2.
|
||||
`react-native` joined the glob in the render-tool convergence (2026-08-06),
|
||||
bringing the glob to **10** packages; its `dist/` was previously unmeasured.
|
||||
Separately, `pnpm --filter @copilotkit/react-native size:headless`
|
||||
(`packages/react-native/scripts/measure-headless.mjs`, run as the last step of the
|
||||
`copilotchat-import-size` job) esbuild-bundles the lean import surface of
|
||||
`@copilotkit/react-native/headless` — deps and all, with `react`/`react-native`
|
||||
external — and writes the gzipped total to the job summary. Like `size:headline`
|
||||
it is a cross-PR relative signal, not a Metro figure, and it does not hard-fail.
|
||||
No `limit` fields — see Phase 2.
|
||||
|
||||
### The CopilotChat regression signal (job summary, not the PR comment)
|
||||
|
||||
@@ -51,7 +56,10 @@ The four **bundled** packages (`core`, `react-core`, `react-ui`, `react-textarea
|
||||
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.
|
||||
The other **six** packages in the CI glob have no `.size-limit.json` and no `size` script (4 + 6 is the 10 packages the workflow's `pattern` covers):
|
||||
|
||||
- `shared`, `runtime-client-gql`, `web-inspector`, `voice`, `a2ui-renderer` — unbundled (they emit re-export barrels with separate chunk files); tracked by the CI glob only.
|
||||
- `react-native` — multi-entry with every runtime dep external, so the glob measures each entry plus its shared chunks. It has no size-limit config either, but it does ship a bespoke `size:headless` script (`scripts/measure-headless.mjs`, an esbuild signal rather than size-limit — see Tier 1 above), run in CI and locally via `pnpm --filter @copilotkit/react-native size:headless`.
|
||||
|
||||
> **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.
|
||||
|
||||
@@ -60,15 +68,46 @@ The five unbundled packages (`shared`, `runtime-client-gql`, `web-inspector`, `v
|
||||
Two checks hard-fail because they assert _structure_, not a byte threshold — no
|
||||
baseline to maintain, and no conflict with the Phase 2 freeze on `limit` fields:
|
||||
|
||||
1. `pnpm --filter @copilotkit/react-core size:assert-headless` — fails if the
|
||||
built `dist/v2/headless.mjs` / `.cjs` reference `shiki`, `mermaid`,
|
||||
`cytoscape`, `katex` or `streamdown`. Runs in `static_bundle_size.yml`.
|
||||
1. `pnpm --filter @copilotkit/react-core size:assert-headless`
|
||||
(`packages/react-core/scripts/assert-headless-purity.mjs`) — reads the **four**
|
||||
built React-Native-reachable entry files (`dist/v2/headless.mjs` / `.cjs` and
|
||||
`dist/v2/context.mjs` / `.cjs`) and fails if the text of any of them contains
|
||||
`shiki`, `mermaid`, `cytoscape`, `katex` or `streamdown`. Both entries are
|
||||
guarded because `@copilotkit/react-native` imports both. Runs in
|
||||
`static_bundle_size.yml` — the step there is named after `/v2/headless` only,
|
||||
but the script covers `/v2/context` as well.
|
||||
2. `packages/react-native/src/__tests__/headless-entry-surface.test.ts` — walks
|
||||
the RN import graph and fails if any module imports a react-core entry other
|
||||
than `/v2/headless` or `/v2/context`. Runs in the normal test job.
|
||||
the relative-import graph of this package's own `src/`, from both
|
||||
`src/headless.ts` and `src/index.ts`, and fails if a reached module imports a
|
||||
react-core entry other than `/v2/headless` or `/v2/context`, imports the heavy
|
||||
render stack directly, or (headless entry only) pulls the optional native
|
||||
chat/attachment peer deps. Runs in the normal test job.
|
||||
|
||||
Together they cover both directions of the #4893 regression: react-native
|
||||
importing the fat entry, and the lean entry growing heavy.
|
||||
**What they cover, and what they don't.** Between them the two checks catch the
|
||||
two shapes of the #4893 regression that are cheap to detect statically:
|
||||
react-native importing the fat `@copilotkit/react-core/v2` entry, and the heavy
|
||||
render stack being _inlined_ into the lean react-core entries. Neither check
|
||||
follows a dependency edge past the files it reads:
|
||||
|
||||
- The purity script is a **substring scan of four emitted files**. It sees only
|
||||
what rolldown inlined into them. `headless` and `context` are built with
|
||||
`react`, `@copilotkit/core`, `@copilotkit/shared`, `@ag-ui/*`, `rxjs`, `zod` and
|
||||
`uuid` **external** (`packages/react-core/tsdown.config.ts`), so a heavy dep
|
||||
arriving transitively through one of those would not appear in the scanned text
|
||||
and would pass. The one external edge that _is_ covered is
|
||||
`@copilotkit/react-core/v2/context` — and only because `context.mjs` / `.cjs`
|
||||
are enumerated as targets in their own right, not because the scan traverses.
|
||||
Being a plain substring match, it also only checks the `.mjs` / `.cjs` outputs
|
||||
(not the UMD builds or the declarations), and a forbidden name in a comment or
|
||||
an unrelated identifier fails it.
|
||||
- The RN test reads only `.ts` / `.tsx` files under `packages/react-native/src/`.
|
||||
It records bare specifiers but never resolves them, so it cannot see anything
|
||||
inside `node_modules`.
|
||||
|
||||
A regression that reaches an RN bundle through a transitive dependency of an
|
||||
externalized package is therefore caught by neither. The `size:headless` esbuild
|
||||
signal (Tier 1) is what makes such a regression's _magnitude_ visible, after the
|
||||
fact and without hard-failing.
|
||||
|
||||
## Where configuration lives
|
||||
|
||||
@@ -76,7 +115,7 @@ importing the fat entry, and the lean entry growing heavy.
|
||||
|
||||
## Adding a new measurement
|
||||
|
||||
Only bundled packages support local size tracking. For unbundled packages, CI covers all chunk files via the glob; no local config is needed.
|
||||
Only bundled packages support local size tracking **via size-limit**. For the other six packages in the glob, CI covers all chunk files; no local config is needed. Where a specific consumer-facing import needs a number, the pattern is a bespoke esbuild script rather than a `.size-limit.json` — `react-core`'s `size:headline` and `react-native`'s `size:headless` are the two existing examples.
|
||||
|
||||
To add a measurement to a bundled package:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user