Skip to content

vm pools: prewarmModuleGraph transforms subtrees that vi.mock(..., factory) replaces #11032

Description

@MarshallOfSound

Describe the bug

Was benchmarking 5.0.0-rc.2 on a large, fairly mock-heavy suite and it came out noticeably slower cold than 4.1 despite #10744 (roughly 1.4× wall on a full cold run, ~2× on a single-process shard). Tracked it down to the prewarm from #10708/#10744: prewarmModuleGraph walks every importedModules edge server-side and transforms whatever it hits. It doesn't know about the test file's hoisted vi.mock(specifier, factory) calls, so a factory-mocked module's real implementation and its whole static subtree still get transformed — stuff the on-demand fetch in 4.x (and forks/threads in 5.0) never touches because the mock intercepts first.

It's purely wasted main-thread transform work + GC + fsModuleCache churn, and it scales with how much a suite mocks — for us it about doubled the number of modules transformed per run, and the main-thread profile is dominated by vite transform + GC accordingly.

Looking into pruning the factory-mocked edges when walking from a test file. Will send a PR if I can get it working.

Reproduction

Excuse the LLM bash... Couldn't make a gist for some reason (github having issues)

mkdir prewarm-repro && cd prewarm-repro && npm init -y >/dev/null && npm pkg set type=module && npm i -D vitest@5.0.0-rc.2
mkdir -p heavy && for i in $(seq 50); do echo "export const v$i = $i" > heavy/leaf$i.js; done
seq 50 | sed 's/.*/export * from ".\/leaf&.js"/' > heavy/index.js
echo 'import * as heavy from "./heavy/index.js"; export const count = () => Object.keys(heavy).length' > consumer.js
cat > consumer.test.js <<'JS'
import { expect, test, vi } from 'vitest'
import { count } from './consumer.js'

// the real ./heavy subtree (51 modules) is never loaded: the factory replaces it
vi.mock('./heavy/index.js', () => ({ a: 1 }))

test('uses the mock', () => {
  expect(count()).toBe(1)
})
JS
for pool in forks vmThreads vmForks; do
  rm -rf .cache
  npx vitest run --pool=$pool --fsModuleCache --fsModuleCachePath=.cache > /dev/null
  echo "$pool: $(ls .cache | wc -l) modules transformed"
done
forks: 3 modules transformed
vmThreads: 69 modules transformed
vmForks: 69 modules transformed

forks transforms the test, consumer.js and the mocked heavy/index.js entry. The vm pools additionally transform all 50 heavy/leaf*.js (plus ~15 of vitest's own inlined runtime chunks, which is expected for vm pools) — the leaves are pure overhead, nothing ever evaluates them. 4.1.10 transforms 2–3 modules here on every pool.

System Info

N/A

Used Package Manager

yarn

Validations

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions