Skip to content

Commit 346d389

Browse files
MO2k4claude
andauthored
fix(vm): don't reuse scripts across vite environments (#11395)
Signed-off-by: Martin Oehlert <453360+MO2k4@users.noreply.github.com> Co-authored-by: Martin Oehlert <453360+MO2k4@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 245f094 commit 346d389

3 files changed

Lines changed: 66 additions & 5 deletions

File tree

‎packages/vitest/src/runtime/moduleRunner/moduleEvaluator.ts‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,22 @@ const isWindows = process.platform === 'win32'
3030
// holds no per-context state (Vite rewrites dynamic imports to
3131
// `__vite_ssr_dynamic_import__`, so no per-context import callback is baked
3232
// in) — only its evaluation has to happen per context. Keyed by module id
33-
// (`mock:` ids stay distinct from their originals).
33+
// (`mock:` ids stay distinct from their originals) and Vite environment: one
34+
// worker can run files of different environments, which transform the same id
35+
// differently.
3436
const vmInlineScriptCache = new Map<string, vm.Script>()
3537

36-
function getVmInlineScript(id: string, wrappedCode: string, options: vm.ScriptOptions): vm.Script {
37-
let script = vmInlineScriptCache.get(id)
38+
function getVmInlineScript(
39+
environment: string,
40+
id: string,
41+
wrappedCode: string,
42+
options: vm.ScriptOptions,
43+
): vm.Script {
44+
const key = `${environment}:${id}`
45+
let script = vmInlineScriptCache.get(key)
3846
if (!script) {
3947
script = new vm.Script(wrappedCode, options)
40-
vmInlineScriptCache.set(id, script)
48+
vmInlineScriptCache.set(key, script)
4149
}
4250
return script
4351
}
@@ -49,6 +57,7 @@ export interface VitestModuleEvaluatorOptions {
4957
injectCjsGlobals?: boolean | undefined
5058
moduleExecutionInfo?: ModuleExecutionInfo
5159
getCurrentTestFilepath?: () => string | undefined
60+
getEnvironmentName?: () => string
5261
compiledFunctionArgumentsNames?: string[]
5362
compiledFunctionArgumentsValues?: unknown[]
5463
getterTracker?: GetterTracker
@@ -411,7 +420,12 @@ export class VitestModuleEvaluator implements ModuleEvaluator {
411420

412421
try {
413422
const initModule = this.vm
414-
? getVmInlineScript(module.id, wrappedCode, options).runInContext(this.vm.context)
423+
? getVmInlineScript(
424+
this.options.getEnvironmentName?.() ?? '',
425+
module.id,
426+
wrappedCode,
427+
options,
428+
).runInContext(this.vm.context)
415429
: vm.runInThisContext(wrappedCode, options)
416430

417431
await initModule(...argumentsValues)

‎packages/vitest/src/runtime/moduleRunner/startVitestModuleRunner.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export function startVitestModuleRunner(options: ContextModuleRunnerOptions): Vi
117117
return state().config.injectCjsGlobals
118118
},
119119
getCurrentTestFilepath: () => state().filepath,
120+
getEnvironmentName: environment,
120121
getterTracker: state().getterTracker,
121122
})
122123

‎test/e2e/test/vm-threads.test.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,52 @@ test.for(['vmThreads', 'vmForks'] as const)(
6666
},
6767
)
6868

69+
// the same module is transformed differently by the client (jsdom) and ssr
70+
// (node) environments, so a worker running both must not reuse one's script
71+
// (env names are interpolated so they don't set this file's own environment)
72+
test.for(['vmThreads', 'vmForks'] as const)(
73+
'%s does not share compiled modules between vite environments',
74+
async (pool) => {
75+
const { stderr, exitCode } = await runInlineTests({
76+
'env.js': `export const env = '__ENV__'`,
77+
'client.test.js': `
78+
// @vitest-environment ${'jsdom'}
79+
import { expect, test } from 'vitest'
80+
import { env } from './env.js'
81+
82+
test('client', () => {
83+
expect(env).toBe('client')
84+
})
85+
`,
86+
'ssr.test.js': `
87+
// @vitest-environment ${'node'}
88+
import { expect, test } from 'vitest'
89+
import { env } from './env.js'
90+
91+
test('ssr', () => {
92+
expect(env).toBe('ssr')
93+
})
94+
`,
95+
'vitest.config.js': `
96+
export default {
97+
plugins: [{
98+
name: 'env-name',
99+
transform(code, id) {
100+
if (id.endsWith('env.js')) {
101+
return code.replace('__ENV__', this.environment.name)
102+
}
103+
},
104+
}],
105+
test: { pool: '${pool}', maxWorkers: 1 },
106+
}
107+
`,
108+
})
109+
110+
expect(stderr).toBe('')
111+
expect(exitCode).toBe(0)
112+
},
113+
)
114+
69115
// vm pools resolve `isolate` to false (isolation comes from a fresh VM
70116
// context per run request), which used to trigger the "single non-isolated
71117
// worker receives all files at once" batching with `maxWorkers: 1` — all

0 commit comments

Comments
 (0)