diff --git a/docs/config/index.md b/docs/config/index.md
index b5b32314a3cf..26c997aab0aa 100644
--- a/docs/config/index.md
+++ b/docs/config/index.md
@@ -1120,41 +1120,28 @@ Will call [`vi.unstubAllEnvs`](/api/vi#vi-unstuballenvs) before each test.
Will call [`vi.unstubAllGlobals`](/api/vi#vi-unstuballglobals) before each test.
-### transformMode
+### testTransformMode
-- **Type:** `{ web?, ssr? }`
+ - **Type:** `{ web?, ssr? }`
+ - **Version:** Since Vitest 0.32.0
-Determine the transform method of modules
+ Determine the transform method for all modules inported inside a test that matches the glob pattern. By default, relies on the environment. For example, tests with JSDOM environment will process all files with `ssr: false` flag and tests with Node environment process all modules with `ssr: true`.
-#### transformMode.ssr
+ #### testTransformMode.ssr
-- **Type:** `RegExp[]`
-- **Default:** `[/\.([cm]?[jt]sx?|json)$/]`
+ - **Type:** `string[]`
+ - **Default:** `[]`
-Use SSR transform pipeline for the specified files.
-Vite plugins will receive `ssr: true` flag when processing those files.
+ Use SSR transform pipeline for all modules inside specified tests.
+ Vite plugins will receive `ssr: true` flag when processing those files.
-#### transformMode.web
+ #### testTransformMode.web
-- **Type:** `RegExp[]`
-- **Default:** *modules other than those specified in `transformMode.ssr`*
+ - **Type:** `string[]`
+ - **Default:** `[]`
-First do a normal transform pipeline (targeting browser), then do a SSR rewrite to run the code in Node.
-Vite plugins will receive `ssr: false` flag when processing those files.
-
-When you use JSX as component models other than React (e.g. Vue JSX or SolidJS), you might want to config as following to make `.tsx` / `.jsx` transformed as client-side components:
-
-```ts
-import { defineConfig } from 'vitest/config'
-
-export default defineConfig({
- test: {
- transformMode: {
- web: [/\.[jt]sx$/],
- },
- },
-})
-```
+ First do a normal transform pipeline (targeting browser), then do a SSR rewrite to run the code in Node.
+ Vite plugins will receive `ssr: false` flag when processing those files.
### snapshotFormat
diff --git a/examples/react-storybook/package.json b/examples/react-storybook/package.json
index 6fe2707944d3..e14106bac721 100644
--- a/examples/react-storybook/package.json
+++ b/examples/react-storybook/package.json
@@ -28,7 +28,7 @@
"@testing-library/react": "^12.1.5",
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.17",
- "@vitejs/plugin-react": "^1.3.2",
+ "@vitejs/plugin-react": "^4.0.1",
"@vitest/ui": "latest",
"babel-loader": "^8.2.5",
"jsdom": "latest",
diff --git a/examples/react-testing-lib-msw/package.json b/examples/react-testing-lib-msw/package.json
index f6b85b9f8a11..af8d6a2eab41 100644
--- a/examples/react-testing-lib-msw/package.json
+++ b/examples/react-testing-lib-msw/package.json
@@ -19,7 +19,7 @@
"@testing-library/user-event": "^13.5.0",
"@types/react": "^17.0.45",
"@types/react-dom": "^17.0.17",
- "@vitejs/plugin-react": "^1.3.2",
+ "@vitejs/plugin-react": "^4.0.1",
"@vitest/ui": "latest",
"cross-fetch": "^3.1.5",
"jsdom": "latest",
diff --git a/examples/solid/vite.config.mjs b/examples/solid/vite.config.mjs
index 9e837c7e0dd6..6e5124f131b3 100644
--- a/examples/solid/vite.config.mjs
+++ b/examples/solid/vite.config.mjs
@@ -7,9 +7,6 @@ import solid from 'vite-plugin-solid'
export default defineConfig({
test: {
environment: 'jsdom',
- transformMode: {
- web: [/.[jt]sx?/],
- },
threads: false,
isolate: false,
},
diff --git a/examples/vue-jsx/vite.config.ts b/examples/vue-jsx/vite.config.ts
index c71fb1b68c63..e957af21263b 100644
--- a/examples/vue-jsx/vite.config.ts
+++ b/examples/vue-jsx/vite.config.ts
@@ -7,8 +7,5 @@ export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
- transformMode: {
- web: [/.[tj]sx$/],
- },
},
})
diff --git a/packages/vitest/src/integrations/env/edge-runtime.ts b/packages/vitest/src/integrations/env/edge-runtime.ts
index e65db7fbf3ca..9d072eb0aaec 100644
--- a/packages/vitest/src/integrations/env/edge-runtime.ts
+++ b/packages/vitest/src/integrations/env/edge-runtime.ts
@@ -4,6 +4,7 @@ import { populateGlobal } from './utils'
export default ({
name: 'edge-runtime',
+ transformMode: 'ssr',
async setup(global) {
const { EdgeVM } = await importModule('@edge-runtime/vm') as typeof import('@edge-runtime/vm')
const vm = new EdgeVM({
diff --git a/packages/vitest/src/integrations/env/happy-dom.ts b/packages/vitest/src/integrations/env/happy-dom.ts
index 328139fc7ae2..0a0cb1cc89b0 100644
--- a/packages/vitest/src/integrations/env/happy-dom.ts
+++ b/packages/vitest/src/integrations/env/happy-dom.ts
@@ -4,6 +4,7 @@ import { populateGlobal } from './utils'
export default ({
name: 'happy-dom',
+ transformMode: 'web',
async setup(global) {
// happy-dom v3 introduced a breaking change to Window, but
// provides GlobalWindow as a way to use previous behaviour
diff --git a/packages/vitest/src/integrations/env/index.ts b/packages/vitest/src/integrations/env/index.ts
index 4084cb71c6ec..0cbd7261df8d 100644
--- a/packages/vitest/src/integrations/env/index.ts
+++ b/packages/vitest/src/integrations/env/index.ts
@@ -1,4 +1,6 @@
-import type { VitestEnvironment } from '../../types/config'
+import type { BuiltinEnvironment, VitestEnvironment } from '../../types/config'
+import type { VitestExecutor } from '../../node'
+import type { Environment } from '../../types'
import node from './node'
import jsdom from './jsdom'
import happy from './happy-dom'
@@ -19,6 +21,10 @@ export const envPackageNames: Record,
'edge-runtime': '@edge-runtime/vm',
}
+function isBuiltinEnvironment(env: VitestEnvironment): env is BuiltinEnvironment {
+ return env in environments
+}
+
export function getEnvPackageName(env: VitestEnvironment) {
if (env === 'node')
return null
@@ -26,3 +32,17 @@ export function getEnvPackageName(env: VitestEnvironment) {
return (envPackageNames as any)[env]
return `vitest-environment-${env}`
}
+
+export async function loadEnvironment(name: VitestEnvironment, executor: VitestExecutor): Promise {
+ if (isBuiltinEnvironment(name))
+ return environments[name]
+ const packageId = (name[0] === '.' || name[0] === '/') ? name : `vitest-environment-${name}`
+ const pkg = await executor.executeId(packageId)
+ if (!pkg || !pkg.default || typeof pkg.default !== 'object' || typeof pkg.default.setup !== 'function') {
+ throw new Error(
+ `Environment "${name}" is not a valid environment. `
+ + `Path "${packageId}" should export default object with a "setup" method.`,
+ )
+ }
+ return pkg.default
+}
diff --git a/packages/vitest/src/integrations/env/jsdom.ts b/packages/vitest/src/integrations/env/jsdom.ts
index a9c9ed784330..f197e9c3444e 100644
--- a/packages/vitest/src/integrations/env/jsdom.ts
+++ b/packages/vitest/src/integrations/env/jsdom.ts
@@ -28,6 +28,7 @@ function catchWindowErrors(window: Window) {
export default ({
name: 'jsdom',
+ transformMode: 'web',
async setup(global, { jsdom = {} }) {
const {
CookieJar,
diff --git a/packages/vitest/src/integrations/env/node.ts b/packages/vitest/src/integrations/env/node.ts
index e984c343dc23..ef1a01cb1523 100644
--- a/packages/vitest/src/integrations/env/node.ts
+++ b/packages/vitest/src/integrations/env/node.ts
@@ -3,6 +3,7 @@ import type { Environment } from '../../types'
export default ({
name: 'node',
+ transformMode: 'ssr',
async setup(global) {
global.console.Console = Console
return {
diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts
index 9493232739cb..b1cb96922097 100644
--- a/packages/vitest/src/node/config.ts
+++ b/packages/vitest/src/node/config.ts
@@ -289,6 +289,8 @@ export function resolveConfig(
port: defaultBrowserPort,
}
+ resolved.testTransformMode ??= {}
+
return resolved
}
diff --git a/packages/vitest/src/node/plugins/index.ts b/packages/vitest/src/node/plugins/index.ts
index f5d17675a420..a71864ccfc04 100644
--- a/packages/vitest/src/node/plugins/index.ts
+++ b/packages/vitest/src/node/plugins/index.ts
@@ -7,7 +7,7 @@ import { ensurePackageInstalled } from '../pkg'
import { resolveApiServerConfig } from '../config'
import { Vitest } from '../core'
import { generateScopedClassName } from '../../integrations/css/css-modules'
-import { EnvReplacerPlugin } from './envReplacer'
+import { SsrReplacerPlugin } from './ssrReplacer'
import { GlobalSetupPlugin } from './globalSetup'
import { CSSEnablerPlugin } from './cssEnabler'
import { CoverageTransform } from './coverageTransform'
@@ -169,7 +169,7 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest('t
await server.watcher.close()
},
},
- EnvReplacerPlugin(),
+ SsrReplacerPlugin(),
GlobalSetupPlugin(ctx, ctx.logger),
...CSSEnablerPlugin(ctx),
CoverageTransform(ctx),
diff --git a/packages/vitest/src/node/plugins/envReplacer.ts b/packages/vitest/src/node/plugins/ssrReplacer.ts
similarity index 63%
rename from packages/vitest/src/node/plugins/envReplacer.ts
rename to packages/vitest/src/node/plugins/ssrReplacer.ts
index 11268649d8f8..15fd60e42dc9 100644
--- a/packages/vitest/src/node/plugins/envReplacer.ts
+++ b/packages/vitest/src/node/plugins/ssrReplacer.ts
@@ -5,16 +5,17 @@ import { cleanUrl } from 'vite-node/utils'
// so people can reassign envs at runtime
// import.meta.env.VITE_NAME = 'app' -> process.env.VITE_NAME = 'app'
-export function EnvReplacerPlugin(): Plugin {
+export function SsrReplacerPlugin(): Plugin {
return {
name: 'vitest:env-replacer',
enforce: 'pre',
transform(code, id) {
- if (!/\bimport\.meta\.env\b/g.test(code))
+ if (!/\bimport\.meta\.env\b/.test(code) && !/\bimport\.meta\.url\b/.test(code))
return null
let s: MagicString | null = null
- const envs = stripLiteral(code).matchAll(/\bimport\.meta\.env\b/g)
+ const cleanCode = stripLiteral(code)
+ const envs = cleanCode.matchAll(/\bimport\.meta\.env\b/g)
for (const env of envs) {
s ||= new MagicString(code)
@@ -25,6 +26,17 @@ export function EnvReplacerPlugin(): Plugin {
s.overwrite(startIndex, endIndex, 'process.env')
}
+ const urls = cleanCode.matchAll(/\bimport\.meta\.url\b/g)
+
+ for (const env of urls) {
+ s ||= new MagicString(code)
+
+ const startIndex = env.index!
+ const endIndex = startIndex + env[0].length
+
+ s.overwrite(startIndex, endIndex, '__vite_ssr_import_meta__.url')
+ }
+
if (s) {
return {
code: s.toString(),
diff --git a/packages/vitest/src/node/plugins/workspace.ts b/packages/vitest/src/node/plugins/workspace.ts
index 69340e681afe..004bdb76b0a4 100644
--- a/packages/vitest/src/node/plugins/workspace.ts
+++ b/packages/vitest/src/node/plugins/workspace.ts
@@ -7,7 +7,7 @@ import type { WorkspaceProject } from '../workspace'
import type { UserWorkspaceConfig } from '../../types'
import { CoverageTransform } from './coverageTransform'
import { CSSEnablerPlugin } from './cssEnabler'
-import { EnvReplacerPlugin } from './envReplacer'
+import { SsrReplacerPlugin } from './ssrReplacer'
import { GlobalSetupPlugin } from './globalSetup'
import { MocksPlugin } from './mocks'
import { deleteDefineConfig, resolveOptimizerConfig } from './utils'
@@ -117,7 +117,7 @@ export function WorkspaceVitestPlugin(project: WorkspaceProject, options: Worksp
await server.watcher.close()
},
},
- EnvReplacerPlugin(),
+ SsrReplacerPlugin(),
...CSSEnablerPlugin(project),
CoverageTransform(project.ctx),
GlobalSetupPlugin(project, project.ctx.logger),
diff --git a/packages/vitest/src/node/pools/child.ts b/packages/vitest/src/node/pools/child.ts
index 20ba9e7b6824..8ba9fe620c67 100644
--- a/packages/vitest/src/node/pools/child.ts
+++ b/packages/vitest/src/node/pools/child.ts
@@ -112,7 +112,7 @@ export function createChildProcessPool(ctx: Vitest, { execArgv, env }: PoolProce
if (!files?.length)
continue
- const filesByOptions = groupBy(files, ({ project, environment }) => project.getName() + JSON.stringify(environment.options))
+ const filesByOptions = groupBy(files, ({ project, environment }) => project.getName() + JSON.stringify(environment.options) + environment.transformMode)
for (const option in filesByOptions) {
const files = filesByOptions[option]
diff --git a/packages/vitest/src/node/pools/rpc.ts b/packages/vitest/src/node/pools/rpc.ts
index 0b67d50e4b8a..ad05fbcad48d 100644
--- a/packages/vitest/src/node/pools/rpc.ts
+++ b/packages/vitest/src/node/pools/rpc.ts
@@ -1,6 +1,5 @@
import type { RawSourceMap } from 'vite-node'
import type { RuntimeRPC } from '../../types'
-import { getEnvironmentTransformMode } from '../../utils/base'
import type { WorkspaceProject } from '../workspace'
export function createMethodsRPC(project: WorkspaceProject): RuntimeRPC {
@@ -25,12 +24,10 @@ export function createMethodsRPC(project: WorkspaceProject): RuntimeRPC {
const r = await project.vitenode.transformRequest(id)
return r?.map as RawSourceMap | undefined
},
- fetch(id, environment) {
- const transformMode = getEnvironmentTransformMode(project.config, environment)
+ fetch(id, transformMode) {
return project.vitenode.fetchModule(id, transformMode)
},
- resolveId(id, importer, environment) {
- const transformMode = getEnvironmentTransformMode(project.config, environment)
+ resolveId(id, importer, transformMode) {
return project.vitenode.resolveId(id, importer, transformMode)
},
onPathsCollected(paths) {
diff --git a/packages/vitest/src/runtime/child.ts b/packages/vitest/src/runtime/child.ts
index 6d3705b56cbc..3dcd5874acfe 100644
--- a/packages/vitest/src/runtime/child.ts
+++ b/packages/vitest/src/runtime/child.ts
@@ -11,7 +11,7 @@ import { rpcDone } from './rpc'
import { setupInspect } from './inspector'
function init(ctx: ChildContext) {
- const { config } = ctx
+ const { config, environment } = ctx
process.env.VITEST_WORKER_ID = '1'
process.env.VITEST_POOL_ID = '1'
@@ -22,7 +22,7 @@ function init(ctx: ChildContext) {
})
// @ts-expect-error untyped global
- globalThis.__vitest_environment__ = config.environment
+ globalThis.__vitest_environment__ = environment.name
// @ts-expect-error I know what I am doing :P
globalThis.__vitest_worker__ = {
ctx,
@@ -77,8 +77,8 @@ export async function run(ctx: ChildContext) {
try {
init(ctx)
- const { run, executor } = await startViteNode(ctx)
- await run(ctx.files, ctx.config, ctx.environment, executor)
+ const { run, executor, environment } = await startViteNode(ctx)
+ await run(ctx.files, ctx.config, { ...ctx.environment, environment }, executor)
await rpcDone()
}
finally {
diff --git a/packages/vitest/src/runtime/entry.ts b/packages/vitest/src/runtime/entry.ts
index 45586375c4b1..a2c9edaeaa76 100644
--- a/packages/vitest/src/runtime/entry.ts
+++ b/packages/vitest/src/runtime/entry.ts
@@ -2,7 +2,7 @@ import { performance } from 'node:perf_hooks'
import type { VitestRunner, VitestRunnerConstructor } from '@vitest/runner'
import { startTests } from '@vitest/runner'
import { resolve } from 'pathe'
-import type { ContextTestEnvironment, ResolvedConfig } from '../types'
+import type { ResolvedConfig, ResolvedTestEnvironment } from '../types'
import { getWorkerState, resetModules } from '../utils'
import { vi } from '../integrations/vi'
import { distDir } from '../paths'
@@ -89,7 +89,7 @@ async function getTestRunner(config: ResolvedConfig, executor: VitestExecutor):
}
// browser shouldn't call this!
-export async function run(files: string[], config: ResolvedConfig, environment: ContextTestEnvironment, executor: VitestExecutor): Promise {
+export async function run(files: string[], config: ResolvedConfig, environment: ResolvedTestEnvironment, executor: VitestExecutor): Promise {
const workerState = getWorkerState()
await setupGlobalEnv(config)
@@ -104,11 +104,11 @@ export async function run(files: string[], config: ResolvedConfig, environment:
workerState.durations.prepare = performance.now() - workerState.durations.prepare
// @ts-expect-error untyped global
- globalThis.__vitest_environment__ = environment
+ globalThis.__vitest_environment__ = environment.name
workerState.durations.environment = performance.now()
- await withEnv(environment.name, environment.options || config.environmentOptions || {}, executor, async () => {
+ await withEnv(environment, environment.options || config.environmentOptions || {}, async () => {
workerState.durations.environment = performance.now() - workerState.durations.environment
for (const file of files) {
diff --git a/packages/vitest/src/runtime/execute.ts b/packages/vitest/src/runtime/execute.ts
index de7cf1550c07..cbce90c327e6 100644
--- a/packages/vitest/src/runtime/execute.ts
+++ b/packages/vitest/src/runtime/execute.ts
@@ -6,11 +6,14 @@ import { normalize, relative, resolve } from 'pathe'
import { processError } from '@vitest/utils/error'
import type { MockMap } from '../types/mocker'
import { getCurrentEnvironment, getWorkerState } from '../utils/global'
-import type { ContextRPC, ContextTestEnvironment, ResolvedConfig } from '../types'
+import type { ContextRPC, Environment, ResolvedConfig, ResolvedTestEnvironment } from '../types'
import { distDir } from '../paths'
+import { loadEnvironment } from '../integrations/env'
import { VitestMocker } from './mocker'
import { rpc } from './rpc'
+const entryUrl = pathToFileURL(resolve(distDir, 'entry.js')).href
+
export interface ExecuteOptions extends ViteNodeRunnerOptions {
mockMap: MockMap
moduleDirectories?: string[]
@@ -25,8 +28,9 @@ export async function createVitestExecutor(options: ExecuteOptions) {
}
let _viteNode: {
- run: (files: string[], config: ResolvedConfig, environment: ContextTestEnvironment, executor: VitestExecutor) => Promise
+ run: (files: string[], config: ResolvedConfig, environment: ResolvedTestEnvironment, executor: VitestExecutor) => Promise
executor: VitestExecutor
+ environment: Environment
}
export const moduleCache = new ModuleCacheMap()
@@ -61,12 +65,14 @@ export async function startViteNode(ctx: ContextRPC) {
process.on('uncaughtException', e => catchError(e, 'Uncaught Exception'))
process.on('unhandledRejection', e => catchError(e, 'Unhandled Rejection'))
+ let transformMode: 'ssr' | 'web' = ctx.environment.transformMode ?? 'ssr'
+
const executor = await createVitestExecutor({
fetchModule(id) {
- return rpc().fetch(id, ctx.environment.name)
+ return rpc().fetch(id, transformMode)
},
resolveId(id, importer) {
- return rpc().resolveId(id, importer, ctx.environment.name)
+ return rpc().resolveId(id, importer, transformMode)
},
moduleCache,
mockMap,
@@ -76,9 +82,13 @@ export async function startViteNode(ctx: ContextRPC) {
base: config.base,
})
- const { run } = await import(pathToFileURL(resolve(distDir, 'entry.js')).href)
+ const environment = await loadEnvironment(ctx.environment.name, executor)
+ ctx.environment.environment = environment
+ transformMode = ctx.environment.transformMode ?? environment.transformMode ?? 'ssr'
+
+ const { run } = await import(entryUrl)
- _viteNode = { run, executor }
+ _viteNode = { run, executor, environment }
return _viteNode
}
diff --git a/packages/vitest/src/runtime/loader.ts b/packages/vitest/src/runtime/loader.ts
index f226ff761bbb..645a338c69b1 100644
--- a/packages/vitest/src/runtime/loader.ts
+++ b/packages/vitest/src/runtime/loader.ts
@@ -44,13 +44,14 @@ export const resolve: Resolver = async (url, context, next) => {
const { parentURL } = context
const state = getWorkerState()
const resolver = state?.rpc.resolveId
+ const environment = state?.ctx.environment
- if (!parentURL || isNodeBuiltin(url) || !resolver)
+ if (!parentURL || isNodeBuiltin(url) || !resolver || !environment)
return next(url, context, next)
const id = normalizeModuleId(url)
const importer = normalizeModuleId(parentURL)
- const resolved = await resolver(id, importer, state.ctx.environment.name)
+ const resolved = await resolver(id, importer, environment.transformMode ?? environment.environment?.transformMode ?? 'ssr')
let result: ResolveResult
let filepath: string
diff --git a/packages/vitest/src/runtime/setup.node.ts b/packages/vitest/src/runtime/setup.node.ts
index 789a6e69beb7..15625fcad1f7 100644
--- a/packages/vitest/src/runtime/setup.node.ts
+++ b/packages/vitest/src/runtime/setup.node.ts
@@ -2,8 +2,7 @@ import { createRequire } from 'node:module'
import { isatty } from 'node:tty'
import { installSourcemapsSupport } from 'vite-node/source-map'
import { createColors, setupColors } from '@vitest/utils'
-import { environments } from '../integrations/env'
-import type { Environment, ResolvedConfig } from '../types'
+import type { EnvironmentOptions, ResolvedConfig, ResolvedTestEnvironment } from '../types'
import { VitestSnapshotEnvironment } from '../integrations/snapshot/environments/node'
import { getSafeTimers, getWorkerState } from '../utils'
import * as VitestIndex from '../index'
@@ -11,7 +10,6 @@ import { RealDate } from '../integrations/mock/date'
import { expect } from '../integrations/chai'
import { rpc } from './rpc'
import { setupCommonEnv } from './setup.common'
-import type { VitestExecutor } from './execute'
// this should only be used in Node
let globalSetup = false
@@ -157,30 +155,17 @@ export async function setupConsoleLogSpy() {
})
}
-async function loadEnvironment(name: string, executor: VitestExecutor) {
- const pkg = await executor.executeId(`vitest-environment-${name}`)
- if (!pkg || !pkg.default || typeof pkg.default !== 'object' || typeof pkg.default.setup !== 'function') {
- throw new Error(
- `Environment "${name}" is not a valid environment. `
- + `Package "vitest-environment-${name}" should have default export with "setup" method.`,
- )
- }
- return pkg.default
-}
-
export async function withEnv(
- name: ResolvedConfig['environment'],
- options: ResolvedConfig['environmentOptions'],
- executor: VitestExecutor,
+ { environment, name }: ResolvedTestEnvironment,
+ options: EnvironmentOptions,
fn: () => Promise,
) {
- const config: Environment = (environments as any)[name] || await loadEnvironment(name, executor)
// @ts-expect-error untyped global
- globalThis.__vitest_environment__ = config.name || name
+ globalThis.__vitest_environment__ = name
expect.setState({
- environment: config.name || name || 'node',
+ environment: name,
})
- const env = await config.setup(globalThis, options)
+ const env = await environment.setup(globalThis, options)
try {
await fn()
}
diff --git a/packages/vitest/src/runtime/worker.ts b/packages/vitest/src/runtime/worker.ts
index 0db3280c364a..ce2fa1a8b5f4 100644
--- a/packages/vitest/src/runtime/worker.ts
+++ b/packages/vitest/src/runtime/worker.ts
@@ -24,7 +24,7 @@ function init(ctx: WorkerContext) {
})
// @ts-expect-error untyped global
- globalThis.__vitest_environment__ = config.environment
+ globalThis.__vitest_environment__ = config.environment.name
// @ts-expect-error I know what I am doing :P
globalThis.__vitest_worker__ = {
ctx,
@@ -62,8 +62,8 @@ export async function run(ctx: WorkerContext) {
try {
init(ctx)
- const { run, executor } = await startViteNode(ctx)
- await run(ctx.files, ctx.config, ctx.environment, executor)
+ const { run, executor, environment } = await startViteNode(ctx)
+ await run(ctx.files, ctx.config, { ...ctx.environment, environment }, executor)
await rpcDone()
}
finally {
diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts
index 6c2a79706b69..c6cc51bd0f34 100644
--- a/packages/vitest/src/types/config.ts
+++ b/packages/vitest/src/types/config.ts
@@ -79,6 +79,24 @@ export type DepsOptimizationOptions = Omit): Awaitable
}
diff --git a/packages/vitest/src/types/rpc.ts b/packages/vitest/src/types/rpc.ts
index 1e8bbe93d2ac..6f875b088be9 100644
--- a/packages/vitest/src/types/rpc.ts
+++ b/packages/vitest/src/types/rpc.ts
@@ -1,14 +1,16 @@
import type { FetchResult, RawSourceMap, ViteNodeResolveId } from 'vite-node'
import type { CancelReason } from '@vitest/runner'
import type { EnvironmentOptions, ResolvedConfig, VitestEnvironment } from './config'
-import type { UserConsoleLog } from './general'
+import type { Environment, UserConsoleLog } from './general'
import type { SnapshotResult } from './snapshot'
import type { File, TaskResultPack } from './tasks'
import type { AfterSuiteRunMeta } from './worker'
+type TransformMode = 'web' | 'ssr'
+
export interface RuntimeRPC {
- fetch: (id: string, environment: VitestEnvironment) => Promise
- resolveId: (id: string, importer: string | undefined, environment: VitestEnvironment) => Promise
+ fetch: (id: string, environment: TransformMode) => Promise
+ resolveId: (id: string, importer: string | undefined, environment: TransformMode) => Promise
getSourceMap: (id: string, force?: boolean) => Promise
onFinished: (files: File[], errors?: unknown[]) => void
@@ -32,6 +34,14 @@ export interface RunnerRPC {
export interface ContextTestEnvironment {
name: VitestEnvironment
+ environment?: Environment
+ transformMode?: TransformMode
+ options: EnvironmentOptions | null
+}
+
+export interface ResolvedTestEnvironment extends ContextTestEnvironment {
+ name: VitestEnvironment
+ environment: Environment
options: EnvironmentOptions | null
}
diff --git a/packages/vitest/src/utils/base.ts b/packages/vitest/src/utils/base.ts
index aa111023d85c..010a1b0e3107 100644
--- a/packages/vitest/src/utils/base.ts
+++ b/packages/vitest/src/utils/base.ts
@@ -1,4 +1,4 @@
-import type { Arrayable, Nullable, ResolvedConfig, VitestEnvironment } from '../types'
+import type { Arrayable, Nullable } from '../types'
export { notNullish, getCallLastIndex } from '@vitest/utils'
@@ -128,12 +128,6 @@ export function stdout(): NodeJS.WriteStream {
return console._stdout || process.stdout
}
-export function getEnvironmentTransformMode(config: ResolvedConfig, environment: VitestEnvironment) {
- if (!config.deps?.experimentalOptimizer?.ssr?.enabled && !config.deps?.experimentalOptimizer?.web?.enabled)
- return undefined
- return (environment === 'happy-dom' || environment === 'jsdom') ? 'web' : 'ssr'
-}
-
// AggregateError is supported in Node.js 15.0.0+
class AggregateErrorPonyfill extends Error {
errors: unknown[]
diff --git a/packages/vitest/src/utils/test-helpers.ts b/packages/vitest/src/utils/test-helpers.ts
index 3b7d0f0651b5..1f079cc0e3a9 100644
--- a/packages/vitest/src/utils/test-helpers.ts
+++ b/packages/vitest/src/utils/test-helpers.ts
@@ -1,6 +1,6 @@
import { promises as fs } from 'node:fs'
import mm from 'micromatch'
-import type { EnvironmentOptions, VitestEnvironment } from '../types'
+import type { EnvironmentOptions, TransformModePatterns, VitestEnvironment } from '../types'
import type { WorkspaceProject } from '../node/workspace'
import { groupBy } from './base'
@@ -17,6 +17,14 @@ export interface FileByEnv {
envOptions: EnvironmentOptions | null
}
+function getTransformMode(patterns: TransformModePatterns, filename: string): 'web' | 'ssr' | undefined {
+ if (patterns.web && mm.isMatch(filename, patterns.web))
+ return 'web'
+ if (patterns.ssr && mm.isMatch(filename, patterns.ssr))
+ return 'ssr'
+ return undefined
+}
+
export async function groupFilesByEnv(files: (readonly [WorkspaceProject, string])[]) {
const filesWithEnv = await Promise.all(files.map(async ([project, file]) => {
const code = await fs.readFile(file, 'utf-8')
@@ -35,12 +43,15 @@ export async function groupFilesByEnv(files: (readonly [WorkspaceProject, string
// 3. Fallback to global env
env ||= project.config.environment || 'node'
+ const transformMode = getTransformMode(project.config.testTransformMode, file)
+
const envOptions = JSON.parse(code.match(/@(?:vitest|jest)-environment-options\s+?(.+)/)?.[1] || 'null')
return {
file,
project,
environment: {
name: env as VitestEnvironment,
+ transformMode,
options: envOptions ? { [env]: envOptions } as EnvironmentOptions : null,
},
}
diff --git a/packages/web-worker/src/utils.ts b/packages/web-worker/src/utils.ts
index 9a59d4101e5c..758b357a46b4 100644
--- a/packages/web-worker/src/utils.ts
+++ b/packages/web-worker/src/utils.ts
@@ -61,14 +61,14 @@ export function createMessageEvent(data: any, transferOrOptions: StructuredSeria
}
export function getRunnerOptions(): any {
- const { config, ctx, rpc, mockMap, moduleCache } = getWorkerState()
+ const { config, rpc, mockMap, moduleCache } = getWorkerState()
return {
fetchModule(id: string) {
- return rpc.fetch(id, ctx.environment.name)
+ return rpc.fetch(id, 'web')
},
resolveId(id: string, importer?: string) {
- return rpc.resolveId(id, importer, ctx.environment.name)
+ return rpc.resolveId(id, importer, 'web')
},
moduleCache,
mockMap,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 84a3982440cf..ef0b016e5ee1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -565,8 +565,8 @@ importers:
specifier: ^17.0.17
version: 17.0.17
'@vitejs/plugin-react':
- specifier: ^1.3.2
- version: 1.3.2
+ specifier: ^4.0.1
+ version: 4.0.3(vite@4.3.9)
'@vitest/ui':
specifier: latest
version: link:../../packages/ui
@@ -663,8 +663,8 @@ importers:
specifier: ^17.0.17
version: 17.0.17
'@vitejs/plugin-react':
- specifier: ^1.3.2
- version: 1.3.2
+ specifier: ^4.0.1
+ version: 4.0.3(vite@4.3.9)
'@vitest/ui':
specifier: latest
version: link:../../packages/ui
@@ -1820,6 +1820,9 @@ importers:
test/resolve:
devDependencies:
+ happy-dom:
+ specifier: ^9.20.3
+ version: 9.20.3
vitest:
specifier: workspace:*
version: link:../../packages/vitest
@@ -4286,16 +4289,6 @@ packages:
'@babel/plugin-transform-react-jsx': 7.19.0(@babel/core@7.22.5)
dev: true
- /@babel/plugin-transform-react-jsx-self@7.18.6(@babel/core@7.18.13):
- resolution: {integrity: sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.22.5
- dev: true
-
/@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.22.5):
resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==}
engines: {node: '>=6.9.0'}
@@ -4306,16 +4299,6 @@ packages:
'@babel/helper-plugin-utils': 7.22.5
dev: true
- /@babel/plugin-transform-react-jsx-source@7.18.6(@babel/core@7.18.13):
- resolution: {integrity: sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
- dependencies:
- '@babel/core': 7.18.13
- '@babel/helper-plugin-utils': 7.22.5
- dev: true
-
/@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.22.5):
resolution: {integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==}
engines: {node: '>=6.9.0'}
@@ -4326,17 +4309,17 @@ packages:
'@babel/helper-plugin-utils': 7.22.5
dev: true
- /@babel/plugin-transform-react-jsx@7.18.10(@babel/core@7.18.13):
+ /@babel/plugin-transform-react-jsx@7.18.10(@babel/core@7.22.5):
resolution: {integrity: sha512-gCy7Iikrpu3IZjYZolFE4M1Sm+nrh1/6za2Ewj77Z+XirT4TsbJcvOFOyF+fRPwU6AKKK136CZxx6L8AbSFG6A==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
dependencies:
- '@babel/core': 7.18.13
+ '@babel/core': 7.22.5
'@babel/helper-annotate-as-pure': 7.18.6
'@babel/helper-module-imports': 7.22.5
'@babel/helper-plugin-utils': 7.22.5
- '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.18.13)
+ '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.22.5)
'@babel/types': 7.22.5
dev: true
@@ -9525,14 +9508,14 @@ packages:
resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
engines: {node: '>=12.0.0'}
dependencies:
- '@babel/core': 7.18.13
- '@babel/plugin-transform-react-jsx': 7.18.10(@babel/core@7.18.13)
- '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.18.13)
- '@babel/plugin-transform-react-jsx-self': 7.18.6(@babel/core@7.18.13)
- '@babel/plugin-transform-react-jsx-source': 7.18.6(@babel/core@7.18.13)
+ '@babel/core': 7.22.5
+ '@babel/plugin-transform-react-jsx': 7.18.10(@babel/core@7.22.5)
+ '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.22.5)
+ '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.22.5)
+ '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.5)
'@rollup/pluginutils': 4.2.1
react-refresh: 0.13.0
- resolve: 1.22.1
+ resolve: 1.22.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -21782,15 +21765,6 @@ packages:
engines: {node: '>=10'}
dev: true
- /resolve@1.22.1:
- resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
- hasBin: true
- dependencies:
- is-core-module: 2.12.1
- path-parse: 1.0.7
- supports-preserve-symlinks-flag: 1.0.0
- dev: true
-
/resolve@1.22.2:
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
hasBin: true
diff --git a/test/core/test/define.test.ts b/test/core/test/define-ssr.test.ts
similarity index 100%
rename from test/core/test/define.test.ts
rename to test/core/test/define-ssr.test.ts
diff --git a/test/core/test/define-web.test.ts b/test/core/test/define-web.test.ts
new file mode 100644
index 000000000000..3af47b384850
--- /dev/null
+++ b/test/core/test/define-web.test.ts
@@ -0,0 +1,63 @@
+// @vitest-environment jsdom
+
+import { afterAll, expect, test } from 'vitest'
+
+declare let __DEFINE__: string
+declare let __JSON__: any
+declare let __MODE__: string
+declare let SOME: {
+ VARIABLE: string
+ SOME: {
+ VARIABLE: string
+ }
+}
+
+// functions to test that they are not statically replaced
+function get__DEFINE__() {
+ return __DEFINE__
+}
+function get__JSON__() {
+ return __JSON__
+}
+function get__MODE__() {
+ return __MODE__
+}
+
+const MODE = process.env.MODE
+
+afterAll(() => {
+ process.env.MODE = MODE
+})
+
+test('process.env.HELLO_PROCESS is defined on "defined" but exists on process.env', () => {
+ expect('HELLO_PROCESS' in process.env).toBe(true)
+ expect(process.env.HELLO_PROCESS).toBe('hello process')
+})
+
+test('can redeclare standard define', () => {
+ expect(get__DEFINE__()).toBe('defined')
+ __DEFINE__ = 'new defined'
+ expect(get__DEFINE__()).toBe('new defined')
+})
+
+test('can redeclare json object', () => {
+ expect(get__JSON__()).toEqual({ hello: 'world' })
+ __JSON__ = { hello: 'test' }
+ const name = '__JSON__'
+ expect(get__JSON__()).toEqual({ hello: 'test' })
+ expect((globalThis as any)[name]).toEqual({ hello: 'test' })
+})
+
+test('reassigning complicated __MODE__', () => {
+ const env = process.env.MODE
+ expect(get__MODE__()).toBe(env)
+ process.env.MODE = 'development'
+ expect(get__MODE__()).not.toBe('development')
+})
+
+test('dotted defines can be reassigned', () => {
+ expect(SOME.VARIABLE).toBe('variable')
+ expect(SOME.SOME.VARIABLE).toBe('nested variable')
+ SOME.VARIABLE = 'new variable'
+ expect(SOME.VARIABLE).toBe('new variable')
+})
diff --git a/test/resolve/package.json b/test/resolve/package.json
index 9aae7475f41c..3d0613a16e6f 100644
--- a/test/resolve/package.json
+++ b/test/resolve/package.json
@@ -6,6 +6,7 @@
"coverage": "vitest run --coverage"
},
"devDependencies": {
+ "happy-dom": "^9.20.3",
"vitest": "workspace:*"
}
}
diff --git a/test/resolve/vitest.config.ts b/test/resolve/vitest.config.ts
index 38ae8d564abf..f18228bd9474 100644
--- a/test/resolve/vitest.config.ts
+++ b/test/resolve/vitest.config.ts
@@ -2,10 +2,10 @@ import { defineConfig } from 'vite'
export default defineConfig({
test: {
- transformMode: {
- web: [/web\.test\.ts/],
- ssr: [/ssr\.test\.ts/],
- },
+ environmentMatchGlobs: [
+ ['**/web.test.ts', 'happy-dom'],
+ ['**/ssr.test.ts', 'node'],
+ ],
deps: {
external: [/pkg-/],
},