From f4e6e99fa3a81fd7a97fb9b435da367dddba7fa4 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 3 Aug 2023 17:43:50 +0200 Subject: [PATCH] feat: allow importing CSS and assets inside external dependencies (#3880) --- docs/config/index.md | 57 +++++- packages/vite-node/src/server.ts | 13 ++ packages/vitest/src/node/config.ts | 5 + packages/vitest/src/node/pools/rpc.ts | 3 + packages/vitest/src/node/pools/vm-threads.ts | 4 +- packages/vitest/src/node/workspace.ts | 4 +- packages/vitest/src/runtime/execute.ts | 18 +- .../vitest/src/runtime/external-executor.ts | 92 +++++++--- .../src/runtime/vm/commonjs-executor.ts | 28 ++- .../vitest/src/runtime/vm/esm-executor.ts | 55 ++---- packages/vitest/src/runtime/vm/utils.ts | 5 + .../vitest/src/runtime/vm/vite-executor.ts | 101 ++++++++++ packages/vitest/src/types/config.ts | 32 ++++ packages/vitest/src/types/rpc.ts | 1 + pnpm-lock.yaml | 172 ++++++------------ test/vm-threads/package.json | 1 + test/vm-threads/src/external/assets/file1.png | 0 test/vm-threads/src/external/assets/file2.txt | 0 test/vm-threads/src/external/assets/file3.svg | 0 test/vm-threads/src/external/css/empty.css | 3 + .../vm-threads/src/external/css/processed.css | 3 + .../src/external/css/processed.module.css | 3 + .../test/import-external-css-assets.test.js | 43 +++++ test/vm-threads/vitest.config.ts | 3 + 24 files changed, 444 insertions(+), 202 deletions(-) create mode 100644 packages/vitest/src/runtime/vm/vite-executor.ts create mode 100644 test/vm-threads/src/external/assets/file1.png create mode 100644 test/vm-threads/src/external/assets/file2.txt create mode 100644 test/vm-threads/src/external/assets/file3.svg create mode 100644 test/vm-threads/src/external/css/empty.css create mode 100644 test/vm-threads/src/external/css/processed.css create mode 100644 test/vm-threads/src/external/css/processed.module.css create mode 100644 test/vm-threads/test/import-external-css-assets.test.js diff --git a/docs/config/index.md b/docs/config/index.md index 1256eafbdf85..54922d6b2d8d 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -196,7 +196,7 @@ When Vitest encounters the external library listed in `include`, it will be bund - Your `alias` configuration is now respected inside bundled packages - Code in your tests is running closer to how it's running in the browser -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. 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`](#transformmode). +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`](#transformmode). This 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`. @@ -204,6 +204,61 @@ This options also inherits your `optimizeDeps` configuration (for web Vitest wil You will not be able to edit your `node_modules` code for debugging, since the code is actually located in your `cacheDir` or `test.cache.dir` directory. If you want to debug with `console.log` statements, edit it directly or force rebundling with `deps.optimizer?.[mode].force` option. ::: +#### deps.optimizer.{mode}.enabled + +- **Type:** `boolean` +- **Default:** `true` + +Enable dependency optimization. + +#### deps.web + +- **Type:** `{ transformAssets?, ... }` +- **Version:** Since Vite 0.34.2 + +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. + +Usually, files inside `node_modules` are externalized, but these options also affect files in [`server.deps.external`](#server-deps-external). + +#### deps.web.transformAssets + +- **Type:** `boolean` +- **Default:** `true` + +Should Vitest process assets (.png, .svg, .jpg, etc) files and resolve them like Vite does in the browser. + +hese module will have a default export equal to the path to the asset, if no query is specified. + +::: warning +At the moment, this option only works with [`experimentalVmThreads`](#experimentalvmthreads) pool. +::: + +#### deps.web.transformCss + +- **Type:** `boolean` +- **Default:** `true` + +Should Vitest process CSS (.css, .scss, .sass, etc) files and resolve them like Vite does in the browser. + +If CSS files are disabled with [`css`](#css) options, this option will just silence `UNKNOWN_EXTENSION` errors. + +::: warning +At the moment, this option only works with [`experimentalVmThreads`](#experimentalvmthreads) pool. +::: + +#### deps.web.transformGlobPattern + +- **Type:** `RegExp | RegExp[]` +- **Default:** `[]` + +Regexp pattern to match external files that should be transformed. + +By default, files inside `node_modules` are externalized and not transformed, unless it's CSS or an asset, and corresponding option is not disabled. + +::: warning +At the moment, this option only works with [`experimentalVmThreads`](#experimentalvmthreads) pool. +::: + #### deps.registerNodeLoader - **Type:** `boolean` diff --git a/packages/vite-node/src/server.ts b/packages/vite-node/src/server.ts index 17834bb2e367..5436281119cb 100644 --- a/packages/vite-node/src/server.ts +++ b/packages/vite-node/src/server.ts @@ -150,6 +150,19 @@ export class ViteNodeServer { return this.transformPromiseMap.get(id)! } + async transformModule(id: string, transformMode?: 'web' | 'ssr') { + if (transformMode !== 'web') + throw new Error('`transformModule` only supports `transformMode: "web"`.') + + const normalizedId = normalizeModuleId(id) + const mod = this.server.moduleGraph.getModuleById(normalizedId) + const result = mod?.transformResult || await this.server.transformRequest(normalizedId) + + return { + code: result?.code, + } + } + getTransformMode(id: string) { const withoutQuery = id.split('?')[0] diff --git a/packages/vitest/src/node/config.ts b/packages/vitest/src/node/config.ts index e7a42b167271..51bf4e999ae7 100644 --- a/packages/vitest/src/node/config.ts +++ b/packages/vitest/src/node/config.ts @@ -137,6 +137,11 @@ export function resolveConfig( if (!resolved.deps.moduleDirectories.includes('/node_modules/')) resolved.deps.moduleDirectories.push('/node_modules/') + resolved.deps.web ??= {} + resolved.deps.web.transformAssets ??= true + resolved.deps.web.transformCss ??= true + resolved.deps.web.transformGlobPattern ??= [] + resolved.server ??= {} resolved.server.deps ??= {} diff --git a/packages/vitest/src/node/pools/rpc.ts b/packages/vitest/src/node/pools/rpc.ts index 020ea2cf4747..6ca1c736c1f9 100644 --- a/packages/vitest/src/node/pools/rpc.ts +++ b/packages/vitest/src/node/pools/rpc.ts @@ -30,6 +30,9 @@ export function createMethodsRPC(project: WorkspaceProject): RuntimeRPC { resolveId(id, importer, transformMode) { return project.vitenode.resolveId(id, importer, transformMode) }, + transform(id, environment) { + return project.vitenode.transformModule(id, environment) + }, onPathsCollected(paths) { ctx.state.collectPaths(paths) project.report('onPathsCollected', paths) diff --git a/packages/vitest/src/node/pools/vm-threads.ts b/packages/vitest/src/node/pools/vm-threads.ts index 77eb53e93294..89aecc1e3381 100644 --- a/packages/vitest/src/node/pools/vm-threads.ts +++ b/packages/vitest/src/node/pools/vm-threads.ts @@ -14,7 +14,7 @@ import type { WorkspaceProject } from '../workspace' import { createMethodsRPC } from './rpc' const workerPath = pathToFileURL(resolve(distDir, './vm.js')).href -const suppressLoaderWarningsPath = resolve(rootDir, './suppress-warnings.cjs') +const suppressWarningsPath = resolve(rootDir, './suppress-warnings.cjs') function createWorkerChannel(project: WorkspaceProject) { const channel = new MessageChannel() @@ -61,7 +61,7 @@ export function createVmThreadsPool(ctx: Vitest, { execArgv, env }: PoolProcessO '--experimental-import-meta-resolve', '--experimental-vm-modules', '--require', - suppressLoaderWarningsPath, + suppressWarningsPath, ...execArgv, ], diff --git a/packages/vitest/src/node/workspace.ts b/packages/vitest/src/node/workspace.ts index 9497e1caa808..6bef2aa640a4 100644 --- a/packages/vitest/src/node/workspace.ts +++ b/packages/vitest/src/node/workspace.ts @@ -293,10 +293,10 @@ export class WorkspaceProject { ...this.config.deps, optimizer: { web: { - enabled: this.config.deps?.optimizer?.web?.enabled ?? false, + enabled: this.config.deps?.optimizer?.web?.enabled ?? true, }, ssr: { - enabled: this.config.deps?.optimizer?.ssr?.enabled ?? false, + enabled: this.config.deps?.optimizer?.ssr?.enabled ?? true, }, }, }, diff --git a/packages/vitest/src/runtime/execute.ts b/packages/vitest/src/runtime/execute.ts index d379a1b57165..52c008edfa5d 100644 --- a/packages/vitest/src/runtime/execute.ts +++ b/packages/vitest/src/runtime/execute.ts @@ -6,7 +6,7 @@ import type { ViteNodeRunnerOptions } from 'vite-node' import { normalize, relative, resolve } from 'pathe' import { processError } from '@vitest/utils/error' import type { MockMap } from '../types/mocker' -import type { ResolvedConfig, ResolvedTestEnvironment, WorkerGlobalState } from '../types' +import type { ResolvedConfig, ResolvedTestEnvironment, RuntimeRPC, WorkerGlobalState } from '../types' import { distDir } from '../paths' import { getWorkerState } from '../utils/global' import { VitestMocker } from './mocker' @@ -21,6 +21,7 @@ export interface ExecuteOptions extends ViteNodeRunnerOptions { moduleDirectories?: string[] context?: vm.Context state: WorkerGlobalState + transform: RuntimeRPC['transform'] } export async function createVitestExecutor(options: ExecuteOptions) { @@ -100,6 +101,9 @@ export async function startVitestExecutor(options: ContextExecutorOptions) { resolveId(id, importer) { return rpc().resolveId(id, importer, getTransformMode()) }, + transform(id) { + return rpc().transform(id, 'web') + }, packageCache, moduleCache, mockMap, @@ -174,12 +178,6 @@ export class VitestExecutor extends ViteNodeRunner { } } else { - this.externalModules = new ExternalModulesExecutor({ - ...options, - fileMap, - context: options.context, - packageCache: options.packageCache, - }) const clientStub = vm.runInContext( `(defaultClient) => ({ ...defaultClient, updateStyle: ${updateStyle.toString()}, removeStyle: ${removeStyle.toString()} })`, options.context, @@ -189,6 +187,12 @@ export class VitestExecutor extends ViteNodeRunner { '@vite/client': clientStub, } this.primitives = vm.runInContext('({ Object, Reflect, Symbol })', options.context) + this.externalModules = new ExternalModulesExecutor({ + ...options, + fileMap, + context: options.context, + packageCache: options.packageCache, + }) } } diff --git a/packages/vitest/src/runtime/external-executor.ts b/packages/vitest/src/runtime/external-executor.ts index bf90480ad3b8..bc426657db86 100644 --- a/packages/vitest/src/runtime/external-executor.ts +++ b/packages/vitest/src/runtime/external-executor.ts @@ -12,6 +12,7 @@ import { CommonjsExecutor } from './vm/commonjs-executor' import type { FileMap } from './vm/file-map' import { EsmExecutor } from './vm/esm-executor' import { interopCommonJsModule } from './vm/utils' +import { ViteExecutor } from './vm/vite-executor' const SyntheticModule: typeof VMSyntheticModule = (vm as any).SyntheticModule @@ -23,12 +24,20 @@ export interface ExternalModulesExecutorOptions extends ExecuteOptions { packageCache: Map } +interface ModuleInformation { + type: 'data' | 'builtin' | 'vite' | 'module' | 'commonjs' + url: string + path: string +} + // TODO: improve Node.js strict mode support in #2854 export class ExternalModulesExecutor { private cjs: CommonjsExecutor private esm: EsmExecutor + private vite: ViteExecutor private context: vm.Context private fs: FileMap + private resolvers: ((id: string, parent: string) => string | undefined)[] = [] constructor(private options: ExternalModulesExecutorOptions) { this.context = options.context @@ -42,6 +51,13 @@ export class ExternalModulesExecutor { importModuleDynamically: this.importModuleDynamically, fileMap: options.fileMap, }) + this.vite = new ViteExecutor({ + esmExecutor: this.esm, + context: this.context, + transform: options.transform, + viteClientModule: options.requestStubs!['/@vite/client'], + }) + this.resolvers = [this.vite.resolve] } // dynamic import can be used in both ESM and CJS, so we have it in the executor @@ -56,6 +72,11 @@ export class ExternalModulesExecutor { } public async resolve(specifier: string, parent: string) { + for (const resolver of this.resolvers) { + const id = resolver(specifier, parent) + if (id) + return id + } return nativeResolve(specifier, parent) } @@ -124,44 +145,59 @@ export class ExternalModulesExecutor { return m } - private async createModule(identifier: string): Promise { + private getModuleInformation(identifier: string): ModuleInformation { if (identifier.startsWith('data:')) - return this.esm.createDataModule(identifier) + return { type: 'data', url: identifier, path: identifier } const extension = extname(identifier) - - if (extension === '.node' || isNodeBuiltin(identifier)) { - const exports = this.require(identifier) - return this.wrapCoreSynteticModule(identifier, exports) - } + if (extension === '.node' || isNodeBuiltin(identifier)) + return { type: 'builtin', url: identifier, path: identifier } const isFileUrl = identifier.startsWith('file://') - const fileUrl = isFileUrl ? identifier : pathToFileURL(identifier).toString() const pathUrl = isFileUrl ? fileURLToPath(identifier.split('?')[0]) : identifier + const fileUrl = isFileUrl ? identifier : pathToFileURL(pathUrl).toString() - // TODO: support wasm in the future - // if (extension === '.wasm') { - // const source = this.readBuffer(pathUrl) - // const wasm = this.loadWebAssemblyModule(source, fileUrl) - // this.moduleCache.set(fileUrl, wasm) - // return wasm - // } - - if (extension === '.cjs') { - const exports = this.require(pathUrl) - return this.wrapCommonJsSynteticModule(fileUrl, exports) + let type: 'module' | 'commonjs' | 'vite' + if (this.vite.canResolve(fileUrl)) { + type = 'vite' + } + else if (extension === '.mjs') { + type = 'module' + } + else if (extension === '.cjs') { + type = 'commonjs' + } + else { + const pkgData = this.findNearestPackageData(normalize(pathUrl)) + type = pkgData.type === 'module' ? 'module' : 'commonjs' } - if (extension === '.mjs') - return await this.esm.createEsmModule(fileUrl, this.fs.readFile(pathUrl)) - - const pkgData = this.findNearestPackageData(normalize(pathUrl)) - - if (pkgData.type === 'module') - return await this.esm.createEsmModule(fileUrl, this.fs.readFile(pathUrl)) + return { type, path: pathUrl, url: fileUrl } + } - const exports = this.cjs.require(pathUrl) - return this.wrapCommonJsSynteticModule(fileUrl, exports) + private async createModule(identifier: string): Promise { + const { type, url, path } = this.getModuleInformation(identifier) + + switch (type) { + case 'data': + return this.esm.createDataModule(identifier) + case 'builtin': { + const exports = this.require(identifier) + return this.wrapCoreSynteticModule(identifier, exports) + } + case 'vite': + return await this.vite.createViteModule(url) + case 'module': + return await this.esm.createEsModule(url, this.fs.readFile(path)) + case 'commonjs': { + const exports = this.require(path) + return this.wrapCommonJsSynteticModule(identifier, exports) + } + default: { + const _deadend: never = type + return _deadend + } + } } async import(identifier: string) { diff --git a/packages/vitest/src/runtime/vm/commonjs-executor.ts b/packages/vitest/src/runtime/vm/commonjs-executor.ts index a423a7817c3b..38782a7231d0 100644 --- a/packages/vitest/src/runtime/vm/commonjs-executor.ts +++ b/packages/vitest/src/runtime/vm/commonjs-executor.ts @@ -21,7 +21,8 @@ interface PrivateNodeModule extends NodeModule { export class CommonjsExecutor { private context: vm.Context - private requireCache: Record = Object.create(null) + private requireCache = new Map() + private publicRequireCache = this.createProxyCache() private moduleCache = new Map>() private builtinCache: Record = Object.create(null) @@ -75,7 +76,7 @@ export class CommonjsExecutor { script.identifier = filename const fn = script.runInContext(executor.context) const __dirname = dirname(filename) - executor.requireCache[filename] = this + executor.requireCache.set(filename, this) try { fn(this.exports, this.require, this, filename, __dirname) return this.exports @@ -165,14 +166,31 @@ export class CommonjsExecutor { set: () => {}, configurable: true, }) - require.main = _require.main - require.cache = this.requireCache + require.main = undefined // there is no main, since we are running tests using ESM + require.cache = this.publicRequireCache return require } + private createProxyCache() { + return new Proxy(Object.create(null), { + defineProperty: () => true, + deleteProperty: () => true, + set: () => true, + get: (_, key: string) => this.requireCache.get(key), + has: (_, key: string) => this.requireCache.has(key), + ownKeys: () => Array.from(this.requireCache.keys()), + getOwnPropertyDescriptor() { + return { + configurable: true, + enumerable: true, + } + }, + }) + } + // very naive implementation for Node.js require private loadCommonJSModule(module: NodeModule, filename: string): Record { - const cached = this.requireCache[filename] + const cached = this.requireCache.get(filename) if (cached) return cached.exports diff --git a/packages/vitest/src/runtime/vm/esm-executor.ts b/packages/vitest/src/runtime/vm/esm-executor.ts index b3bcad880eab..d471a50fc399 100644 --- a/packages/vitest/src/runtime/vm/esm-executor.ts +++ b/packages/vitest/src/runtime/vm/esm-executor.ts @@ -1,19 +1,14 @@ /* eslint-disable antfu/no-cjs-exports */ -import vm from 'node:vm' -import { CSS_LANGS_RE, KNOWN_ASSET_RE } from 'vite-node/constants' -import { extname, normalize } from 'pathe' -import { getColors } from '@vitest/utils' +import type vm from 'node:vm' import type { ExternalModulesExecutor } from '../external-executor' -import type { VMModule, VMSourceTextModule, VMSyntheticModule } from './types' +import type { VMModule } from './types' +import { SourceTextModule, SyntheticModule } from './utils' interface EsmExecutorOptions { context: vm.Context } -const SyntheticModule: typeof VMSyntheticModule = (vm as any).SyntheticModule -const SourceTextModule: typeof VMSourceTextModule = (vm as any).SourceTextModule - const dataURIRegex = /^data:(?text\/javascript|application\/json|application\/wasm)(?:;(?charset=utf-8|base64))?,(?.*)$/ @@ -45,42 +40,10 @@ export class EsmExecutor { return m } - public async createEsmModule(fileUrl: string, code: string) { + public async createEsModule(fileUrl: string, code: string) { const cached = this.moduleCache.get(fileUrl) if (cached) return cached - const [urlPath] = fileUrl.split('?') - if (CSS_LANGS_RE.test(urlPath) || KNOWN_ASSET_RE.test(urlPath)) { - const path = normalize(urlPath) - let name = path.split('/node_modules/').pop() || '' - if (name?.startsWith('@')) - name = name.split('/').slice(0, 2).join('/') - else - name = name.split('/')[0] - const ext = extname(path) - let error = `[vitest] Cannot import ${fileUrl}. At the moment, importing ${ext} files inside external dependencies is not allowed. ` - if (name) { - const c = getColors() - error += 'As a temporary workaround you can try to inline the package by updating your config:' -+ `\n\n${ -c.gray(c.dim('// vitest.config.js')) -}\n${ -c.green(`export default { - test: { - deps: { - optimizer: { - web: { - include: [ - ${c.yellow(c.bold(`"${name}"`))} - ] - } - } - } - } -}\n`)}` - } - throw new Error(error) - } // TODO: should not be allowed in strict mode, implement in #2854 if (fileUrl.endsWith('.json')) { const m = new SyntheticModule( @@ -156,6 +119,14 @@ c.green(`export default { return syntheticModule } + public cacheModule(identifier: string, module: VMModule) { + this.moduleCache.set(identifier, module) + } + + public resolveCachedModule(identifier: string) { + return this.moduleCache.get(identifier) + } + public async createDataModule(identifier: string): Promise { const cached = this.moduleCache.get(identifier) if (cached) @@ -206,6 +177,6 @@ c.green(`export default { return module } - return this.createEsmModule(identifier, code) + return this.createEsModule(identifier, code) } } diff --git a/packages/vitest/src/runtime/vm/utils.ts b/packages/vitest/src/runtime/vm/utils.ts index c9feec352208..dc4c57747766 100644 --- a/packages/vitest/src/runtime/vm/utils.ts +++ b/packages/vitest/src/runtime/vm/utils.ts @@ -1,4 +1,6 @@ +import vm from 'node:vm' import { isPrimitive } from 'vite-node/utils' +import type { VMSourceTextModule, VMSyntheticModule } from './types' export function interopCommonJsModule(interopDefault: boolean | undefined, mod: any) { if (isPrimitive(mod) || Array.isArray(mod) || mod instanceof Promise) { @@ -31,3 +33,6 @@ export function interopCommonJsModule(interopDefault: boolean | undefined, mod: defaultExport: mod, } } + +export const SyntheticModule: typeof VMSyntheticModule = (vm as any).SyntheticModule +export const SourceTextModule: typeof VMSourceTextModule = (vm as any).SourceTextModule diff --git a/packages/vitest/src/runtime/vm/vite-executor.ts b/packages/vitest/src/runtime/vm/vite-executor.ts new file mode 100644 index 000000000000..18d008f035f8 --- /dev/null +++ b/packages/vitest/src/runtime/vm/vite-executor.ts @@ -0,0 +1,101 @@ +import type vm from 'node:vm' +import { pathToFileURL } from 'node:url' +import { normalize } from 'pathe' +import { CSS_LANGS_RE, KNOWN_ASSET_RE } from 'vite-node/constants' +import { toArray } from 'vite-node/utils' +import type { RuntimeRPC } from '../../types/rpc' +import type { WorkerGlobalState } from '../../types' +import type { EsmExecutor } from './esm-executor' +import { SyntheticModule } from './utils' + +interface ViteExecutorOptions { + context: vm.Context + transform: RuntimeRPC['transform'] + esmExecutor: EsmExecutor + viteClientModule: Record +} + +const CLIENT_ID = '/@vite/client' +const CLIENT_FILE = pathToFileURL(CLIENT_ID).href + +export class ViteExecutor { + private esm: EsmExecutor + + constructor(private options: ViteExecutorOptions) { + this.esm = options.esmExecutor + } + + public resolve = (identifier: string, parent: string) => { + if (identifier === CLIENT_ID) { + if (this.workerState.environment.transformMode === 'web') + return identifier + const packageName = this.getPackageName(parent) + throw new Error( + `[vitest] Vitest cannot handle ${CLIENT_ID} imported in ${parent} when running in SSR environment. Add "${packageName}" to "ssr.noExternal" if you are using Vite SSR, or to "server.deps.inline" if you are using Vite Node.`, + ) + } + } + + get workerState(): WorkerGlobalState { + return this.options.context.__vitest_worker__ + } + + private getPackageName(modulePath: string) { + const path = normalize(modulePath) + let name = path.split('/node_modules/').pop() || '' + if (name?.startsWith('@')) + name = name.split('/').slice(0, 2).join('/') + else + name = name.split('/')[0] + return name + } + + public async createViteModule(fileUrl: string) { + if (fileUrl === CLIENT_FILE) + return this.createViteClientModule() + const cached = this.esm.resolveCachedModule(fileUrl) + if (cached) + return cached + const result = await this.options.transform(fileUrl, 'web') + if (!result.code) + throw new Error(`[vitest] Failed to transform ${fileUrl}. Does the file exists?`) + return this.esm.createEsModule(fileUrl, result.code) + } + + private createViteClientModule() { + const identifier = CLIENT_ID + const cached = this.esm.resolveCachedModule(identifier) + if (cached) + return cached + const stub = this.options.viteClientModule + const moduleKeys = Object.keys(stub) + const module = new SyntheticModule( + moduleKeys, + () => { + moduleKeys.forEach((key) => { + module.setExport(key, stub[key]) + }) + }, + { context: this.options.context, identifier }, + ) + this.esm.cacheModule(identifier, module) + return module + } + + public canResolve = (fileUrl: string) => { + const transformMode = this.workerState.environment.transformMode + if (transformMode !== 'web') + return false + if (fileUrl === CLIENT_FILE) + return true + const config = this.workerState.config.deps?.web || {} + const [modulePath] = fileUrl.split('?') + if (config.transformCss && CSS_LANGS_RE.test(modulePath)) + return true + if (config.transformAssets && KNOWN_ASSET_RE.test(modulePath)) + return true + if (toArray(config.transformGlobPattern).some(pattern => pattern.test(modulePath))) + return true + return false + } +} diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts index 670c50a410de..b82071f65249 100644 --- a/packages/vitest/src/types/config.ts +++ b/packages/vitest/src/types/config.ts @@ -106,6 +106,38 @@ interface DepsOptions { web?: DepsOptimizationOptions ssr?: DepsOptimizationOptions } + web?: { + /** + * Should Vitest process assets (.png, .svg, .jpg, etc) files and resolve them like Vite does in the browser. + * + * These module will have a default export equal to the path to the asset, if no query is specified. + * + * **At the moment, this option only works with `experimentalVmThreads` pool.** + * + * @default true + */ + transformAssets?: boolean + /** + * Should Vitest process CSS (.css, .scss, .sass, etc) files and resolve them like Vite does in the browser. + * + * If CSS files are disabled with `css` options, this option will just silence UNKNOWN_EXTENSION errors. + * + * **At the moment, this option only works with `experimentalVmThreads` pool.** + * + * @default true + */ + transformCss?: boolean + /** + * Regexp pattern to match external files that should be transformed. + * + * By default, files inside `node_modules` are externalized and not transformed. + * + * **At the moment, this option only works with `experimentalVmThreads` pool.** + * + * @default [] + */ + transformGlobPattern?: RegExp | RegExp[] + } /** * Externalize means that Vite will bypass the package to native Node. * diff --git a/packages/vitest/src/types/rpc.ts b/packages/vitest/src/types/rpc.ts index 9d0721b8864b..bd93be04c432 100644 --- a/packages/vitest/src/types/rpc.ts +++ b/packages/vitest/src/types/rpc.ts @@ -10,6 +10,7 @@ type TransformMode = 'web' | 'ssr' export interface RuntimeRPC { fetch: (id: string, environment: TransformMode) => Promise + transform: (id: string, environment: TransformMode) => Promise resolveId: (id: string, importer: string | undefined, environment: TransformMode) => Promise getSourceMap: (id: string, force?: boolean) => Promise diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8eddfff146c..b9b54a997e11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1963,6 +1963,9 @@ importers: test/vm-threads: devDependencies: + jsdom: + specifier: latest + version: 22.1.0 vitest: specifier: workspace:* version: link:../../packages/vitest @@ -2342,11 +2345,6 @@ packages: dependencies: '@babel/highlight': 7.22.5 - /@babel/compat-data@7.22.5: - resolution: {integrity: sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/compat-data@7.22.9: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} engines: {node: '>=6.9.0'} @@ -2474,16 +2472,6 @@ packages: jsesc: 2.5.2 dev: true - /@babel/generator@7.22.5: - resolution: {integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.5 - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.18 - jsesc: 2.5.2 - dev: true - /@babel/generator@7.22.9: resolution: {integrity: sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==} engines: {node: '>=6.9.0'} @@ -2514,11 +2502,11 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.22.5 + '@babel/compat-data': 7.22.9 '@babel/core': 7.18.13 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.3 - semver: 6.3.0 + browserslist: 4.21.10 + semver: 6.3.1 dev: true /@babel/helper-compilation-targets@7.22.9(@babel/core@7.18.13): @@ -2589,7 +2577,7 @@ packages: '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-replace-supers': 7.20.7 '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/helper-split-export-declaration': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 transitivePeerDependencies: - supports-color dev: true @@ -2608,7 +2596,7 @@ packages: '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-replace-supers': 7.20.7 '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/helper-split-export-declaration': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 transitivePeerDependencies: - supports-color dev: true @@ -2627,7 +2615,7 @@ packages: '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-replace-supers': 7.20.7 '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/helper-split-export-declaration': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 transitivePeerDependencies: - supports-color dev: true @@ -2755,10 +2743,10 @@ packages: '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-module-imports': 7.22.5 '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.5 '@babel/template': 7.22.5 - '@babel/traverse': 7.22.5 + '@babel/traverse': 7.22.8 '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color @@ -2904,13 +2892,6 @@ packages: '@babel/types': 7.22.5 dev: true - /@babel/helper-split-export-declaration@7.22.5: - resolution: {integrity: sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.5 - dev: true - /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} @@ -2946,7 +2927,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.22.5 - '@babel/traverse': 7.22.5 + '@babel/traverse': 7.22.8 '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color @@ -2984,6 +2965,7 @@ packages: hasBin: true dependencies: '@babel/types': 7.22.5 + dev: false /@babel/parser@7.22.7: resolution: {integrity: sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==} @@ -5032,24 +5014,6 @@ packages: /@babel/traverse@7.18.13: resolution: {integrity: sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.22.5 - '@babel/generator': 7.22.5 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.5 - '@babel/parser': 7.22.7 - '@babel/types': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/traverse@7.22.5: - resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==} - engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.22.5 '@babel/generator': 7.22.9 @@ -6028,7 +5992,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 jest-message-util: 27.5.1 jest-util: 27.5.1 @@ -6049,7 +6013,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.8.1 @@ -6086,7 +6050,7 @@ packages: dependencies: '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 jest-mock: 27.5.1 dev: true @@ -6103,7 +6067,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@sinonjs/fake-timers': 8.1.0 - '@types/node': 18.16.19 + '@types/node': 20.4.6 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 @@ -6132,7 +6096,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -6245,7 +6209,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 '@types/yargs': 15.0.14 chalk: 4.1.2 dev: true @@ -6256,7 +6220,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -6268,7 +6232,7 @@ packages: '@jest/schemas': 29.4.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 '@types/yargs': 17.0.12 chalk: 4.1.2 dev: true @@ -6792,7 +6756,7 @@ packages: engines: {node: '>=14'} hasBin: true dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 playwright-core: 1.28.0 dev: true @@ -8879,7 +8843,7 @@ packages: /@types/cheerio@0.22.31: resolution: {integrity: sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/codemirror@5.60.8: @@ -8949,33 +8913,33 @@ packages: resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==} dependencies: '@types/jsonfile': 6.1.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/fs-extra@9.0.13: resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/glob@8.0.0: resolution: {integrity: sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==} dependencies: '@types/minimatch': 5.1.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/graceful-fs@4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/hast@2.3.4: @@ -9047,7 +9011,7 @@ packages: /@types/jsdom@21.1.1: resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 '@types/tough-cookie': 4.0.2 parse5: 7.1.2 dev: true @@ -9059,7 +9023,7 @@ packages: /@types/jsonfile@6.1.1: resolution: {integrity: sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/lodash@4.14.195: @@ -9093,7 +9057,7 @@ packages: /@types/node-fetch@2.6.2: resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 form-data: 3.0.1 dev: true @@ -9142,7 +9106,7 @@ packages: /@types/prompts@2.4.4: resolution: {integrity: sha512-p5N9uoTH76lLvSAaYSZtBCdEXzpOOufsRjnhjVSrZGXikVGHX9+cc9ERtHRV4hvBKHyZb1bg4K+56Bd2TqUn4A==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 kleur: 3.0.3 dev: true @@ -9219,7 +9183,7 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/resolve@1.20.2: @@ -9236,7 +9200,7 @@ packages: /@types/set-cookie-parser@2.4.2: resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/sinonjs__fake-timers@8.1.1: @@ -9278,7 +9242,7 @@ packages: /@types/through@0.0.30: resolution: {integrity: sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/tough-cookie@4.0.2: @@ -9316,7 +9280,7 @@ packages: /@types/webpack-sources@3.2.0: resolution: {integrity: sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 '@types/source-list-map': 0.1.2 source-map: 0.7.4 dev: true @@ -9324,7 +9288,7 @@ packages: /@types/webpack@4.41.32: resolution: {integrity: sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 '@types/tapable': 1.0.8 '@types/uglify-js': 3.17.0 '@types/webpack-sources': 3.2.0 @@ -9339,7 +9303,7 @@ packages: /@types/ws@8.5.5: resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /@types/yargs-parser@21.0.0: @@ -9368,7 +9332,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true optional: true @@ -9950,7 +9914,7 @@ packages: /@vue/compiler-sfc@3.2.39: resolution: {integrity: sha512-fqAQgFs1/BxTUZkd0Vakn3teKUt//J3c420BgnYgEOoVdTwYpBTSXCMJ88GOBCylmUBbtquGPli9tVs7LzsWIA==} dependencies: - '@babel/parser': 7.22.5 + '@babel/parser': 7.22.7 '@vue/compiler-core': 3.2.39 '@vue/compiler-dom': 3.2.39 '@vue/compiler-ssr': 3.2.39 @@ -11835,17 +11799,6 @@ packages: node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) - /browserslist@4.21.3: - resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001518 - electron-to-chromium: 1.4.479 - node-releases: 2.0.13 - update-browserslist-db: 1.0.11(browserslist@4.21.3) - dev: true - /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -17633,7 +17586,7 @@ packages: '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -17769,7 +17722,7 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 jest-mock: 27.5.1 jest-util: 27.5.1 jsdom: 16.7.0 @@ -17787,7 +17740,7 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 jest-mock: 27.5.1 jest-util: 27.5.1 dev: true @@ -17808,7 +17761,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@types/graceful-fs': 4.1.5 - '@types/node': 18.16.19 + '@types/node': 20.4.6 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -17831,7 +17784,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@types/graceful-fs': 4.1.5 - '@types/node': 18.16.19 + '@types/node': 20.4.6 anymatch: 3.1.2 fb-watchman: 2.0.1 graceful-fs: 4.2.10 @@ -17871,7 +17824,7 @@ packages: '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 co: 4.6.0 expect: 27.5.1 @@ -17951,7 +17904,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 dev: true /jest-pnp-resolver@1.2.3(jest-resolve@27.5.1): @@ -18012,7 +17965,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 emittery: 0.8.1 graceful-fs: 4.2.10 @@ -18069,7 +18022,7 @@ packages: resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} engines: {node: '>= 10.14.2'} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 graceful-fs: 4.2.10 dev: true @@ -18077,7 +18030,7 @@ packages: resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 graceful-fs: 4.2.10 dev: true @@ -18116,7 +18069,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 graceful-fs: 4.2.10 is-ci: 2.0.0 @@ -18128,7 +18081,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.10 @@ -18140,7 +18093,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.0.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.10 @@ -18165,7 +18118,7 @@ packages: dependencies: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 18.16.19 + '@types/node': 20.4.6 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 27.5.1 @@ -18176,7 +18129,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true @@ -18185,7 +18138,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.16.19 + '@types/node': 20.4.6 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -23564,7 +23517,7 @@ packages: mime: 2.6.0 qs: 6.11.0 readable-stream: 3.6.0 - semver: 7.5.2 + semver: 7.5.4 transitivePeerDependencies: - supports-color dev: true @@ -24893,17 +24846,6 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 - /update-browserslist-db@1.0.11(browserslist@4.21.3): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.3 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: diff --git a/test/vm-threads/package.json b/test/vm-threads/package.json index 014665a562f0..ab8e0168b1e5 100644 --- a/test/vm-threads/package.json +++ b/test/vm-threads/package.json @@ -5,6 +5,7 @@ "test": "vitest" }, "devDependencies": { + "jsdom": "latest", "vitest": "workspace:*" } } diff --git a/test/vm-threads/src/external/assets/file1.png b/test/vm-threads/src/external/assets/file1.png new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/vm-threads/src/external/assets/file2.txt b/test/vm-threads/src/external/assets/file2.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/vm-threads/src/external/assets/file3.svg b/test/vm-threads/src/external/assets/file3.svg new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/vm-threads/src/external/css/empty.css b/test/vm-threads/src/external/css/empty.css new file mode 100644 index 000000000000..2e9b1770acc8 --- /dev/null +++ b/test/vm-threads/src/external/css/empty.css @@ -0,0 +1,3 @@ +.test1 { + color: red; +} diff --git a/test/vm-threads/src/external/css/processed.css b/test/vm-threads/src/external/css/processed.css new file mode 100644 index 000000000000..b5703b3eb94a --- /dev/null +++ b/test/vm-threads/src/external/css/processed.css @@ -0,0 +1,3 @@ +.test2 { + color: green; +} diff --git a/test/vm-threads/src/external/css/processed.module.css b/test/vm-threads/src/external/css/processed.module.css new file mode 100644 index 000000000000..156a4ab2d11c --- /dev/null +++ b/test/vm-threads/src/external/css/processed.module.css @@ -0,0 +1,3 @@ +.test3 { + color: yellow; +} diff --git a/test/vm-threads/test/import-external-css-assets.test.js b/test/vm-threads/test/import-external-css-assets.test.js new file mode 100644 index 000000000000..433fb773d775 --- /dev/null +++ b/test/vm-threads/test/import-external-css-assets.test.js @@ -0,0 +1,43 @@ +// @vitest-environment jsdom + +import { describe, expect, test } from 'vitest' + +import '../src/external/css/empty.css' +import '../src/external/css/processed.css' + +import processedModule from '../src/external/css/processed.module.css' + +import file1 from '../src/external/assets/file1.png' +import file2 from '../src/external/assets/file2.txt' +import file3 from '../src/external/assets/file3.svg' + +describe('import external css', () => { + test('when importing empty.css, element doesn\'t change style', () => { + const el = document.createElement('div') + el.classList.add('test1') + expect(el.classList.contains('test1')).toBe(true) + expect(window.getComputedStyle(el).color).toBe('') + }) + + test('when importing processed.css, element changes style', () => { + const el = document.createElement('div') + el.classList.add('test2') + expect(el.classList.contains('test2')).toBe(true) + expect(window.getComputedStyle(el).color).toBe('rgb(0, 128, 0)') + }) + + test('when importing processed.module.css, element changes style', () => { + const el = document.createElement('div') + el.classList.add(processedModule.test3) + expect(el.classList.contains(processedModule.test3)).toBe(true) + expect(window.getComputedStyle(el).color).toBe('rgb(255, 255, 0)') + }) +}) + +describe('import external assets', () => { + test('correctly imports assets as paths', () => { + expect(file1).toBe('/src/external/assets/file1.png') + expect(file2).toBe('/src/external/assets/file2.txt') + expect(file3).toBe('/src/external/assets/file3.svg') + }) +}) diff --git a/test/vm-threads/vitest.config.ts b/test/vm-threads/vitest.config.ts index 75b409798a9e..82a8b7b7f11f 100644 --- a/test/vm-threads/vitest.config.ts +++ b/test/vm-threads/vitest.config.ts @@ -3,6 +3,9 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { experimentalVmThreads: true, + css: { + include: [/processed/], + }, server: { deps: { external: [/src\/external/],