- 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.
9.9 KiB
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-react6.0.5,@rolldown/plugin-babel0.2.3, and@tailwindcss/vite4.3.3. The version sentence in the Problem section reflects earlier manifest ranges, not the current lockfile. fsevents2.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 buildoutDir. Addingnode_modulesand rootdisttoserver.watch.ignoredis 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
- Reword cause 1 as a hypothesis about an FSEvents fallback and capture evidence that the affected process is actually polling.
- Reword cause 2 to acknowledge Vite's existing ignore defaults. Add only project-specific paths if measurements show that they are watched.
- Anchor the Babel include filter to the absolute root
src/directory. The proposed/src\/.*\.[jt]sx$/expression is not root-specific. - Record both manifest ranges and installed lockfile versions, and repeat measurements after any dependency install.
- Start a single strict-port dev server for measurement and resolve its exact PID.
pgrep -f viteis ambiguous on this machine because several unrelated Vite servers run concurrently. - Measure startup separately from at least 30 seconds of idle CPU, an HMR edit under root
src/, and an HMR edit underpackages/avatar-core/src/. - Treat the Vite 7 downgrade as a separate decision that modifies
package.jsonandpnpm-lock.yaml; this contradicts the current "vite.config.ts only" file list. - 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:
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:
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):
plugins: [react(), babel({ presets: [reactCompilerPreset()] }), tailwindcss()],
Add an include filter to the babel() call:
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).tsfiles 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:
- Run
pnpm devand note RSS memory after 60 seconds (ps -o rss -p $(pgrep -f vite)). - 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-babelwith standard@vitejs/plugin-reactBabel config (v5 style), since@rolldown/plugin-babelis Vite 8–specific. - Verifying the Tailwind v4 plugin remains compatible with Vite 7.
- Replacing
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:
pnpm dev— verify the server starts and HMR works (edit a.tsxinsrc/, confirm hot reload).- Monitor CPU for 30 seconds idle:
top -pid $(pgrep -f vite) -l 5. Expect < 10% idle CPU (down from ~200%). - Edit a file in
packages/avatar-core/src/— verify HMR still picks up the change despite the watcherignoredpattern (the alias resolves to source, which is under the project root and not undernode_modules). pnpm build— verify production build still succeeds (theserverblock does not affect build).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).