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

Open
#15,539 1 comment 0 reactions 0 assignees View on GitHub
package:vitest
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.