- 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.
199 lines
9.9 KiB
Markdown
199 lines
9.9 KiB
Markdown
# 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).
|