Skip to content

vitest's per-module transform costs 3.7x on the noise graph (162.11s vs 43.63s) #267

Description

@wormeyman

Measured 2026-08-18 while refuting the bun/deno question. The CI gate wall is
set by the four tests shards. The largest single lever measured so far is not
the runtime, the engine, or the test environment - it is vitest's per-module
transform
.

Recording it so it is not re-derived. No fix is proposed here.

The measurement

test/findIslands.spec.ts, the suite's heaviest file, 11 tests, all 11
passing both ways
:

arm duration
as it ships 162.11s
its three src/ imports pre-bundled by esbuild into one file 43.63s

3.7x, 118 seconds on one file. Reproduced on a microbenchmark over the same
module graph: 21.86s against 5.46s - and 5.46s matches plain node's 5.40s on
identical work. So bundling recovers the whole gap and lands on the
no-framework floor.

For scale, the full local suite (229 files, 2020 tests, 254.87s wall, 12 cores)
decomposes as tests 67.3%, import 29.8%, environment 2.1%, transform 0.7%.
The 0.7% is the transpile line item. "Import" is V8 executing module bodies,
and that is where this cost actually lands.

What it is NOT - each ruled out by measurement, do not re-test these

  • Not the runtime. Plain node 5.40s, plain deno 5.17s, plain bun 5.97s on
    the same work.
  • Not happy-dom. Re-run under environment: node with only happy-dom's
    ImageData installed: still 20.09s, with environment 0ms.
  • Not pool or isolate. --pool=forks, --no-isolate, and both
    together all land at 20.2-20.4s.
  • Not reachable from deps.optimizer. Enabled for web and ssr with the
    noise entries in optimizeDeps.include: 21.03s, i.e. nothing. That knob
    pre-bundles node_modules, not source.

One trap worth repeating: the first environment: node attempt looked like a
40x win at 568ms and was a failure - ImageData is not defined. Read the
pass/fail line before believing a number.

Open

The mechanism is not isolated. Two candidates, and nothing measured so far
distinguishes them:

  1. vite-node's per-module ESM wrappers. The graph is 99 modules, and
    isolate: true is required here, so every spec file re-executes all of them.
  2. Source-map instrumentation.

Why this is filed rather than fixed

Two things bound how much to want a fix:

  • Pre-bundling src/ for tests breaks vi.mock of source modules, and it
    means the suite tests a bundle rather than the shipped module graph. Against
    this repo's byte-exactness invariant that is a real cost, not a detail.
  • Port phase 7: cut over and delete the TypeScript math #227 already targets the same cost from the other side. The tax falls on
    the TypeScript noise math specifically, so deleting that math removes the math
    cost and its transform tax together.

The Rust port's own measured 7.5-13.2x is not contaminated by this. That
arm was run under plain node, not vitest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesttoolchain-ciBuild, test, CI, dependencies, developer tooling

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions