···121121```
122122123123::: warning
124124-Vitest also injects an instance of `ViteNodeRunner` as `__vitest_executor` property. You can use it to process files in `importFile` method (this is default behavior of `TestRunner` and `BenchmarkRunner`).
124124+Vitest also injects an instance of `ModuleRunner` from `vite/module-runner` as `moduleRunner` property. You can use it to process files in `importFile` method (this is default behavior of `TestRunner` and `BenchmarkRunner`).
125125126126-`ViteNodeRunner` exposes `executeId` method, which is used to import test files in a Vite-friendly environment. Meaning, it will resolve imports and transform file content at runtime so that Node can understand it:
126126+`ModuleRunner` exposes `import` method, which is used to import test files in a Vite-friendly environment. Meaning, it will resolve imports and transform file content at runtime so that Node can understand it:
127127128128```ts
129129export default class Runner {
130130 async importFile(filepath: string) {
131131- await this.__vitest_executor.executeId(filepath)
131131+ await this.moduleRunner.import(filepath)
132132 }
133133}
134134```
+8-30
docs/config/index.md
···233233234234#### deps.optimizer {#deps-optimizer}
235235236236-- **Type:** `{ ssr?, web? }`
236236+- **Type:** `{ ssr?, client? }`
237237- **See also:** [Dep Optimization Options](https://vitejs.dev/config/dep-optimization-options.html)
238238239239Enable dependency optimization. If you have a lot of tests, this might improve their performance.
···245245- Your `alias` configuration is now respected inside bundled packages
246246- Code in your tests is running closer to how it's running in the browser
247247248248-Be aware that only packages in `deps.optimizer?.[mode].include` option are bundled (some plugins populate this automatically, like Svelte). You can read more about available options in [Vite](https://vitejs.dev/config/dep-optimization-options.html) docs (Vitest doesn't support `disable` and `noDiscovery` options). By default, Vitest uses `optimizer.web` for `jsdom` and `happy-dom` environments, and `optimizer.ssr` for `node` and `edge` environments, but it is configurable by [`transformMode`](#testtransformmode).
248248+Be aware that only packages in `deps.optimizer?.[mode].include` option are bundled (some plugins populate this automatically, like Svelte). You can read more about available options in [Vite](https://vitejs.dev/config/dep-optimization-options.html) docs (Vitest doesn't support `disable` and `noDiscovery` options). By default, Vitest uses `optimizer.client` for `jsdom` and `happy-dom` environments, and `optimizer.ssr` for `node` and `edge` environments.
249249250250This options also inherits your `optimizeDeps` configuration (for web Vitest will extend `optimizeDeps`, for ssr - `ssr.optimizeDeps`). If you redefine `include`/`exclude` option in `deps.optimizer` it will extend your `optimizeDeps` when running tests. Vitest automatically removes the same options from `include`, if they are listed in `exclude`.
251251···260260261261Enable dependency optimization.
262262263263-#### deps.web {#deps-web}
263263+#### deps.client {#deps-client}
264264265265- **Type:** `{ transformAssets?, ... }`
266266267267-Options that are applied to external files when transform mode is set to `web`. By default, `jsdom` and `happy-dom` use `web` mode, while `node` and `edge` environments use `ssr` transform mode, so these options will have no affect on files inside those environments.
267267+Options that are applied to external files when the environment is set to `client`. By default, `jsdom` and `happy-dom` use `client` environment, while `node` and `edge` environments use `ssr`, so these options will have no affect on files inside those environments.
268268269269Usually, files inside `node_modules` are externalized, but these options also affect files in [`server.deps.external`](#server-deps-external).
270270271271-#### deps.web.transformAssets
271271+#### deps.client.transformAssets
272272273273- **Type:** `boolean`
274274- **Default:** `true`
···281281At the moment, this option only works with [`vmThreads`](#vmthreads) and [`vmForks`](#vmforks) pools.
282282:::
283283284284-#### deps.web.transformCss
284284+#### deps.client.transformCss
285285286286- **Type:** `boolean`
287287- **Default:** `true`
···294294At the moment, this option only works with [`vmThreads`](#vmthreads) and [`vmForks`](#vmforks) pools.
295295:::
296296297297-#### deps.web.transformGlobPattern
297297+#### deps.client.transformGlobPattern
298298299299- **Type:** `RegExp | RegExp[]`
300300- **Default:** `[]`
···560560561561export default <Environment>{
562562 name: 'custom',
563563- transformMode: 'ssr',
563563+ viteEnvironment: 'ssr',
564564 setup() {
565565 // custom setup
566566 return {
···16751675- **Default:** `false`
1676167616771677Will call [`vi.unstubAllGlobals`](/api/vi#vi-unstuballglobals) before each test.
16781678-16791679-### testTransformMode {#testtransformmode}
16801680-16811681- - **Type:** `{ web?, ssr? }`
16821682-16831683- Determine the transform method for all modules imported 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`.
16841684-16851685- #### testTransformMode.ssr
16861686-16871687- - **Type:** `string[]`
16881688- - **Default:** `[]`
16891689-16901690- Use SSR transform pipeline for all modules inside specified tests.<br>
16911691- Vite plugins will receive `ssr: true` flag when processing those files.
16921692-16931693- #### testTransformMode.web
16941694-16951695- - **Type:** `string[]`
16961696- - **Default:** `[]`
16971697-16981698- First do a normal transform pipeline (targeting browser), then do a SSR rewrite to run the code in Node.<br>
16991699- Vite plugins will receive `ssr: false` flag when processing those files.
1700167817011679### snapshotFormat<NonProjectOption />
17021680
+2-2
docs/guide/environment.md
···48484949export default <Environment>{
5050 name: 'custom',
5151- transformMode: 'ssr',
5151+ viteEnvironment: 'ssr',
5252 // optional - only if you support "experimental-vm" pool
5353 async setupVM() {
5454 const vm = await import('node:vm')
···7474```
75757676::: warning
7777-Vitest requires `transformMode` option on environment object. It should be equal to `ssr` or `web`. This value determines how plugins will transform source code. If it's set to `ssr`, plugin hooks will receive `ssr: true` when transforming or resolving files. Otherwise, `ssr` is set to `false`.
7777+Vitest requires `viteEnvironment` option on environment object (fallbacks to the Vitest environment name by default). It should be equal to `ssr`, `client` or any custom [Vite environment](https://vite.dev/guide/api-environment) name. This value determines which environment is used to process file.
7878:::
79798080You also have access to default Vitest environments through `vitest/environments` entry:
+17-1
docs/guide/migration.md
···145145```
146146:::
147147148148+### Replacing `vite-node` with [Module Runner](https://vite.dev/guide/api-environment-runtimes.html#modulerunner)
149149+150150+Module Runner is a successor to `vite-node` implemented directly in Vite. Vitest now uses it directly instead of having a wrapper around Vite SSR handler. This means that certain features are no longer available:
151151+152152+- `VITE_NODE_DEPS_MODULE_DIRECTORIES` environment variable was replaced with `VITEST_MODULE_DIRECTORIES`
153153+- Vitest no longer injects `__vitest_executor` into every [test runner](/advanced/runner). Instead, it injects `moduleRunner` which is an instance of [`ModuleRunner`](https://vite.dev/guide/api-environment-runtimes.html#modulerunner)
154154+- `vitest/execute` entry point was removed. It was always meant to be internal
155155+- [Custom environments](/guide/environment) no longer need to provide a `transformMode` property. Instead, provide `viteEnvironment`. If it is not provided, Vitest will use the environment name to transform files on the server (see [`server.environments`](https://vite.dev/guide/api-environment-instances.html))
156156+- `vite-node` is no longer a dependency of Vitest
157157+- `deps.optimizer.web` was renamed to [`deps.optimizer.client`](/config/#deps-optimizer-client). You can also use any custom names to apply optimizer configs when using other server environments
158158+159159+Vite has its own externalization mechanism, but we decided to keep using the old one to reduce the amount of breaking changes. You can keep using [`server.deps`](/config/#server-deps) to inline or externalize packages.
160160+161161+This update should not be noticeable unless you rely on advanced features mentioned above.
162162+148163### Deprecated APIs are Removed
149164150165Vitest 4.0 removes some deprecated APIs, including:
···152167- `poolMatchGlobs` config option. Use [`projects`](/guide/projects) instead.
153168- `environmentMatchGlobs` config option. Use [`projects`](/guide/projects) instead.
154169- `workspace` config option. Use [`projects`](/guide/projects) instead.
170170+- `deps.external`, `deps.inline`, `deps.fallbackCJS` config options. Use `server.deps.external`, `server.deps.inline`, or `server.deps.fallbackCJS` instead.
155171156156-This release also removes all deprecated types. This finally fixes an issue where Vitest accidentally pulled in `node` types (see [#5481](https://github.com/vitest-dev/vitest/issues/5481) and [#6141](https://github.com/vitest-dev/vitest/issues/6141)).
172172+This release also removes all deprecated types. This finally fixes an issue where Vitest accidentally pulled in `@types/node` (see [#5481](https://github.com/vitest-dev/vitest/issues/5481) and [#6141](https://github.com/vitest-dev/vitest/issues/6141)).
157173158174## Migrating from Jest {#jest}
159175
···11+// TODO: this is all copy pasted from Vite - can they expose a module that exports only constants?
22+33+export const KNOWN_ASSET_TYPES: string[] = [
44+ // images
55+ 'apng',
66+ 'bmp',
77+ 'png',
88+ 'jpe?g',
99+ 'jfif',
1010+ 'pjpeg',
1111+ 'pjp',
1212+ 'gif',
1313+ 'svg',
1414+ 'ico',
1515+ 'webp',
1616+ 'avif',
1717+1818+ // media
1919+ 'mp4',
2020+ 'webm',
2121+ 'ogg',
2222+ 'mp3',
2323+ 'wav',
2424+ 'flac',
2525+ 'aac',
2626+2727+ // fonts
2828+ 'woff2?',
2929+ 'eot',
3030+ 'ttf',
3131+ 'otf',
3232+3333+ // other
3434+ 'webmanifest',
3535+ 'pdf',
3636+ 'txt',
3737+]
3838+3939+export const KNOWN_ASSET_RE: RegExp = new RegExp(
4040+ `\\.(${KNOWN_ASSET_TYPES.join('|')})$`,
4141+)
4242+export const CSS_LANGS_RE: RegExp
4343+ = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/
4444+/**
4545+ * Prefix for resolved Ids that are not valid browser import specifiers
4646+ */
4747+export const VALID_ID_PREFIX = `/@id/`
4848+4949+/**
5050+ * Plugins that use 'virtual modules' (e.g. for helper functions), prefix the
5151+ * module ID with `\0`, a convention from the rollup ecosystem.
5252+ * This prevents other plugins from trying to process the id (like node resolution),
5353+ * and core features like sourcemaps can use this info to differentiate between
5454+ * virtual modules and regular files.
5555+ * `\0` is not a permitted char in import URLs so we have to replace them during
5656+ * import analysis. The id will be decoded back before entering the plugins pipeline.
5757+ * These encoded virtual ids are also prefixed by the VALID_ID_PREFIX, so virtual
5858+ * modules in the browser end up encoded as `/@id/__x00__{id}`
5959+ */
6060+export const NULL_BYTE_PLACEHOLDER = `__x00__`
+42
packages/utils/src/helpers.ts
···11import type { Arrayable, Nullable } from './types'
22+import { NULL_BYTE_PLACEHOLDER, VALID_ID_PREFIX } from './constants'
2334interface CloneOptions {
45 forceWritable?: boolean
···54555556export function slash(path: string): string {
5657 return path.replace(/\\/g, '/')
5858+}
5959+6060+const postfixRE = /[?#].*$/
6161+export function cleanUrl(url: string): string {
6262+ return url.replace(postfixRE, '')
6363+}
6464+6565+const externalRE = /^(?:[a-z]+:)?\/\//
6666+export const isExternalUrl = (url: string): boolean => externalRE.test(url)
6767+6868+/**
6969+ * Prepend `/@id/` and replace null byte so the id is URL-safe.
7070+ * This is prepended to resolved ids that are not valid browser
7171+ * import specifiers by the importAnalysis plugin.
7272+ */
7373+export function wrapId(id: string): string {
7474+ return id.startsWith(VALID_ID_PREFIX)
7575+ ? id
7676+ : VALID_ID_PREFIX + id.replace('\0', NULL_BYTE_PLACEHOLDER)
7777+}
7878+7979+/**
8080+ * Undo {@link wrapId}'s `/@id/` and null byte replacements.
8181+ */
8282+export function unwrapId(id: string): string {
8383+ return id.startsWith(VALID_ID_PREFIX)
8484+ ? id.slice(VALID_ID_PREFIX.length).replace(NULL_BYTE_PLACEHOLDER, '\0')
8585+ : id
8686+}
8787+8888+export function withTrailingSlash(path: string): string {
8989+ if (path[path.length - 1] !== '/') {
9090+ return `${path}/`
9191+ }
9292+ return path
9393+}
9494+9595+const bareImportRE = /^(?![a-z]:)[\w@](?!.*:\/\/)/i
9696+9797+export function isBareImport(id: string): boolean {
9898+ return bareImportRE.test(id)
5799}
5810059101// convert RegExp.toString to RegExp
···11+import { expect, test } from 'vitest'
22+// @ts-expect-error not typed import
33+import * as client from '/@vite/client'
44+55+test('client is imported', () => {
66+ expect(client).toHaveProperty('createHotContext')
77+})
···11+import { external as viteEnvironmentExternal } from '@test/vite-environment-external'
22+import { external as viteExternal } from '@test/vite-external'
33+14import { describe, expect, it } from 'vitest'
2536// @ts-expect-error is not typed with imports
···37403841// @ts-expect-error is not typed with imports
3942import * as nestedDefaultExternalCjs from '../src/external/nested-default-cjs'
4040-4143import c, { d } from '../src/module-esm'
4444+4245import * as timeout from '../src/timeout'
4646+4747+it('extect vite.noExternal to be respected', () => {
4848+ expect(viteExternal).toBe(false)
4949+ expect(viteEnvironmentExternal).toBe(false)
5050+})
43514452it('doesn\'t when extending module', () => {
4553 expect(() => Object.assign(globalThis, timeout)).not.toThrow()
-1
test/core/test/require.test.ts
···11// @vitest-environment jsdom
2233-// import { KNOWN_ASSET_RE } from 'vite-node/constants'
43import { describe, expect, it } from 'vitest'
5465const _require = require
···1919 "reportCoverage with {\\"allTestsRun\\":true}"
2020 ],
2121 "coverageReports": [
2222- "{\\"coverage\\":{\\"customCoverage\\":\\"Coverage report passed from workers to main thread\\"},\\"testFiles\\":[\\"fixtures/test/even.test.ts\\"],\\"transformMode\\":\\"ssr\\",\\"projectName\\":\\"\\"}",
2323- "{\\"coverage\\":{\\"customCoverage\\":\\"Coverage report passed from workers to main thread\\"},\\"testFiles\\":[\\"fixtures/test/math.test.ts\\"],\\"transformMode\\":\\"ssr\\",\\"projectName\\":\\"\\"}"
2222+ "{\\"coverage\\":{\\"customCoverage\\":\\"Coverage report passed from workers to main thread\\"},\\"testFiles\\":[\\"fixtures/test/even.test.ts\\"],\\"environment\\":\\"ssr\\",\\"projectName\\":\\"\\"}",
2323+ "{\\"coverage\\":{\\"customCoverage\\":\\"Coverage report passed from workers to main thread\\"},\\"testFiles\\":[\\"fixtures/test/math.test.ts\\"],\\"environment\\":\\"ssr\\",\\"projectName\\":\\"\\"}"
2424 ],
2525 "transformedFiles": [
2626 "<process-cwd>/fixtures/src/even.ts",
+3-3
test/reporters/tests/utils.test.ts
···11-import type { ViteNodeRunner } from 'vite-node/client'
11+import type { ModuleRunner } from 'vite/module-runner'
22import type { Vitest } from 'vitest/node'
33/**
44 * @format
···11111212const customReporterPath = resolve(import.meta.dirname, '../src/custom-reporter.js')
1313const fetchModule = {
1414- executeId: (id: string) => import(id),
1515-} as ViteNodeRunner
1414+ import: (id: string) => import(id),
1515+} as ModuleRunner
1616const ctx = {
1717 runner: fetchModule,
1818} as Vitest
···77 MockInstance,
88} from '@vitest/spy'
99import type { RuntimeOptions, SerializedConfig } from '../runtime/config'
1010-import type { VitestMocker } from '../runtime/mocker'
1010+import type { VitestMocker } from '../runtime/moduleRunner/moduleMocker'
1111import type { MockFactoryWithHelper, MockOptions } from '../types/mocker'
1212import { fn, isMockFunction, mocks, spyOn } from '@vitest/spy'
1313import { assertTypes, createSimpleStackTrace } from '@vitest/utils'
···687687 },
688688689689 stubEnv(name: string, value: string | boolean | undefined) {
690690+ const state = getWorkerState()
691691+ const env = state.metaEnv
690692 if (!_stubsEnv.has(name)) {
691691- _stubsEnv.set(name, process.env[name])
693693+ _stubsEnv.set(name, env[name])
692694 }
693695 if (_envBooleans.includes(name)) {
694694- process.env[name] = value ? '1' : ''
696696+ env[name] = value ? '1' : ''
695697 }
696698 else if (value === undefined) {
697697- delete process.env[name]
699699+ delete env[name]
698700 }
699701 else {
700700- process.env[name] = String(value)
702702+ env[name] = String(value)
701703 }
702704 return utils
703705 },
···716718 },
717719718720 unstubAllEnvs() {
721721+ const state = getWorkerState()
722722+ const env = state.metaEnv
719723 _stubsEnv.forEach((original, name) => {
720724 if (original === undefined) {
721721- delete process.env[name]
725725+ delete env[name]
722726 }
723727 else {
724724- process.env[name] = original
728728+ env[name] = original
725729 }
726730 })
727731 _stubsEnv.clear()
···729733 },
730734731735 resetModules() {
732732- resetModules(workerState.moduleCache as any)
736736+ resetModules(workerState.evaluatedModules)
733737 return utils
734738 },
735739
+13-18
packages/vitest/src/node/core.ts
···22import type { Awaitable } from '@vitest/utils'
33import type { Writable } from 'node:stream'
44import type { ViteDevServer } from 'vite'
55+import type { ModuleRunner } from 'vite/module-runner'
56import type { SerializedCoverageConfig } from '../runtime/config'
67import type { ArgumentsType, ProvidedContext, UserConsoleLog } from '../types/general'
78import type { CliOptions } from './cli/cli-api'
···1516import { SnapshotManager } from '@vitest/snapshot/manager'
1617import { noop, toArray } from '@vitest/utils'
1718import { normalize, relative } from 'pathe'
1818-import { ViteNodeRunner } from 'vite-node/client'
1919-import { ViteNodeServer } from 'vite-node/server'
2019import { version } from '../../package.json' with { type: 'json' }
2120import { WebSocketReporter } from '../api/setup'
2221import { distDir } from '../paths'
···2625import { VitestCache } from './cache'
2726import { resolveConfig } from './config/resolveConfig'
2827import { getCoverageProvider } from './coverage'
2828+import { ServerModuleRunner } from './environments/serverRunner'
2929import { FilesNotFoundError } from './errors'
3030import { Logger } from './logger'
3131import { VitestPackageInstaller } from './packageInstaller'
···3535import { BlobReporter, readBlobs } from './reporters/blob'
3636import { HangingProcessReporter } from './reporters/hanging-process'
3737import { createBenchmarkReporters, createReporters } from './reporters/utils'
3838+import { VitestResolver } from './resolver'
3839import { VitestSpecifications } from './specifications'
3940import { StateManager } from './state'
4041import { TestRun } from './test-run'
···9192 /** @internal */ _browserSessions = new BrowserSessions()
9293 /** @internal */ _cliOptions: CliOptions = {}
9394 /** @internal */ reporters: Reporter[] = []
9494- /** @internal */ vitenode: ViteNodeServer = undefined!
9595- /** @internal */ runner: ViteNodeRunner = undefined!
9595+ /** @internal */ runner!: ModuleRunner
9696 /** @internal */ _testRun: TestRun = undefined!
9797+ /** @internal */ _resolver!: VitestResolver
97989899 private isFirstRun = true
99100 private restartsCount = 0
···220221 this.watcher.registerWatcher()
221222 }
222223223223- this.vitenode = new ViteNodeServer(server, this.config.server)
224224-225225- const node = this.vitenode
226226- this.runner = new ViteNodeRunner({
227227- root: server.config.root,
228228- base: server.config.base,
229229- fetchModule(id: string) {
230230- return node.fetchModule(id)
231231- },
232232- resolveId(id: string, importer?: string) {
233233- return node.resolveId(id, importer)
234234- },
235235- })
224224+ this._resolver = new VitestResolver(server.config.cacheDir, resolved)
225225+ const environment = server.environments.__vitest__
226226+ this.runner = new ServerModuleRunner(
227227+ environment,
228228+ this._resolver,
229229+ resolved,
230230+ )
236231237232 if (this.config.watch) {
238233 // hijack server restart
···387382 * @param moduleId The ID of the module in Vite module graph
388383 */
389384 public import<T>(moduleId: string): Promise<T> {
390390- return this.runner.executeId(moduleId)
385385+ return this.runner.import(moduleId)
391386 }
392387393388 private async resolveProjects(cliOptions: UserConfig): Promise<TestProject[]> {
+14-17
packages/vitest/src/node/coverage.ts
···66import type { AfterSuiteRunMeta } from '../types/general'
77import { existsSync, promises as fs, readdirSync, writeFileSync } from 'node:fs'
88import path from 'node:path'
99+import { cleanUrl, slash } from '@vitest/utils'
910import { relative, resolve } from 'pathe'
1011import pm from 'picomatch'
1112import { glob } from 'tinyglobby'
1213import c from 'tinyrainbow'
1313-import { cleanUrl, slash } from 'vite-node/utils'
1414import { coverageConfigDefaults } from '../defaults'
1515import { resolveCoverageReporters } from '../node/config/resolveConfig'
1616import { resolveCoverageProviderModule } from '../utils/coverage'
···4242type CoverageFiles = Map<
4343 NonNullable<AfterSuiteRunMeta['projectName']> | symbol,
4444 Record<
4545- AfterSuiteRunMeta['transformMode'],
4545+ AfterSuiteRunMeta['environment'],
4646 { [TestFilenames: string]: string }
4747 >
4848>
···253253 this.pendingPromises = []
254254 }
255255256256- onAfterSuiteRun({ coverage, transformMode, projectName, testFiles }: AfterSuiteRunMeta): void {
256256+ onAfterSuiteRun({ coverage, environment, projectName, testFiles }: AfterSuiteRunMeta): void {
257257 if (!coverage) {
258258 return
259259- }
260260-261261- if (transformMode !== 'web' && transformMode !== 'ssr' && transformMode !== 'browser') {
262262- throw new Error(`Invalid transform mode: ${transformMode}`)
263259 }
264260265261 let entry = this.coverageFiles.get(projectName || DEFAULT_PROJECT)
266262267263 if (!entry) {
268268- entry = { web: {}, ssr: {}, browser: {} }
264264+ entry = {}
269265 this.coverageFiles.set(projectName || DEFAULT_PROJECT, entry)
270266 }
271267···275271 `coverage-${uniqueId++}.json`,
276272 )
277273274274+ entry[environment] ??= {}
278275 // If there's a result from previous run, overwrite it
279279- entry[transformMode][testFilenames] = filename
276276+ entry[environment][testFilenames] = filename
280277281278 const promise = fs.writeFile(filename, JSON.stringify(coverage), 'utf-8')
282279 this.pendingPromises.push(promise)
···286283 /** Callback invoked with a single coverage result */
287284 onFileRead: (data: CoverageType) => void
288285 /** Callback invoked once all results of a project for specific transform mode are read */
289289- onFinished: (project: Vitest['projects'][number], transformMode: AfterSuiteRunMeta['transformMode']) => Promise<void>
286286+ onFinished: (project: Vitest['projects'][number], environment: string) => Promise<void>
290287 onDebug: ((...logs: any[]) => void) & { enabled: boolean }
291288 }): Promise<void> {
292289 let index = 0
···296293 this.pendingPromises = []
297294298295 for (const [projectName, coveragePerProject] of this.coverageFiles.entries()) {
299299- for (const [transformMode, coverageByTestfiles] of Object.entries(coveragePerProject) as Entries<typeof coveragePerProject>) {
296296+ for (const [environment, coverageByTestfiles] of Object.entries(coveragePerProject) as Entries<typeof coveragePerProject>) {
300297 const filenames = Object.values(coverageByTestfiles)
301298 const project = this.ctx.getProjectByName(projectName as string)
302299···315312 )
316313 }
317314318318- await onFinished(project, transformMode)
315315+ await onFinished(project, environment)
319316 }
320317 }
321318 }
···640637 ...ctx.projects.map(project => ({
641638 root: project.config.root,
642639 isBrowserEnabled: project.isBrowserEnabled(),
643643- vitenode: project.vitenode,
640640+ vite: project.vite,
644641 })),
645642 // Check core last as it will match all files anyway
646646- { root: ctx.config.root, vitenode: ctx.vitenode, isBrowserEnabled: ctx.getRootProject().isBrowserEnabled() },
643643+ { root: ctx.config.root, vite: ctx.vite, isBrowserEnabled: ctx.getRootProject().isBrowserEnabled() },
647644 ]
648645649646 return async function transformFile(filename: string): Promise<TransformResult | null | undefined> {
650647 let lastError
651648652652- for (const { root, vitenode, isBrowserEnabled } of servers) {
649649+ for (const { root, vite, isBrowserEnabled } of servers) {
653650 // On Windows root doesn't start with "/" while filenames do
654651 if (!filename.startsWith(root) && !filename.startsWith(`/${root}`)) {
655652 continue
656653 }
657654658655 if (isBrowserEnabled) {
659659- const result = await vitenode.transformRequest(filename, undefined, 'web').catch(() => null)
656656+ const result = await vite.environments.client.transformRequest(filename).catch(() => null)
660657661658 if (result) {
662659 return result
···664661 }
665662666663 try {
667667- return await vitenode.transformRequest(filename)
664664+ return await vite.environments.ssr.transformRequest(filename)
668665 }
669666 catch (error) {
670667 lastError = error
+4-4
packages/vitest/src/node/globalSetup.ts
···11-import type { ViteNodeRunner } from 'vite-node/client'
11+import type { ModuleRunner } from 'vite/module-runner'
22import type { TestProject } from './project'
33import { toArray } from '@vitest/utils'
44···99}
10101111export async function loadGlobalSetupFiles(
1212- runner: ViteNodeRunner,
1212+ runner: ModuleRunner,
1313 globalSetup: string | string[],
1414): Promise<GlobalSetupFile[]> {
1515 const globalSetupFiles = toArray(globalSetup)
···20202121async function loadGlobalSetupFile(
2222 file: string,
2323- runner: ViteNodeRunner,
2323+ runner: ModuleRunner,
2424): Promise<GlobalSetupFile> {
2525- const m = await runner.executeFile(file)
2525+ const m = await runner.import(file)
2626 for (const exp of ['default', 'setup', 'teardown']) {
2727 if (m[exp] != null && typeof m[exp] !== 'function') {
2828 throw new Error(
+1-1
packages/vitest/src/node/pool.ts
···144144 return customPools.get(filepath)!
145145 }
146146147147- const pool = await ctx.runner.executeId(filepath)
147147+ const pool = await ctx.runner.import(filepath)
148148 if (typeof pool.default !== 'function') {
149149 throw new TypeError(
150150 `Custom pool "${filepath}" must export a function as default export`,
···1010} from '../node/types/config'
1111import '../node/types/vite'
12121313-export { extraInlineDeps } from '../constants'
1413// will import vitest declare test in module 'vite'
1514export {
1615 configDefaults,
-1
packages/vitest/src/public/execute.ts
···11-export { VitestExecutor } from '../runtime/execute'
+2
packages/vitest/src/public/index.ts
···136136export type { SerializedError } from '@vitest/utils'
137137export type { SerializedTestSpecification }
138138export type { DiffOptions } from '@vitest/utils/diff'
139139+140140+export { EvaluatedModules } from 'vite/module-runner'
+14
packages/vitest/src/public/module-runner.ts
···11+export {
22+ VitestModuleEvaluator,
33+ type VitestModuleEvaluatorOptions,
44+} from '../runtime/moduleRunner/moduleEvaluator'
55+export {
66+ VitestModuleRunner,
77+ type VitestModuleRunnerOptions,
88+} from '../runtime/moduleRunner/moduleRunner'
99+export {
1010+ type ContextModuleRunnerOptions,
1111+ startVitestModuleRunner,
1212+ VITEST_VM_CONTEXT_SYMBOL,
1313+} from '../runtime/moduleRunner/startModuleRunner'
1414+export { getWorkerState } from '../runtime/utils'
···11import type { CancelReason, FileSpecification, Task } from '@vitest/runner'
22import type { BirpcReturn } from 'birpc'
33+import type { EvaluatedModules } from 'vite/module-runner'
34import type { SerializedConfig } from '../runtime/config'
45import type { Environment } from './environment'
55-import type { TransformMode } from './general'
66import type { RunnerRPC, RuntimeRPC } from './rpc'
7788export type WorkerRPC = BirpcReturn<RuntimeRPC, RunnerRPC>
991010export interface ContextTestEnvironment {
1111 name: string
1212- transformMode?: TransformMode
1312 options: Record<string, any> | null
1413}
1514···3332 rpc: WorkerRPC
3433 current?: Task
3534 filepath?: string
3535+ metaEnv: {
3636+ [key: string]: any
3737+ BASE_URL: string
3838+ MODE: string
3939+ DEV: boolean
4040+ PROD: boolean
4141+ SSR: boolean
4242+ }
3643 environment: Environment
3744 environmentTeardownRun?: boolean
3845 onCancel: Promise<CancelReason>
3939- moduleCache: Map<string, any>
4646+ evaluatedModules: EvaluatedModules
4747+ resolvingModules: Set<string>
4048 moduleExecutionInfo: Map<string, any>
4149 onCleanup: (listener: () => unknown) => void
4250 providedContext: Record<string, any>
+5-6
packages/vitest/src/utils/coverage.ts
···11-import type { ModuleExecutionInfo } from 'vite-node/client'
21import type { SerializedCoverageConfig } from '../runtime/config'
3243export interface RuntimeCoverageModuleLoader {
55- executeId: (id: string) => Promise<{ default: RuntimeCoverageProviderModule }>
44+ import: (id: string) => Promise<{ default: RuntimeCoverageProviderModule }>
65 isBrowser?: boolean
77- moduleExecutionInfo?: ModuleExecutionInfo
66+ moduleExecutionInfo?: Map<string, { startOffset: number }>
87}
98109export interface RuntimeCoverageProviderModule {
···2120 /**
2221 * Executed on after each run in the worker thread. Possible to return a payload passed to the provider
2322 */
2424- takeCoverage?: (runtimeOptions?: { moduleExecutionInfo?: ModuleExecutionInfo }) => unknown | Promise<unknown>
2323+ takeCoverage?: (runtimeOptions?: { moduleExecutionInfo?: Map<string, { startOffset: number }> }) => unknown | Promise<unknown>
25242625 /**
2726 * Executed after all tests have been run in the worker thread.
···5150 builtInModule += '/browser'
5251 }
53525454- const { default: coverageModule } = await loader.executeId(builtInModule)
5353+ const { default: coverageModule } = await loader.import(builtInModule)
55545655 if (!coverageModule) {
5756 throw new Error(
···6564 let customProviderModule
66656766 try {
6868- customProviderModule = await loader.executeId(options.customProviderModule!)
6767+ customProviderModule = await loader.import(options.customProviderModule!)
6968 }
7069 catch (error) {
7170 throw new Error(
+2-1
packages/vitest/src/utils/graph.ts
···2626 }
2727 let id = clearId(mod.id)
2828 seen.set(mod, id)
2929+ // TODO: how to know if it was rewritten(?) - what is rewritten?
2930 const rewrote = browser
3031 ? mod.file?.includes(project.browser!.vite.config.cacheDir)
3132 ? mod.id
3233 : false
3333- : await project.vitenode.shouldExternalize(id)
3434+ : false
3435 if (rewrote) {
3536 id = rewrote
3637 externalized.add(id)
+1-21
packages/vitest/src/utils/test-helpers.ts
···11import type { TestProject } from '../node/project'
22import type { TestSpecification } from '../node/spec'
33-import type { EnvironmentOptions, TransformModePatterns, VitestEnvironment } from '../node/types/config'
33+import type { EnvironmentOptions, VitestEnvironment } from '../node/types/config'
44import type { ContextTestEnvironment } from '../types/worker'
55import { promises as fs } from 'node:fs'
66-import pm from 'picomatch'
76import { groupBy } from './base'
8798export const envsOrder: string[] = ['node', 'jsdom', 'happy-dom', 'edge-runtime']
···1211 file: string
1312 env: VitestEnvironment
1413 envOptions: EnvironmentOptions | null
1515-}
1616-1717-function getTransformMode(
1818- patterns: TransformModePatterns,
1919- filename: string,
2020-): 'web' | 'ssr' | undefined {
2121- if (patterns.web && pm.isMatch(filename, patterns.web)) {
2222- return 'web'
2323- }
2424- if (patterns.ssr && pm.isMatch(filename, patterns.ssr)) {
2525- return 'ssr'
2626- }
2727- return undefined
2814}
29153016export async function groupFilesByEnv(
···4329 // 2. Fallback to global env
4430 env ||= project.config.environment || 'node'
45314646- const transformMode = getTransformMode(
4747- project.config.testTransformMode,
4848- filepath,
4949- )
5050-5132 let envOptionsJson = code.match(/@(?:vitest|jest)-environment-options\s+(.+)/)?.[1]
5233 if (envOptionsJson?.endsWith('*/')) {
5334 // Trim closing Docblock characters the above regex might have captured
···5839 const envKey = env === 'happy-dom' ? 'happyDOM' : env
5940 const environment: ContextTestEnvironment = {
6041 name: env as VitestEnvironment,
6161- transformMode,
6242 options: envOptions
6343 ? ({ [envKey]: envOptions } as EnvironmentOptions)
6444 : null,
···11import { test } from 'vitest'
22-import testStack from "@vitest/test-dep-error"
33-import testStackTs from "@vitest/test-dep-error/ts.ts"
44-import testStackTranspiled from "@vitest/test-dep-error/transpiled.js"
55-import testStackTranspiledInline from "@vitest/test-dep-error/transpiled-inline.js"
22+import testStack from "@test/test-dep-error"
33+import testStackTs from "@test/test-dep-error/ts.ts"
44+import testStackTranspiled from "@test/test-dep-error/transpiled.js"
55+import testStackTranspiledInline from "@test/test-dep-error/transpiled-inline.js"
6677test('js', () => {
88 testStack()
···11+import type { DevEnvironment, FetchResult, Rollup, TransformResult } from 'vite'
22+import type { FetchFunctionOptions } from 'vite/module-runner'
33+import type { FetchCachedFileSystemResult } from '../../types/general'
44+import type { VitestResolver } from '../resolver'
55+import { mkdirSync } from 'node:fs'
66+import { rename, stat, unlink, writeFile } from 'node:fs/promises'
77+import { tmpdir } from 'node:os'
88+import { isExternalUrl, nanoid, unwrapId } from '@vitest/utils'
99+import { dirname, join } from 'pathe'
1010+import { fetchModule } from 'vite'
1111+import { hash } from '../hash'
1212+1313+const created = new Set()
1414+const promises = new Map<string, Promise<void>>()
1515+1616+export function createFetchModuleFunction(
1717+ resolver: VitestResolver,
1818+ cacheFs: boolean = false,
1919+ tmpDir: string = join(tmpdir(), nanoid()),
2020+): (
2121+ url: string,
2222+ importer: string | undefined,
2323+ environment: DevEnvironment,
2424+ options?: FetchFunctionOptions
2525+) => Promise<FetchResult | FetchCachedFileSystemResult> {
2626+ const cachedFsResults = new Map<string, string>()
2727+ return async (
2828+ url,
2929+ importer,
3030+ environment,
3131+ options,
3232+ ) => {
3333+ // We are copy pasting Vite's externalization logic from `fetchModule` because
3434+ // we instead rely on our own `shouldExternalize` method because Vite
3535+ // doesn't support `resolve.external` in non SSR environments (jsdom/happy-dom)
3636+ if (url.startsWith('data:')) {
3737+ return { externalize: url, type: 'builtin' }
3838+ }
3939+4040+ if (url === '/@vite/client' || url === '@vite/client') {
4141+ // this will be stubbed
4242+ return { externalize: '/@vite/client', type: 'module' }
4343+ }
4444+4545+ const isFileUrl = url.startsWith('file://')
4646+4747+ if (isExternalUrl(url) && !isFileUrl) {
4848+ return { externalize: url, type: 'network' }
4949+ }
5050+5151+ // Vite does the same in `fetchModule`, but we want to externalize modules ourselves,
5252+ // so we do this first to resolve the module and check its `id`. The next call of
5353+ // `ensureEntryFromUrl` inside `fetchModule` is cached and should take no time
5454+ // This also makes it so externalized modules are inside the module graph.
5555+ const moduleGraphModule = await environment.moduleGraph.ensureEntryFromUrl(unwrapId(url))
5656+ const cached = !!moduleGraphModule.transformResult
5757+5858+ // if url is already cached, we can just confirm it's also cached on the server
5959+ if (options?.cached && cached) {
6060+ return { cache: true }
6161+ }
6262+6363+ if (moduleGraphModule.id) {
6464+ const externalize = await resolver.shouldExternalize(moduleGraphModule.id)
6565+ if (externalize) {
6666+ return { externalize, type: 'module' }
6767+ }
6868+ }
6969+7070+ const moduleRunnerModule = await fetchModule(
7171+ environment,
7272+ url,
7373+ importer,
7474+ {
7575+ ...options,
7676+ inlineSourceMap: false,
7777+ },
7878+ ).catch(handleRollupError)
7979+8080+ const result = processResultSource(environment, moduleRunnerModule)
8181+8282+ if (!cacheFs || !('code' in result)) {
8383+ return result
8484+ }
8585+8686+ const code = result.code
8787+ // to avoid serialising large chunks of code,
8888+ // we store them in a tmp file and read in the test thread
8989+ if (cachedFsResults.has(result.id)) {
9090+ return getCachedResult(result, cachedFsResults)
9191+ }
9292+ const dir = join(tmpDir, environment.name)
9393+ const name = hash('sha1', result.id, 'hex')
9494+ const tmp = join(dir, name)
9595+ if (!created.has(dir)) {
9696+ mkdirSync(dir, { recursive: true })
9797+ created.add(dir)
9898+ }
9999+ if (promises.has(tmp)) {
100100+ await promises.get(tmp)
101101+ cachedFsResults.set(result.id, tmp)
102102+ return getCachedResult(result, cachedFsResults)
103103+ }
104104+ promises.set(
105105+ tmp,
106106+107107+ atomicWriteFile(tmp, code)
108108+ // Fallback to non-atomic write for windows case where file already exists:
109109+ .catch(() => writeFile(tmp, code, 'utf-8'))
110110+ .finally(() => promises.delete(tmp)),
111111+ )
112112+ await promises.get(tmp)
113113+ cachedFsResults.set(result.id, tmp)
114114+ return getCachedResult(result, cachedFsResults)
115115+ }
116116+}
117117+118118+let SOURCEMAPPING_URL = 'sourceMa'
119119+SOURCEMAPPING_URL += 'ppingURL'
120120+121121+const MODULE_RUNNER_SOURCEMAPPING_SOURCE = '//# sourceMappingSource=vite-generated'
122122+123123+function processResultSource(environment: DevEnvironment, result: FetchResult): FetchResult {
124124+ if (!('code' in result)) {
125125+ return result
126126+ }
127127+128128+ const node = environment.moduleGraph.getModuleById(result.id)
129129+ if (node?.transformResult) {
130130+ // this also overrides node.transformResult.code which is also what the module
131131+ // runner does under the hood by default (we disable source maps inlining)
132132+ inlineSourceMap(node.transformResult)
133133+ }
134134+135135+ return {
136136+ ...result,
137137+ code: node?.transformResult?.code || result.code,
138138+ }
139139+}
140140+141141+const OTHER_SOURCE_MAP_REGEXP = new RegExp(
142142+ `//# ${SOURCEMAPPING_URL}=data:application/json[^,]+base64,([A-Za-z0-9+/=]+)$`,
143143+ 'gm',
144144+)
145145+146146+// we have to inline the source map ourselves, because
147147+// - we don't need //# sourceURL since we are running code in VM
148148+// - important in stack traces and the V8 coverage
149149+// - we need to inject an empty line for --inspect-brk
150150+function inlineSourceMap(result: TransformResult) {
151151+ const map = result.map
152152+ let code = result.code
153153+154154+ if (
155155+ !map
156156+ || !('version' in map)
157157+ || code.includes(MODULE_RUNNER_SOURCEMAPPING_SOURCE)
158158+ ) {
159159+ return result
160160+ }
161161+162162+ // to reduce the payload size, we only inline vite node source map, because it's also the only one we use
163163+ OTHER_SOURCE_MAP_REGEXP.lastIndex = 0
164164+ if (OTHER_SOURCE_MAP_REGEXP.test(code)) {
165165+ code = code.replace(OTHER_SOURCE_MAP_REGEXP, '')
166166+ }
167167+168168+ const sourceMap = { ...map }
169169+170170+ // If the first line is not present on source maps, add simple 1:1 mapping ([0,0,0,0], [1,0,0,0])
171171+ // so that debuggers can be set to break on first line
172172+ if (sourceMap.mappings.startsWith(';')) {
173173+ sourceMap.mappings = `AAAA,CAAA${sourceMap.mappings}`
174174+ }
175175+176176+ result.code = `${code.trimEnd()}\n${
177177+ MODULE_RUNNER_SOURCEMAPPING_SOURCE
178178+ }\n//# ${SOURCEMAPPING_URL}=${genSourceMapUrl(sourceMap)}\n`
179179+180180+ return result
181181+}
182182+183183+function genSourceMapUrl(map: Rollup.SourceMap | string): string {
184184+ if (typeof map !== 'string') {
185185+ map = JSON.stringify(map)
186186+ }
187187+ return `data:application/json;base64,${Buffer.from(map).toString('base64')}`
188188+}
189189+190190+function getCachedResult(result: Extract<FetchResult, { code: string }>, cachedFsResults: Map<string, string>): FetchCachedFileSystemResult {
191191+ const tmp = cachedFsResults.get(result.id)
192192+ if (!tmp) {
193193+ throw new Error(`The cached result was returned too early for ${result.id}.`)
194194+ }
195195+ return {
196196+ cached: true as const,
197197+ file: result.file,
198198+ id: result.id,
199199+ tmp,
200200+ url: result.url,
201201+ invalidate: result.invalidate,
202202+ }
203203+}
204204+205205+// serialize rollup error on server to preserve details as a test error
206206+export function handleRollupError(e: unknown): never {
207207+ if (
208208+ e instanceof Error
209209+ && ('plugin' in e || 'frame' in e || 'id' in e)
210210+ ) {
211211+ // eslint-disable-next-line no-throw-literal
212212+ throw {
213213+ name: e.name,
214214+ message: e.message,
215215+ stack: e.stack,
216216+ cause: e.cause,
217217+ __vitest_rollup_error__: {
218218+ plugin: (e as any).plugin,
219219+ id: (e as any).id,
220220+ loc: (e as any).loc,
221221+ frame: (e as any).frame,
222222+ },
223223+ }
224224+ }
225225+ throw e
226226+}
227227+228228+/**
229229+ * Performs an atomic write operation using the write-then-rename pattern.
230230+ *
231231+ * Why we need this:
232232+ * - Ensures file integrity by never leaving partially written files on disk
233233+ * - Prevents other processes from reading incomplete data during writes
234234+ * - Particularly important for test files where incomplete writes could cause test failures
235235+ *
236236+ * The implementation writes to a temporary file first, then renames it to the target path.
237237+ * This rename operation is atomic on most filesystems (including POSIX-compliant ones),
238238+ * guaranteeing that other processes will only ever see the complete file.
239239+ *
240240+ * Added in https://github.com/vitest-dev/vitest/pull/7531
241241+ */
242242+async function atomicWriteFile(realFilePath: string, data: string): Promise<void> {
243243+ const dir = dirname(realFilePath)
244244+ const tmpFilePath = join(dir, `.tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`)
245245+246246+ try {
247247+ await writeFile(tmpFilePath, data, 'utf-8')
248248+ await rename(tmpFilePath, realFilePath)
249249+ }
250250+ finally {
251251+ try {
252252+ if (await stat(tmpFilePath)) {
253253+ await unlink(tmpFilePath)
254254+ }
255255+ }
256256+ catch {}
257257+ }
258258+}
···11+import type { DevEnvironment } from 'vite'
22+import { existsSync } from 'node:fs'
33+import path from 'node:path'
44+import { cleanUrl, withTrailingSlash, wrapId } from '@vitest/utils'
55+66+// this is copy pasted from vite
77+export function normalizeResolvedIdToUrl(
88+ environment: DevEnvironment,
99+ resolvedId: string,
1010+): string {
1111+ const root = environment.config.root
1212+ const depsOptimizer = environment.depsOptimizer
1313+1414+ let url: string
1515+1616+ // normalize all imports into resolved URLs
1717+ // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'`
1818+ if (resolvedId.startsWith(withTrailingSlash(root))) {
1919+ // in root: infer short absolute path from root
2020+ url = resolvedId.slice(root.length)
2121+ }
2222+ else if (
2323+ depsOptimizer?.isOptimizedDepFile(resolvedId)
2424+ // vite-plugin-react isn't following the leading \0 virtual module convention.
2525+ // This is a temporary hack to avoid expensive fs checks for React apps.
2626+ // We'll remove this as soon we're able to fix the react plugins.
2727+ || (resolvedId !== '/@react-refresh'
2828+ && path.isAbsolute(resolvedId)
2929+ && existsSync(cleanUrl(resolvedId)))
3030+ ) {
3131+ // an optimized deps may not yet exists in the filesystem, or
3232+ // a regular file exists but is out of root: rewrite to absolute /@fs/ paths
3333+ url = path.posix.join('/@fs/', resolvedId)
3434+ }
3535+ else {
3636+ url = resolvedId
3737+ }
3838+3939+ // if the resolved id is not a valid browser import specifier,
4040+ // prefix it to make it valid. We will strip this before feeding it
4141+ // back into the transform pipeline
4242+ if (url[0] !== '.' && url[0] !== '/') {
4343+ url = wrapId(resolvedId)
4444+ }
4545+4646+ return url
4747+}
···11import type { Vitest } from '../core'
22import type { TestSpecification } from '../spec'
33import type { TestSequencer } from './types'
44+import { slash } from '@vitest/utils'
45import { relative, resolve } from 'pathe'
55-import { slash } from 'vite-node/utils'
66import { hash } from '../hash'
7788export class BaseSequencer implements TestSequencer {
···44import type { SnapshotStateOptions } from '@vitest/snapshot'
55import type { SerializedDiffOptions } from '@vitest/utils/diff'
66import type { AliasOptions, ConfigEnv, DepOptimizationConfig, ServerOptions, UserConfig as ViteUserConfig } from 'vite'
77-import type { ViteNodeServerOptions } from 'vite-node'
87import type { ChaiConfig } from '../../integrations/chai/config'
98import type { SerializedConfig } from '../../runtime/config'
109import type { Arrayable, LabelColor, ParsedStack, ProvidedContext, TestError } from '../../types/general'
···135134 enabled?: boolean
136135}
137136138138-export interface TransformModePatterns {
139139- /**
140140- * Use SSR transform pipeline for all modules inside specified tests.
141141- * Vite plugins will receive `ssr: true` flag when processing those files.
142142- *
143143- * @default tests with node or edge environment
144144- */
145145- ssr?: string[]
146146- /**
147147- * First do a normal transform pipeline (targeting browser),
148148- * then then do a SSR rewrite to run the code in Node.
149149- * Vite plugins will receive `ssr: false` flag when processing those files.
150150- *
151151- * @default tests with jsdom or happy-dom environment
152152- */
153153- web?: string[]
154154-}
155155-156137interface DepsOptions {
157138 /**
158139 * Enable dependency optimization. This can improve the performance of your tests.
159140 */
160160- optimizer?: {
161161- web?: DepsOptimizationOptions
162162- ssr?: DepsOptimizationOptions
163163- }
141141+ optimizer?: Partial<Record<'client' | 'ssr' | ({} & string), DepsOptimizationOptions>>
164142 web?: {
165143 /**
166144 * Should Vitest process assets (.png, .svg, .jpg, etc) files and resolve them like Vite does in the browser.
···193171 */
194172 transformGlobPattern?: RegExp | RegExp[]
195173 }
196196- /**
197197- * Externalize means that Vite will bypass the package to native Node.
198198- *
199199- * Externalized dependencies will not be applied Vite's transformers and resolvers.
200200- * And does not support HMR on reload.
201201- *
202202- * Typically, packages under `node_modules` are externalized.
203203- *
204204- * @deprecated If you rely on vite-node directly, use `server.deps.external` instead. Otherwise, consider using `deps.optimizer.{web,ssr}.exclude`.
205205- */
206206- external?: (string | RegExp)[]
207207- /**
208208- * Vite will process inlined modules.
209209- *
210210- * This could be helpful to handle packages that ship `.js` in ESM format (that Node can't handle).
211211- *
212212- * If `true`, every dependency will be inlined
213213- *
214214- * @deprecated If you rely on vite-node directly, use `server.deps.inline` instead. Otherwise, consider using `deps.optimizer.{web,ssr}.include`.
215215- */
216216- inline?: (string | RegExp)[] | true
217174218175 /**
219176 * Interpret CJS module's default as named exports
···221178 * @default true
222179 */
223180 interopDefault?: boolean
224224-225225- /**
226226- * When a dependency is a valid ESM package, try to guess the cjs version based on the path.
227227- * This will significantly improve the performance in huge repo, but might potentially
228228- * cause some misalignment if a package have different logic in ESM and CJS mode.
229229- *
230230- * @default false
231231- *
232232- * @deprecated Use `server.deps.fallbackCJS` instead.
233233- */
234234- fallbackCJS?: boolean
235181236182 /**
237183 * A list of directories relative to the config file that should be treated as module directories.
···297243 */
298244 deps?: DepsOptions
299245300300- /**
301301- * Vite-node server options
302302- */
303303- server?: Omit<ViteNodeServerOptions, 'transformMode'>
246246+ server?: {
247247+ deps?: ServerDepsOptions
248248+ }
304249305250 /**
306251 * Base directory to scan for the test files
···559504 * @default '/__vitest__/'
560505 */
561506 uiBase?: string
562562-563563- /**
564564- * Determine the transform method for all modules imported inside a test that matches the glob pattern.
565565- */
566566- testTransformMode?: TransformModePatterns
567507568508 /**
569509 * Format options for snapshot testing.
···11071047 | 'minWorkers'
11081048 | 'fileParallelism'
11091049 | 'watchTriggerPatterns'
10501050+10511051+export interface ServerDepsOptions {
10521052+ /**
10531053+ * Externalize means that Vite will bpass the package to native Node.
10541054+ *
10551055+ * Externalized dependencies will not be applied Vite's transformers and resolvers.
10561056+ * And does not support HMR on reload.
10571057+ *
10581058+ * Typically, packages under `node_modules` are externalized.
10591059+ */
10601060+ external?: (string | RegExp)[]
10611061+ /**
10621062+ * Vite will process inlined modules.
10631063+ *
10641064+ * This could be helpful to handle packages that ship `.js` in ESM format (that Node can't handle).
10651065+ *
10661066+ * If `true`, every dependency will be inlined
10671067+ */
10681068+ inline?: (string | RegExp)[] | true
10691069+ /**
10701070+ * Try to guess the CJS version of a package when it's invalid ESM
10711071+ * @default false
10721072+ */
10731073+ fallbackCJS?: boolean
10741074+}
1110107511111076export type ProjectConfig = Omit<
11121077 InlineConfig,
···11import type { SnapshotEnvironment } from '@vitest/snapshot/environment'
22import type { SerializedConfig } from '../../../runtime/config'
33-import type { VitestExecutor } from '../../../runtime/execute'
33+import type { VitestModuleRunner } from '../../../runtime/moduleRunner/moduleRunner'
4455export async function resolveSnapshotEnvironment(
66 config: SerializedConfig,
77- executor: VitestExecutor,
77+ executor: VitestModuleRunner,
88): Promise<SnapshotEnvironment> {
99 if (!config.snapshotEnvironment) {
1010 const { VitestNodeSnapshotEnvironment } = await import('./node')
1111 return new VitestNodeSnapshotEnvironment()
1212 }
13131414- const mod = await executor.executeId(config.snapshotEnvironment)
1414+ const mod = await executor.import(config.snapshotEnvironment)
1515 if (typeof mod.default !== 'object' || !mod.default) {
1616 throw new Error(
1717 'Snapshot environment module must have a default export object with a shape of `SnapshotEnvironment`',