bible-strong-avatar-lab/docs/specs/20260814-vite-dev-server-performance.md
Eric Cappannelli 99300b3b30 feat(runtime): ship semantic avatar packages and Studio export
- add the v1 AvatarDefinition schema, bounded parser, strict validation, semantic catalog, pure geometry, scene generation, and deterministic playback to @bible-strong/avatar-core
- add the React 19 renderer with semantic controls, SSR-safe embedded/floating layouts, direct frame updates, pointer and keyboard movement, constraints, callbacks, and accessible controls
- add real tarball packaging and a clean React/Vite consumer using the exported Strobi definition
- add Studio semantic-key authoring, runtime readiness, JSON download/copy, bundled-key recovery, concise errors, targeted local-project clearing, npm guidance, syntax highlighting, and a runnable package preview
- preserve the historical ZIP and Studio project exports, regenerate the standalone engine, and synchronize English, French, and Simplified Chinese copy
- add focused contract, playback, renderer, interaction, persistence, export, and localization coverage
- archive the completed runtime and semantic-curation specs, retain the Vite performance draft, and record the engineering session in TIMELOG.md

Validation:
- pnpm check: 19 test files, 162 tests, typecheck, engine freshness, package builds, and Studio production build passed
- npm pack dry runs: 30 core files and 10 React files
- package smoke: real tarballs installed, typechecked, and built outside the workspace
- browser checks: semantic playback, embedded/floating render, drag, mobile overflow, runtime export recovery, formatted copy, syntax colors, and live package preview

Publication remains disabled: both packages stay private and AGPL-3.0-only pending licensing and repository metadata approval. Vue and Angular adapters remain deferred.
2026-08-16 10:18:39 +02:00

199 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Vite Dev Server Performance Fix
## Status
Draft - impact review completed on 2026-08-14; corrections and Eric's approval are still required.
## Impact review - 2026-08-14
### Decision
Do not implement this draft unchanged. The watcher and compiler-scope changes are isolated from the
runtime packages, but parts of the diagnosis and validation procedure are inaccurate for the current
workspace.
### Verified current state
- The installed workspace versions are Vite 8.2.1, `@vitejs/plugin-react` 6.0.5,
`@rolldown/plugin-babel` 0.2.3, and `@tailwindcss/vite` 4.3.3. The version sentence in the Problem
section reflects earlier manifest ranges, not the current lockfile.
- `fsevents` 2.3.3 is installed through Vite. Chokidar uses FSEvents when it is available; polling on
macOS is the fallback when FSEvents cannot be used. The draft must not state that Vite
unconditionally polls on macOS.
- Two already-running dev servers for this repository were sampled five times while idle on
2026-08-14. They reported 0-0.1% CPU, so the reported ~200% condition was not reproduced during
this review. A before/after measurement under the condition that triggers the problem is required.
- Vite 8.2.1 already ignores `.git`, `node_modules`, `test-results`, `cacheDir`, and every emptied
build `outDir`. Adding `node_modules` and root `dist` to `server.watch.ignored` is therefore
defensive and mostly redundant, not a fix for a missing default.
- The installed React Compiler preset already has a code filter, but it still admits many pure files
containing capitalized identifiers or hook-like names. Restricting Babel to actual JSX/TSX source
remains a credible optimization.
### Impact on the avatar-runtime work
| Area | Impact |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Root Studio dev server | Direct. `server.watch` and the root Babel plugin change startup, idle watching, and HMR behavior. |
| `packages/avatar-core` | Source HMR must remain enabled through the root alias. Its own build config is unaffected. The Babel compiler can safely exclude this package because it contains no JSX. |
| `packages/avatar-react` | Its library build config is unaffected. A generic `/src/...tsx/` regex also matches `packages/avatar-react/src/Avatar.tsx`, contrary to the draft's "app only" explanation; an app-root-anchored filter is required. |
| React consumer fixture | No direct impact because it owns a separate Vite config. Its HMR needs a separate smoke check only if that config is changed later. |
| Vitest, tarball smoke, production packages | No direct impact. Their processes/configs are separate, but the normal regression suite must still pass. |
| Standalone engine generation | No idle impact. `pnpm dev` runs `pnpm engine` once before Vite, so startup CPU must be measured separately from steady-state Vite CPU. |
### Required corrections before approval
1. Reword cause 1 as a hypothesis about an FSEvents fallback and capture evidence that the affected
process is actually polling.
2. Reword cause 2 to acknowledge Vite's existing ignore defaults. Add only project-specific paths if
measurements show that they are watched.
3. Anchor the Babel include filter to the absolute root `src/` directory. The proposed
`/src\/.*\.[jt]sx$/` expression is not root-specific.
4. Record both manifest ranges and installed lockfile versions, and repeat measurements after any
dependency install.
5. Start a single strict-port dev server for measurement and resolve its exact PID. `pgrep -f vite`
is ambiguous on this machine because several unrelated Vite servers run concurrently.
6. Measure startup separately from at least 30 seconds of idle CPU, an HMR edit under root `src/`,
and an HMR edit under `packages/avatar-core/src/`.
7. Treat the Vite 7 downgrade as a separate decision that modifies `package.json` and
`pnpm-lock.yaml`; this contradicts the current "vite.config.ts only" file list.
8. Re-evaluate the cited Vite 8/Rolldown memory reports against Vite 8.2.1 and the installed Rolldown
version before using an 800 MB downgrade threshold.
## Problem
The dev server (`pnpm dev`) consumes ~200% CPU on macOS. The project runs Vite 8.0.13 with
`@vitejs/plugin-react` 6.0.2, `@rolldown/plugin-babel` (React Compiler preset),
`@tailwindcss/vite` 4.3.3, and a pnpm workspace monorepo (`packages/avatar-core` linked via
`workspace:*`).
Four documented causes have been identified, ordered by expected impact.
## Root causes
### 1. File watcher defaults to polling on macOS
Vite inherits a legacy default that sets `usePolling: true` on macOS. Instead of using the
kernel's native `FSEvents`, chokidar scans every watched file on a timer. In a monorepo with
`node_modules` symlinks this produces sustained CPU even when no file changes.
**Source**: https://github.com/vitejs/vite/issues/21033
### 2. Watcher scope includes node_modules and dist
No `server.watch.ignored` is configured. The workspace alias
`@bible-strong/avatar-core → packages/avatar-core/src/index.ts` causes the watcher to follow
pnpm symlinks into `node_modules/.pnpm`, multiplying the number of watched paths.
### 3. React Compiler runs on all files
`@rolldown/plugin-babel` with `reactCompilerPreset()` is applied globally. It processes every
`.ts`/`.tsx` file including `packages/avatar-core`, which contains zero React components — pure
geometry, math, and schema validation. The compiler's analysis pass is expensive and wasted on
non-component code.
**Source**: https://github.com/vitejs/vite-plugin-react/discussions/1148
### 4. Vite 8 (Rolldown) baseline memory regression
Vite 8's Rolldown bundler uses ~1.1 GB within 37 seconds vs ~300 MB for Vite 5. Higher memory
pressure triggers frequent GC cycles that manifest as CPU usage.
**Sources**:
- https://github.com/vitejs/rolldown-vite/issues/577
- https://github.com/rolldown/rolldown/issues/9330
## Changes
All changes are in `/vite.config.ts` (root). No other files are modified.
### Change 1 — Disable polling and scope the watcher
Add a `server` block to the Vite config:
```ts
server: {
watch: {
usePolling: false,
ignored: ['**/node_modules/**', '**/dist/**'],
},
},
```
**Why `usePolling: false`**: macOS FSEvents is reliable and near-zero CPU. The polling
fallback exists for network filesystems (NFS/SMB) which do not apply here.
**Why `ignored`**: prevents chokidar from traversing pnpm's `.pnpm` store and build output
directories. These paths never contain source files that need HMR.
### Change 2 — Scope the Babel React Compiler to app source only
Current config (line 14):
```ts
plugins: [react(), babel({ presets: [reactCompilerPreset()] }), tailwindcss()],
```
Add an `include` filter to the `babel()` call:
```ts
plugins: [
react(),
babel({
presets: [reactCompilerPreset()],
include: [/src\/.*\.[jt]sx$/],
}),
tailwindcss(),
],
```
The pattern `src\/.*\.[jt]sx$` matches only `.jsx`/`.tsx` files under `src/` (the app).
It excludes:
- `packages/avatar-core/` (no React components)
- `.ts` files that are pure logic (no JSX to compile)
### Change 3 — Monitor and consider Vite downgrade (conditional)
If changes 1–2 do not bring CPU below ~30% idle:
1. Run `pnpm dev` and note RSS memory after 60 seconds (`ps -o rss -p $(pgrep -f vite)`).
2. If RSS exceeds 800 MB, the Rolldown memory regression is contributing. Consider pinning
Vite 7 (`"vite": "^7.0.0"`) until Rolldown stabilises. This requires:
- Replacing `@rolldown/plugin-babel` with standard `@vitejs/plugin-react` Babel config
(v5 style), since `@rolldown/plugin-babel` is Vite 8–specific.
- Verifying the Tailwind v4 plugin remains compatible with Vite 7.
This step is **not** part of the default changeset — only pursue it if the first two changes
are insufficient.
## Validation
After applying changes 1–2:
1. `pnpm dev` — verify the server starts and HMR works (edit a `.tsx` in `src/`, confirm
hot reload).
2. Monitor CPU for 30 seconds idle: `top -pid $(pgrep -f vite) -l 5`. Expect < 10% idle CPU
(down from ~200%).
3. Edit a file in `packages/avatar-core/src/` — verify HMR still picks up the change despite
the watcher `ignored` pattern (the alias resolves to source, which is under the project
root and not under `node_modules`).
4. `pnpm build` — verify production build still succeeds (the `server` block does not affect
build).
5. `pnpm test` — verify no test regressions.
## Files modified
| File | Nature of change |
| ---------------- | ------------------------------------------------------------------- |
| `vite.config.ts` | Add `server.watch` config, add `include` filter to `babel()` plugin |
## Out of scope
- Upgrading or downgrading Vite (unless change 3 is triggered).
- Modifying `packages/avatar-core/vite.config.ts` (library build config, not the dev server).
- Changing `vitest.config.ts` (test runner, separate process).
- Tailwind v4 plugin tuning (4.3.3 has no known performance issues per
https://github.com/tailwindlabs/tailwindcss/issues/16911, fixed since 4.0.10).