Vitest code flow

One transform-location field, two purposes

PR #10733 replaces the private _vitest_tmp property with the typed __vitestTmp field. Both flows hand an on-disk path to the module runner, but their triggers and lifetimes remain different.

PR #10733commit 73bcc3erefactor/unify-transform-tmp
persistent cachetemporary IPC copyshared helper or result shape

When each mechanism kicks in

fsModuleCache is a persistent cache. The fork temporary copy is an IPC transport optimization. They are independently enabled.

WorkerfsModuleCache disabledfsModuleCache enabled
Fork workerFork temporary copy
Write transformed code to a per-run file instead of sending the large string through child-process IPC.
Persistent FS cache
Use the persistent entry for cacheable modules. Excluded modules still use a per-run file for fork IPC.
Non-fork workerNormal result
Return through the worker's normal transport.
Persistent FS cache
Use the persistent entry for cacheable modules; otherwise return normally.

Important: the temporary file is not a fallback cache. Cacheable modules use persistent paths. Modules excluded from persistent caching can still use per-run paths to avoid large fork IPC payloads.

Common entry chain

Every module request reaches the same main-process fetcher.

  1. workermodule runner: rpc().fetch()Worker sends a module request.startVitestModuleRunner.ts:145-151
  2. worker transport / RPC boundary
  3. maincreateMethodsRPC().fetch()Main-process RPC handler receives the request.rpc.ts:30-44
  4. mainproject._fetcher()Project-level fetch function is called.rpc.ts:44
  5. mainModuleFetcher.fetch()Branch by persistent-cache availability and worker transport.fetchModule.ts:360-379

mainPersistent cache hit

Read an existing transform from experimental.fsModuleCache.

  1. getCachePath()Resolve or generate the persistent path.fetchModule.ts:174-194
  2. getCachedModule(cachePath)Try the module-graph memo, then disk.fetchModule.ts:222-240
  3. fsCache.getCachedModule()Read transformed code from persistent storage.fetchModule.ts:239
  4. transformResult.__vitestTmp = cachePathHydrate the graph and memoize its backing file.fetchModule.ts:254-259
  5. return { cached: true, tmp: cachePath }Shared filesystem-backed result shape.fetchModule.ts:277-284
  6. worker transport / RPC boundary
  7. workerreadFileSync(result.tmp)Shared consumer: reconstruct source from either path.startVitestModuleRunner.ts:152-154

mainPersistent cache miss

Transform, save to the persistent cache, then return its path.

  1. getCachedModule() → undefinedNo reusable entry exists.fetchModule.ts:128-133
  2. fetchAndProcess()Ask Vite to transform the module.fetchModule.ts:134
  3. cacheResult(result, cachePath)Shared helper: orchestrate write and path return.fetchModule.ts:139
  4. fsCache.saveCachedModule(cachePath, result, ...)Shared write shape, persistent path and metadata.fetchModule.ts:322-329
  5. getCachedResult(result, cachePath)Shared adapter: construct { cached: true, tmp }.fetchModule.ts:455-463
  6. worker transport / RPC boundary
  7. workerreadFileSync(result.tmp)Shared consumer: reconstruct source from either path.startVitestModuleRunner.ts:152-154

mainFork temporary-copy path

Avoid sending a large transformed-code string through child-process IPC when no persistent path exists.

  1. ForksPoolWorker.cacheFs = trueFork workers request filesystem transfer.forksWorker.ts:9-13
  2. PoolRunner → createMethodsRPC({ cacheFs })The flag enters the RPC handler.poolRunner.ts:99-103
  3. project._fetcher(..., cacheFs)RPC forwards the flag.rpc.ts:44
  4. ModuleFetcher.fetch(..., makeTmpCopies = true)The fetcher receives the flag under its local name.fetchModule.ts:368-379
  5. getCachePath() → nullEnter the temporary-copy branch.fetchModule.ts:82-90
  6. fetchAndProcess()Transform and choose a per-run path.fetchModule.ts:90-112
  7. cacheResult(result, tmpFile)Shared helper: orchestrate write and path return.fetchModule.ts:113
  8. fsCache.saveCachedModule(tmpFile, result, ...)Shared write shape, per-run path.fetchModule.ts:322-329
  9. getCachedResult(result, tmpFile)Shared adapter: construct { cached: true, tmp }.fetchModule.ts:455-463
  10. transformResult.__vitestTmp = tmpFileMemoize the backing file for later requests.fetchModule.ts:113-116
  11. mainRPC returns the path-based resultSerialize the small result, not transformed code.rpc.ts:44-57
  12. child-process IPC / RPC boundary
  13. workerreadFileSync(result.tmp)Shared consumer: reconstruct source from either path.startVitestModuleRunner.ts:152-154

Two shared shapes, different jobs

transformResult.__vitestTmp
mainIn-memory memo for later fetches
≠
FetchCachedFileSystemResult.tmp
workerCurrent-request transport field
startVitestModuleRunner.ts:145-155

Why stash the path on transformResult?

It is an in-memory memo for later fetches, not a hidden transport through the current call chain.

current fetch: cachePath | tmpFile → getCachedResult() → result.tmp → RPC boundary → workerlater fetch: transformResult.__vitestTmp → reuse existing path

The fork path avoids writing another temporary file; the persistent path avoids reading and rebuilding the disk-cache entry. The memo disappears when Vite replaces or invalidates the transform.

Key distinction: the persistent path avoids transforming again; the temporary path avoids transferring a large buffer. The consumer only needs an on-disk location, so one typed field represents both.