Skip to content

Commit

Permalink
feat!: transformMode affects only test files, not regular files (#3491)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Jul 11, 2023
1 parent 7c8117a commit 9608bf7
Show file tree
Hide file tree
Showing 33 changed files with 236 additions and 168 deletions.
41 changes: 14 additions & 27 deletions docs/config/index.md
Expand Up @@ -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.<br>
Vite plugins will receive `ssr: true` flag when processing those files.
Use SSR transform pipeline for all modules inside specified tests.<br>
Vite plugins will receive `ssr: true` flag when processing those files.

#### transformMode&#46;web
#### testTransformMode&#46;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.<br>
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.<br>
Vite plugins will receive `ssr: false` flag when processing those files.

### snapshotFormat<NonProjectOption />

Expand Down
2 changes: 1 addition & 1 deletion examples/react-storybook/package.json
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion examples/react-testing-lib-msw/package.json
Expand Up @@ -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",
Expand Down
3 changes: 0 additions & 3 deletions examples/solid/vite.config.mjs
Expand Up @@ -7,9 +7,6 @@ import solid from 'vite-plugin-solid'
export default defineConfig({
test: {
environment: 'jsdom',
transformMode: {
web: [/.[jt]sx?/],
},
threads: false,
isolate: false,
},
Expand Down
3 changes: 0 additions & 3 deletions examples/vue-jsx/vite.config.ts
Expand Up @@ -7,8 +7,5 @@ export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
transformMode: {
web: [/.[tj]sx$/],
},
},
})
1 change: 1 addition & 0 deletions packages/vitest/src/integrations/env/edge-runtime.ts
Expand Up @@ -4,6 +4,7 @@ import { populateGlobal } from './utils'

export default <Environment>({
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({
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/integrations/env/happy-dom.ts
Expand Up @@ -4,6 +4,7 @@ import { populateGlobal } from './utils'

export default <Environment>({
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
Expand Down
22 changes: 21 additions & 1 deletion 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'
Expand All @@ -19,10 +21,28 @@ export const envPackageNames: Record<Exclude<keyof typeof environments, 'node'>,
'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
if (env in envPackageNames)
return (envPackageNames as any)[env]
return `vitest-environment-${env}`
}

export async function loadEnvironment(name: VitestEnvironment, executor: VitestExecutor): Promise<Environment> {
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
}
1 change: 1 addition & 0 deletions packages/vitest/src/integrations/env/jsdom.ts
Expand Up @@ -28,6 +28,7 @@ function catchWindowErrors(window: Window) {

export default <Environment>({
name: 'jsdom',
transformMode: 'web',
async setup(global, { jsdom = {} }) {
const {
CookieJar,
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/integrations/env/node.ts
Expand Up @@ -3,6 +3,7 @@ import type { Environment } from '../../types'

export default <Environment>({
name: 'node',
transformMode: 'ssr',
async setup(global) {
global.console.Console = Console
return {
Expand Down
2 changes: 2 additions & 0 deletions packages/vitest/src/node/config.ts
Expand Up @@ -289,6 +289,8 @@ export function resolveConfig(
port: defaultBrowserPort,
}

resolved.testTransformMode ??= {}

return resolved
}

Expand Down
4 changes: 2 additions & 2 deletions packages/vitest/src/node/plugins/index.ts
Expand Up @@ -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'
Expand Down Expand Up @@ -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),
Expand Down
Expand Up @@ -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)
Expand All @@ -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(),
Expand Down
4 changes: 2 additions & 2 deletions packages/vitest/src/node/plugins/workspace.ts
Expand Up @@ -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'
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/pools/child.ts
Expand Up @@ -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]
Expand Down
7 changes: 2 additions & 5 deletions 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 {
Expand All @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions packages/vitest/src/runtime/child.ts
Expand Up @@ -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'
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions packages/vitest/src/runtime/entry.ts
Expand Up @@ -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'
Expand Down Expand Up @@ -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<void> {
export async function run(files: string[], config: ResolvedConfig, environment: ResolvedTestEnvironment, executor: VitestExecutor): Promise<void> {
const workerState = getWorkerState()

await setupGlobalEnv(config)
Expand All @@ -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) {
Expand Down
22 changes: 16 additions & 6 deletions packages/vitest/src/runtime/execute.ts
Expand Up @@ -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[]
Expand All @@ -25,8 +28,9 @@ export async function createVitestExecutor(options: ExecuteOptions) {
}

let _viteNode: {
run: (files: string[], config: ResolvedConfig, environment: ContextTestEnvironment, executor: VitestExecutor) => Promise<void>
run: (files: string[], config: ResolvedConfig, environment: ResolvedTestEnvironment, executor: VitestExecutor) => Promise<void>
executor: VitestExecutor
environment: Environment
}

export const moduleCache = new ModuleCacheMap()
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
Expand Down

0 comments on commit 9608bf7

Please sign in to comment.