From 9608bf72d89d8eaad530ce77e5d1eee5f45678c1 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 11 Jul 2023 10:56:13 +0200 Subject: [PATCH] feat!: transformMode affects only test files, not regular files (#3491) --- docs/config/index.md | 41 +++++------- examples/react-storybook/package.json | 2 +- examples/react-testing-lib-msw/package.json | 2 +- examples/solid/vite.config.mjs | 3 - examples/vue-jsx/vite.config.ts | 3 - .../src/integrations/env/edge-runtime.ts | 1 + .../vitest/src/integrations/env/happy-dom.ts | 1 + packages/vitest/src/integrations/env/index.ts | 22 ++++++- packages/vitest/src/integrations/env/jsdom.ts | 1 + packages/vitest/src/integrations/env/node.ts | 1 + packages/vitest/src/node/config.ts | 2 + packages/vitest/src/node/plugins/index.ts | 4 +- .../{envReplacer.ts => ssrReplacer.ts} | 18 +++++- packages/vitest/src/node/plugins/workspace.ts | 4 +- packages/vitest/src/node/pools/child.ts | 2 +- packages/vitest/src/node/pools/rpc.ts | 7 +-- packages/vitest/src/runtime/child.ts | 8 +-- packages/vitest/src/runtime/entry.ts | 8 +-- packages/vitest/src/runtime/execute.ts | 22 +++++-- packages/vitest/src/runtime/loader.ts | 5 +- packages/vitest/src/runtime/setup.node.ts | 27 ++------ packages/vitest/src/runtime/worker.ts | 6 +- packages/vitest/src/types/config.ts | 40 ++++++------ packages/vitest/src/types/general.ts | 1 + packages/vitest/src/types/rpc.ts | 16 ++++- packages/vitest/src/utils/base.ts | 8 +-- packages/vitest/src/utils/test-helpers.ts | 13 +++- packages/web-worker/src/utils.ts | 6 +- pnpm-lock.yaml | 58 +++++------------ .../{define.test.ts => define-ssr.test.ts} | 0 test/core/test/define-web.test.ts | 63 +++++++++++++++++++ test/resolve/package.json | 1 + test/resolve/vitest.config.ts | 8 +-- 33 files changed, 236 insertions(+), 168 deletions(-) rename packages/vitest/src/node/plugins/{envReplacer.ts => ssrReplacer.ts} (63%) rename test/core/test/{define.test.ts => define-ssr.test.ts} (100%) create mode 100644 test/core/test/define-web.test.ts 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-/], },