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

9.9 KiB
Raw Blame History

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:

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)
  • .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).