Skip to content

fix: cache transform results in getTestDependencies across projects (fix #9855)#9936

Open
seanogdev wants to merge 3 commits intovitest-dev:mainfrom
seanogdev:related-transform-cache
Open

fix: cache transform results in getTestDependencies across projects (fix #9855)#9936
seanogdev wants to merge 3 commits intovitest-dev:mainfrom
seanogdev:related-transform-cache

Conversation

@seanogdev
Copy link
Copy Markdown

@seanogdev seanogdev commented Mar 21, 2026

Summary

When running vitest related with multiple projects, getTestDependencies calls transformRequest on shared files through each project's Vite server independently. This triggers a bug in @vue/compiler-sfc where module-level LRU caches (parseCache and templateAnalysisCache) get desynced across servers, causing compiled output to silently drop template-only component imports.

Reproduction: https://github.com/seanogdev/vue-sfc-cache-poisoning-repro

The problem

@vue/compiler-sfc has three interacting issues:

  1. compiler.parse() returns cached mutable descriptor objects via an LRU(500), shared across all Vite servers in the process
  2. compileTemplate() mutates descriptor.template.ast in place, turning v-if elements (type=1) into IfNodes (type=9)
  3. resolveTemplateAnalysisResult() only walks node.type === 1, so when it re-walks a mutated AST after cache eviction it misses components inside v-if/v-for

In a workspace with a jsdom project and a browser/Playwright project sharing @vitejs/plugin-vue, the redundant transformRequest calls from getTestDependencies cause these caches to desync. The result: <script setup> components used only in <template> get excluded from __returned__ and become undefined at runtime ("Invalid vnode type" errors).

Running vitest related with both projects caused browser test failures. Running vitest related --project <storybook-only> passed cleanly, confirming the cross-project interaction.

The potential fix

A shared Map<string, string[]> caches each file's resolved import paths across all specs within a single filterTestsBySource call. The cache only stores dependency lists (import paths from deps/dynamicDeps), not transformed code. This means each file is only transformed once during dependency analysis, preventing the redundant transformRequest calls that trigger the upstream cache desync.

Test plan

  • Regression test in test/cli/test/vue-sfc-cache-poisoning.test.ts verifies the compiler-sfc cache poisoning mechanism directly using the compiler API
  • Full pipeline test reproduces the exact desync with multiple Vite servers and LRU eviction
  • The end-to-end browser failure was verified against the reproduction repo

Closes #9855

When running `vitest related` with multiple projects, `getTestDependencies`
called `transformRequest` for each project on shared files. This caused
plugins with module-level caches (like `@vitejs/plugin-vue`'s
`descriptorCache`) to be poisoned when the same file was transformed by
multiple Vite server instances.

Adds a `transformCache` that shares dependency results across all specs
within a single `filterTestsBySource` call, so each file is only
transformed once during dependency analysis.

Closes vitest-dev#9855
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 21, 2026

Deploy Preview for vitest-dev ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 0a31f3f
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/69be95321aa7e900086ac380
😎 Deploy Preview https://deploy-preview-9936--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

if (!transformed) {
return
let dependencies: string[]
const cached = transformCache?.get(filepath)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this cause issues if two Vite plugins transform the same file differently? For example if your Node tests dropped some browser specific module imports, the browser tests would get the cached version and see incorrect test dependencies.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache just stores import paths, not transformed code, so different plugin output shouldnt matter here. Issue is probably upstream. But I don't see a harm with this either.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transformed code can have different dependencies though depending on the plugin

Prevents cross-project cache poisoning when different Vite plugins
transform the same file differently per project.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vitest related with multiple projects corrupts Vue SFC compilation via shared plugin cache

3 participants