···9191Duration 5.90s (transform 842ms, setup 543ms, import 2.35s, tests 2.94s, environment 0ms, prepare 3ms)
9292```
93939494+## Node Compile Cache
9595+9696+Vitest supports Node's [on-disk compile cache](https://nodejs.org/api/cli.html#node_compile_cachedir): when the `NODE_COMPILE_CACHE` environment variable points at a directory, the V8 bytecode of Vitest's own modules and of your externalized dependencies is written to disk and reused by later runs instead of being recompiled. Vitest propagates the variable to every worker, and workers persist the modules they compiled when they shut down.
9797+9898+```shell
9999+NODE_COMPILE_CACHE=node_modules/.cache/node-compile-cache vitest
100100+```
101101+102102+The first run with an empty directory pays for serializing the compiled modules, so this is only worth enabling when the directory survives between runs: local runs, or CI pipelines that cache the directory. `NODE_DISABLE_COMPILE_CACHE=1` disables the cache entirely, taking precedence over `NODE_COMPILE_CACHE`.
103103+104104+Note that Vitest automatically disables the compile cache in workers when the `v8` coverage provider is enabled — V8 serializes cached scripts without the source positions that precise coverage relies on.
105105+94106## Pool
9510796108By default Vitest runs tests in `pool: 'forks'`. While `'forks'` pool is better for compatibility issues ([hanging process](/guide/common-errors.html#failed-to-terminate-worker) and [segfaults](/guide/common-errors.html#segfaults-and-native-code-errors)), it may be slightly slower than `pool: 'threads'` in larger projects.
+21
packages/vitest/src/runtime/workers/init.ts
···22import type { MetaEnv, WorkerSetupContext } from '../../types/worker'
33import type { FileSpecification } from '../runner/types'
44import type { VitestWorker } from './types'
55+// default import: `flushCompileCache` only exists since Node 22.10, a named
66+// import would fail to link on older versions
77+import Module from 'node:module'
58import { serializeError } from '@vitest/utils/error'
69import { disableDefaultColors } from 'tinyrainbow'
710import { Traces } from '../../utils/traces'
···241244 case 'stop': {
242245 await runPromise
243246247247+ // Persist this worker's compile cache before the parent tears the
248248+ // worker down — forks are SIGTERM'd and never reach Node's exit-time
249249+ // flush, so without this the cache stays write-only for them. Runs
250250+ // even when teardown throws (the compiled modules are still worth
251251+ // persisting). A no-op when the cache is disabled or was fully loaded
252252+ // from disk, and cheap (~tens of ms) otherwise, so every worker can
253253+ // afford it.
254254+ const persistCompileCache = () => {
255255+ try {
256256+ Module.flushCompileCache?.()
257257+ }
258258+ catch {}
259259+ }
260260+244261 try {
245262 const context = traces.getContextFromCarrier(message.otelCarrier)
246263···256273257274 await traces.finish()
258275276276+ persistCompileCache()
277277+259278 send({ type: 'stopped', error, __vitest_worker_response__ })
260279 }
261280 catch (error) {
281281+ persistCompileCache()
282282+262283 send({ type: 'stopped', error: serializeError(error), __vitest_worker_response__ })
263284 }
264285
+1-18
packages/vitest/vitest.mjs
···11#!/usr/bin/env node
22-import * as module from 'node:module'
33-44-// Enable Node's on-disk compile cache before importing the CLI so both the CLI
55-// graph and (via the inherited env variable) every spawned worker skip V8
66-// recompilation of unchanged modules. `enableCompileCache()` only affects the
77-// current process — child processes pick the cache up from NODE_COMPILE_CACHE.
88-// Respects an explicit NODE_COMPILE_CACHE and NODE_DISABLE_COMPILE_CACHE; the
99-// API is not available before Node 22.8.
1010-try {
1111- const result = module.enableCompileCache?.()
1212- if (result?.directory && !process.env.NODE_COMPILE_CACHE) {
1313- process.env.NODE_COMPILE_CACHE = result.directory
1414- }
1515-}
1616-catch {}
1717-1818-// eslint-disable-next-line antfu/no-top-level-await -- the import must not be hoisted above `enableCompileCache`
1919-await import('./dist/cli.js')
22+import './dist/cli.js'
···11+import { expect, test } from 'vitest'
22+33+test('worker sees the expected compile cache environment', () => {
44+ // set when the spec expects the v8 coverage provider to strip the cache
55+ if (process.env.EXPECT_COVERAGE_STRIPPED) {
66+ expect(process.env.NODE_COMPILE_CACHE).toBeUndefined()
77+ expect(process.env.NODE_DISABLE_COMPILE_CACHE).toBe('1')
88+ }
99+ // gate on the discriminator between the spec's runs, not on
1010+ // EXPECTED_COMPILE_CACHE_DIR itself — if the spec's env stops propagating,
1111+ // this fails loudly (toBe(undefined)) instead of passing vacuously
1212+ else if (!process.env.NODE_DISABLE_COMPILE_CACHE) {
1313+ expect(process.env.NODE_COMPILE_CACHE).toBe(process.env.EXPECTED_COMPILE_CACHE_DIR)
1414+ }
1515+ expect(document).toBeDefined()
1616+})