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
Used Package Manager
yarn
Validations
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:
prewarmModuleGraphwalks everyimportedModulesedge server-side and transforms whatever it hits. It doesn't know about the test file's hoistedvi.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 (andforks/threadsin 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)
forkstransforms the test,consumer.jsand the mockedheavy/index.jsentry. The vm pools additionally transform all 50heavy/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
Used Package Manager
yarn
Validations