diff --git a/docs/guide/environment.md b/docs/guide/environment.md index d6f899fe2e1d..88816518a91c 100644 --- a/docs/guide/environment.md +++ b/docs/guide/environment.md @@ -13,6 +13,12 @@ By default, you can use these environments: - `happy-dom` emulates browser environment by providing Browser API, and considered to be faster than jsdom, but lacks some API, uses [`happy-dom`](https://github.com/capricorn86/happy-dom) package - `edge-runtime` emulates Vercel's [edge-runtime](https://edge-runtime.vercel.app/), uses [`@edge-runtime/vm`](https://www.npmjs.com/package/@edge-runtime/vm) package +::: info +When using `jsdom` or `happy-dom` environments, Vitest follows the same rules that Vite does when importing [CSS](https://vitejs.dev/guide/features.html#css) and [assets](https://vitejs.dev/guide/features.html#static-assets). If importing external dependency fails with `unknown extension .css` error, you need to inline the whole import chain manually by adding all packages to [`server.deps.external`](/config/#server-deps-external). For example, if the error happens in `package-3` in this import chain: `source code -> package-1 -> package-2 -> package-3`, you need to add all three packages to `server.deps.external`. + +Since Vitest 2.0.4 the `require` of CSS and assets inside the external dependencies are resolved automatically. +::: + ## Environments for Specific Files When setting `environment` option in your config, it will apply to all the test files in your project. To have more fine-grained control, you can use control comments to specify environment for specific files. Control comments are comments that start with `@vitest-environment` and are followed by the environment name: diff --git a/packages/vitest/src/runtime/runVmTests.ts b/packages/vitest/src/runtime/runVmTests.ts index 5b3b4107501e..621022a316cc 100644 --- a/packages/vitest/src/runtime/runVmTests.ts +++ b/packages/vitest/src/runtime/runVmTests.ts @@ -4,6 +4,7 @@ import timers from 'node:timers' import { performance } from 'node:perf_hooks' import { collectTests, startTests } from '@vitest/runner' import { installSourcemapsSupport } from 'vite-node/source-map' +import { KNOWN_ASSET_TYPES } from 'vite-node/constants' import { setupChaiConfig } from '../integrations/chai/config' import { startCoverageInsideWorker, @@ -36,10 +37,14 @@ export async function run( if (workerState.environment.transformMode === 'web') { const _require = createRequire(import.meta.url) // always mock "required" `css` files, because we cannot process them - _require.extensions['.css'] = () => ({}) - _require.extensions['.scss'] = () => ({}) - _require.extensions['.sass'] = () => ({}) - _require.extensions['.less'] = () => ({}) + _require.extensions['.css'] = resolveCss + _require.extensions['.scss'] = resolveCss + _require.extensions['.sass'] = resolveCss + _require.extensions['.less'] = resolveCss + // since we are using Vite, we can assume how these will be resolved + KNOWN_ASSET_TYPES.forEach((type) => { + _require.extensions[`.${type}`] = resolveAsset + }) } // @ts-expect-error not typed global for patched timers @@ -93,3 +98,11 @@ export async function run( await stopCoverageInsideWorker(config.coverage, executor) } + +function resolveCss(mod: NodeJS.Module) { + mod.exports = '' +} + +function resolveAsset(mod: NodeJS.Module, url: string) { + mod.exports = url +} diff --git a/packages/vitest/src/runtime/setup-node.ts b/packages/vitest/src/runtime/setup-node.ts index 5fe45557d895..100a7d302072 100644 --- a/packages/vitest/src/runtime/setup-node.ts +++ b/packages/vitest/src/runtime/setup-node.ts @@ -2,6 +2,7 @@ import { createRequire } from 'node:module' import util from 'node:util' import timers from 'node:timers' import { installSourcemapsSupport } from 'vite-node/source-map' +import { KNOWN_ASSET_TYPES } from 'vite-node/constants' import type { EnvironmentOptions, ResolvedConfig, @@ -44,10 +45,14 @@ export async function setupGlobalEnv( if (environment.transformMode === 'web') { const _require = createRequire(import.meta.url) // always mock "required" `css` files, because we cannot process them - _require.extensions['.css'] = () => ({}) - _require.extensions['.scss'] = () => ({}) - _require.extensions['.sass'] = () => ({}) - _require.extensions['.less'] = () => ({}) + _require.extensions['.css'] = resolveCss + _require.extensions['.scss'] = resolveCss + _require.extensions['.sass'] = resolveCss + _require.extensions['.less'] = resolveCss + // since we are using Vite, we can assume how these will be resolved + KNOWN_ASSET_TYPES.forEach((type) => { + _require.extensions[`.${type}`] = resolveAsset + }) process.env.SSR = '' } else { @@ -69,6 +74,14 @@ export async function setupGlobalEnv( } } +function resolveCss(mod: NodeJS.Module) { + mod.exports = '' +} + +function resolveAsset(mod: NodeJS.Module, url: string) { + mod.exports = url +} + export async function setupConsoleLogSpy() { const { createCustomConsole } = await import('./console') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6eeaf2b778c9..f2b6d6f1b296 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1191,6 +1191,9 @@ importers: url: specifier: ^0.11.0 version: 0.11.0 + vite-node: + specifier: workspace:* + version: link:../../packages/vite-node vitest: specifier: workspace:* version: link:../../packages/vitest diff --git a/test/core/package.json b/test/core/package.json index dfbe63a501de..2439a810a807 100644 --- a/test/core/package.json +++ b/test/core/package.json @@ -28,6 +28,7 @@ "tinyrainbow": "^1.2.0", "tinyspy": "^1.0.2", "url": "^0.11.0", + "vite-node": "workspace:*", "vitest": "workspace:*", "vitest-environment-custom": "file:./vitest-environment-custom", "vue": "^3.4.26", diff --git a/test/core/src/file-txt.txt b/test/core/src/file-txt.txt new file mode 100644 index 000000000000..8e27be7d6154 --- /dev/null +++ b/test/core/src/file-txt.txt @@ -0,0 +1 @@ +text diff --git a/test/core/test/require.test.ts b/test/core/test/require.test.ts index 4d66074db582..17df9ace9714 100644 --- a/test/core/test/require.test.ts +++ b/test/core/test/require.test.ts @@ -1,5 +1,6 @@ // @vitest-environment jsdom +// import { KNOWN_ASSET_RE } from 'vite-node/constants' import { describe, expect, it } from 'vitest' const _require = require @@ -11,9 +12,14 @@ describe('using "require" to import a module', () => { const scss = _require('./../src/file-scss.scss') const less = _require('./../src/file-less.less') - expect(css).toEqual({}) - expect(sass).toEqual({}) - expect(scss).toEqual({}) - expect(less).toEqual({}) + expect(css).toEqual('') + expect(sass).toEqual('') + expect(scss).toEqual('') + expect(less).toEqual('') + }) + + it('importing assets works', () => { + const path = _require.resolve('./../src/file-txt.txt') + expect(_require('./../src/file-txt.txt')).toBe(path) }) })