cloudflare / cloudflare/workers-sdk
[vitest-plugin] Per-file workerd instantiation dominates suite time (98% import overhead, 2% tests); aggregate cost grows with worker count and no reuse option exists
- Dominant language
- TypeScript
- Stars
- 4.5k
- Forks
- 1.5k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 187
Description
## Summary
For integration suites of many small test files, per-file workerd instantiation + worker-bundle import is effectively the entire test bill: our 46-file suite spent **90.9s of a 113s CI run in `import` and 1.4s running tests**. There appears to be no supported way to amortize it — each Vitest worker boots its own workerd and re-imports the full module graph per file, and neither `@cloudflare/vitest-plugin` 1.1.4 nor `@cloudflare/vitest-pool-workers` 0.22.0 exposes a worker/module-graph reuse option (we checked both option schemas).
## Measurements
Worker (Hono + MCP SDK + Drizzle app, `main: src/index.ts`), vitest 4.1.11, plugin 1.1.4, Node 22.
**CI (4-vCPU ubuntu-latest GitHub runner), 46 files / 328 tests:**
```
Duration 113.16s (transform 7.90s, setup 0ms, import 90.94s, tests 1.43s)
```
We then split the suite into two Vitest projects — 34 files that don't touch `cloudflare:test` moved to a plain-node project (they now cost ~0.5s total), leaving 12 genuine integration files on the plugin. Those 12 files still cost ~80s on CI: **~7–11s of instantiation+import per file**.
**Aggregate import cost scales with Vitest worker count** — each worker pays the boot+import independently (local M-series, 12 files):
| maxWorkers | wall | aggregate import |
|---|---|---|
| 1 | 23.0s | 20.8s |
| 4 | 9.6s | 33.2s |
| ~10 (default) | 7.6s | 74.3s |
Local machines have cores to burn, so wall time still improves — but on the 4-vCPU CI runner the redundant work plus contention inverts it (12 files, wall time): default **93.5s**, maxWorkers 1 **94.2s** (per-file module isolation defeats within-worker amortization), maxWorkers 2 **79.9s** (our optimum).
## Why the obvious workarounds don't compose
- **Merging test files** to amortize instantiation runs into #15446 (SELF.fetch degrades superlinearly with cumulative requests per instance) — the two costs push file granularity in opposite directions, and there's no setting that escapes both.
- **Capping workers** (what we shipped) only trades redundancy against contention; the per-file floor remains.
## Ask
An opt-in reuse mode — a shared workerd/module-graph across test files within a worker (storage isolation is a separate concern and per-test snapshotting could remain), or per-worker module-graph caching so the Nth file's import doesn't cost the same as the first. Even documentation of the intended knob would help: right now the only lever that exists is Vitest's `maxWorkers`, and its optimum is hardware-dependent and counterintuitive (more parallelism = more total work).
Happy to produce a synthetic public reproduction (N small test files importing a worker with a heavy dependency graph) if useful — the numbers above are from a private project, but the shape is generic.
Contributor guide
Research direction
Start by reviewing the option schemas for @cloudflare/vitest-plugin 1.1.4 and @cloudflare/vitest-pool-workers 0.22.0, then trace how a worker using main: src/index.ts is instantiated for each Vitest file. Compare the reported maxWorkers measurements and the constraints from #15446. Done means an opt-in reuse or module-graph caching mode is defined and validated without losing the required isolation behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- performance, testing, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100