-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Describe the bug
When running vitest related <file> in a workspace with multiple projects (e.g., a jsdom project and a browser/Playwright project), the getTestDependencies function SSR-transforms Vue SFC files for dependency analysis across ALL projects. This SSR transform poisons @vitejs/plugin-vue's module-level descriptorCache singleton, causing subsequent browser/client transforms to produce incorrect output.
Specifically, for <script setup> components, template-only component imports are omitted from the setup function's __returned__ object, making them undefined in the render function's $setup proxy. This causes [Vue warn]: Invalid vnode type when creating vnode: undefined and components fail to render.
Reproduction
Minimal reproduction: https://github.com/seanogdev/vue-sfc-cache-poisoning-repro
pnpm install
pnpm test # runs: vitest related src/Target.vue --runThe bug only triggers via vitest related (which calls getTestDependencies across all projects). Running vitest run directly passes because it skips the multi-project dependency resolution that causes the cache poisoning.
Environment:
- Vitest workspace with 2 projects:
- Project A: jsdom environment (app unit tests)
- Project B: browser/Playwright environment (Storybook component tests via
@storybook/addon-vitest)
- Both projects share a common Vite config with
@vitejs/plugin-vue
Vue component structure:
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'; // Used ONLY in template
const someRef = ref(0); // Used in script
</script>
<template>
<ChildComponent />
<span>{{ someRef }}</span>
</template>Steps:
vitest related Parent.vue→ FAILS -ChildComponentis undefined in rendervitest --project my-storybook-project Parent.vue→ PASSESvitest Parent.vue(name filter, both projects) → PASSES
Root Cause Analysis
The issue is in the interaction between vitest related's dependency analysis and @vitejs/plugin-vue's caching:
-
getTestDependencies(inpackages/vitest/src/node/specifications.ts) callsproject.vite.environments.ssr.transformRequest(filepath)for each project to build module dependency graphs. -
@vitejs/plugin-vue'sdescriptorCacheis a module-level singletonMap<string, SFCDescriptor>keyed by filename only — no environment/SSR discrimination. When the SSR transform runs for the jsdom project, it creates/mutates a descriptor that's shared with the browser project. -
When
@vue/compiler-sfc'scompileScriptruns for SSR, it processes the<script setup>block differently. Template-only component imports may be excluded from__returned__because SSR rendering handles component resolution differently. -
The subsequent client/browser transform reuses the poisoned descriptor from the shared cache, producing a setup function where
__returned__omits template-only imports:Failing (vitest related):
const __returned__ = { props, modelValue, someRef } // ChildComponent is MISSING
Passing (single project):
const __returned__ = { props, modelValue, someRef, ChildComponent }
-
The compiled render function accesses
$setup["ChildComponent"]which returnsundefined, triggering the "Invalid vnode type" warning.
Evidence
Confirmed by instrumenting the component's render function at runtime:
// Failing run ($setup keys via for..in):
["localVar1", "localVar2", "props", "modelValue", ...]
// ChildComponent and other template-only imports ABSENT
// Passing run ($setup keys):
["localVar1", "localVar2", "props", "modelValue", ..., "ChildComponent"]
// All imports present
The raw import at the module level resolves correctly in both cases (typeof ChildComponent === 'object'), but the $setup proxy used by the compiled render function does not include it.
Expected behavior
vitest related should produce identical test results to running the same tests via vitest --project <name> or vitest <name-filter>.
Possible fixes
- Vitest:
getTestDependenciescould avoid usingenvironments.ssr.transformRequestfor browser-mode projects, or use an isolated transform pipeline for dependency analysis @vitejs/plugin-vue: ThedescriptorCacheshould be keyed byfilename + environmentto prevent cross-environment pollution- Vite Environment API: Plugin state isolation between environments should be enforced
System Info
Vitest: 4.0.18
@vitejs/plugin-vue: 6.0.4
Vue: 3.5.30
Vite: 7.3.1
OS: macOS (Darwin 25.3.0)
Used Package Manager
pnpm
Validations
- Check that you are using the latest version of Vitest
- Read the docs
- Check that there isn't already an issue that reports the same bug