Skip to content

Speed up web vitest suite (~51s -> ~32s): cut per-file import/jsdom overhead - #52

Merged
Aztec03hub merged 1 commit into
mainfrom
speedup-web-vitest
Jun 26, 2026
Merged

Speed up web vitest suite (~51s -> ~32s): cut per-file import/jsdom overhead#52
Aztec03hub merged 1 commit into
mainfrom
speedup-web-vitest

Conversation

@Aztec03hub

Copy link
Copy Markdown
Owner

Summary

The web unit-test suite's wall-clock was dominated by per-file overhead, not test logic. The same module graph (App, mqtt-store, components, bits-ui, the lucide-svelte icon barrel) was re-imported and re-executed, and a fresh jsdom environment created, for every one of the 94 spec files. Actual test execution was already cheap (~16s).

This PR switches the bulk of the suite to Vitest's vmThreads pool, which runs each spec file in its own fresh V8 VM context (full per-file isolation, no cross-file state leakage) while reusing the worker's compiled-module cache across files. That collapses the dominant import/environment cost. No test assertions or test code changed; the only file touched is web/vitest.config.js.

Levers evaluated

Lever Result
isolate: false (threads) Rejected: 111 failures / 14 files. ESM bindings are captured at first import, so the shared vi.mock('api.js') of the mqtt-store leaks across files. Exactly the cross-file coupling the brief warns about.
deps.optimizer.web (lucide / multi-dep) No measurable gain (within noise); multi-dep made it worse.
pool: 'threads' (whole suite) Slower than the forks baseline for this jsdom-heavy workload.
pool: 'vmThreads' (applied) ~halves wall-clock with full isolation.

Under vmThreads the VM's window / location globals are non-configurable, so the 3 specs that replace the whole window/location object (api-base-derivation, connection-status, slash-commands) are routed to a standard threads project via test.projects. Specs that only define a property on window (matchMedia, innerWidth, etc.) run fine under vmThreads. A future spec that reassigns the whole global fails loudly under vmThreads, never silently.

Before -> after (matched-load A/B, 3 runs each)

Metric Before After Delta
wall-clock ~51s ~32s -37%
duration ~47s ~30s -36%
import ~240s ~174s -28%
environment ~46s ~5.4s -88%
transform ~71s ~64s -10%
tests ~16s ~12s -25%

The dominant saving is the per-file jsdom environment cost (-88%) plus reduced module re-import.

Stability gate (5 consecutive runs)

All 5 runs: exit 0, 94 files / 1292 tests passed, 0 unhandled errors, 0 stderr lines.

stab1: wall 32.89s  94 files / 1292 tests  stderr 0  unhandled 0  exit 0
stab2: wall 32.24s  94 files / 1292 tests  stderr 0  unhandled 0  exit 0
stab3: wall 32.69s  94 files / 1292 tests  stderr 0  unhandled 0  exit 0
stab4: wall 32.42s  94 files / 1292 tests  stderr 0  unhandled 0  exit 0
stab5: wall 32.56s  94 files / 1292 tests  stderr 0  unhandled 0  exit 0

The bits-ui scroll-lock drain in tests/setup.js (PR #46) and the clean 0-unhandled / 0-stderr output (PR #48) are preserved. pnpm --dir web build stays green. No skips/excludes added; same 1292 assertions.

🤖 Generated with Claude Code

…e import/jsdom overhead

The web unit-test suite's cost was dominated by per-file overhead, not test
logic: the same module graph (App, mqtt-store, components, bits-ui, the
lucide-svelte icon barrel) was re-imported and re-executed, and a fresh jsdom
environment was created, for every one of the 94 spec files.

Switch the bulk of the suite to vitest's vmThreads pool, which runs each spec
file in its own fresh V8 VM context (full per-file isolation, no cross-file
state leakage) while reusing the worker's compiled-module cache across files.

The three specs that replace the whole window/location global object (not just
define a property on it) cannot run under vmThreads, where those globals are
non-configurable; they are routed to a standard threads project via
test.projects. No test assertions or test code changed.

Measured (matched-load A/B, 3 runs each):
  wall        ~51s  -> ~32s   (-37%)
  duration    ~47s  -> ~30s
  import      ~240s -> ~174s
  environment  ~46s -> ~5.4s  (-88%)
  transform    ~71s -> ~64s
  tests        ~16s -> ~12s

Still 94 files / 1292 tests pass with 0 unhandled errors and 0 stderr noise
(verified 5 consecutive runs). The bits-ui scroll-lock drain in tests/setup.js
(PR #46) and the clean output (PR #48) are preserved.
@Aztec03hub
Aztec03hub merged commit f1d6ffd into main Jun 26, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant