feat: support edge runtime (#1574)

* feat: add edge-runtime environment

https://github.com/vercel/edge-runtime

* chore: apply code review

* chore: ci

* chore: ci

* chore: add @edge-runtime/vm to peerDependencies

* chore: lint

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
Co-authored-by: Vladimir Sheremet <sheremet.va@icloud.com>
This commit is contained in:
Yixuan Xu 2022-07-02 17:51:26 +08:00 committed by GitHub
parent 4a95177356
commit b947be48c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 122 additions and 7 deletions

View File

@ -153,13 +153,14 @@ export default defineConfig({
### environment
- **Type:** `'node' | 'jsdom' | 'happy-dom'`
- **Type:** `'node' | 'jsdom' | 'happy-dom' | 'edge-runtime'`
- **Default:** `'node'`
The environment that will be used for testing. The default environment in Vitest
is a Node.js environment. If you are building a web application, you can use
browser-like environment through either [`jsdom`](https://github.com/jsdom/jsdom)
or [`happy-dom`](https://github.com/capricorn86/happy-dom) instead.
If you are building edge functions, you can use [`edge-runtime`](https://edge-runtime.vercel.app/packages/vm) environment
By adding a `@vitest-environment` docblock or comment at the top of the file,
you can specify another environment to be used for all tests in that file:

View File

@ -465,6 +465,13 @@ Repository: git://github.com/kpdecker/jsdiff.git
---------------------------------------
## eastasianwidth
License: MIT
By: Masaki Komagata
Repository: git://github.com/komagata/eastasianwidth.git
---------------------------------------
## emoji-regex
License: MIT
By: Mathias Bynens

View File

@ -62,6 +62,7 @@
"prepublishOnly": "nr build"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
"@vitest/ui": "*",
"c8": "*",
"happy-dom": "*",
@ -79,6 +80,9 @@
},
"jsdom": {
"optional": true
},
"@edge-runtime/vm": {
"optional": true
}
},
"dependencies": {
@ -94,6 +98,7 @@
},
"devDependencies": {
"@antfu/install-pkg": "^0.1.0",
"@edge-runtime/vm": "1.1.0-beta.10",
"@sinonjs/fake-timers": "^9.1.2",
"@types/diff": "^5.0.2",
"@types/jsdom": "^16.2.14",

View File

@ -0,0 +1,24 @@
import { importModule } from 'local-pkg'
import type { Environment } from '../../types'
import { populateGlobal } from './utils'
export default <Environment>({
name: 'edge-runtime',
async setup(global) {
const { EdgeVM } = await importModule('@edge-runtime/vm') as typeof import('@edge-runtime/vm')
const vm = new EdgeVM({
extend: (context) => {
context.global = context
context.Buffer = Buffer
return context
},
})
const { keys, originals } = populateGlobal(global, vm.context, { bindFunctions: true })
return {
teardown(global) {
keys.forEach(key => delete global[key])
originals.forEach((v, k) => global[k] = v)
},
}
},
})

View File

@ -1,9 +1,19 @@
import node from './node'
import jsdom from './jsdom'
import happy from './happy-dom'
import edge from './edge-runtime'
export const environments = {
node,
jsdom,
'happy-dom': happy,
'edge-runtime': edge,
}
export const envs = Object.keys(environments)
export const envPackageNames: Record<Exclude<keyof typeof environments, 'node'>, string> = {
'jsdom': 'jsdom',
'happy-dom': 'happy-dom',
'edge-runtime': '@edge-runtime/vm',
}

View File

@ -1,4 +1,5 @@
import type { UserConfig as ViteUserConfig } from 'vite'
import { envPackageNames } from '../integrations/env'
import type { UserConfig } from '../types'
import { ensurePackageInstalled } from '../utils'
import { createVitest } from './create'
@ -37,7 +38,8 @@ export async function startVitest(cliFilters: string[], options: CliOptions, vit
}
if (ctx.config.environment && ctx.config.environment !== 'node') {
if (!await ensurePackageInstalled(ctx.config.environment)) {
const packageName = envPackageNames[ctx.config.environment]
if (!await ensurePackageInstalled(packageName)) {
process.exitCode = 1
return false
}

View File

@ -1,6 +1,7 @@
import { promises as fs } from 'fs'
import type { BuiltinEnvironment, ResolvedConfig } from '../types'
import { getWorkerState, resetModules } from '../utils'
import { envs } from '../integrations/env'
import { setupGlobalEnv, withEnv } from './setup'
import { startTests } from './run'
@ -9,8 +10,6 @@ export async function run(files: string[], config: ResolvedConfig): Promise<void
const workerState = getWorkerState()
const envs = ['node', 'jsdom', 'happy-dom']
// if calling from a worker, there will always be one file
// if calling with no-threads, this will be the whole suite
const filesWithEnv = await Promise.all(files.map(async (file) => {

View File

@ -8,7 +8,7 @@ import type { Reporter } from './reporter'
import type { SnapshotStateOptions } from './snapshot'
import type { Arrayable } from './general'
export type BuiltinEnvironment = 'node' | 'jsdom' | 'happy-dom'
export type BuiltinEnvironment = 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime'
export type ApiConfig = Pick<CommonServerOptions, 'port' | 'strictPort' | 'host'>
@ -98,7 +98,7 @@ export interface InlineConfig {
/**
* Running environment
*
* Supports 'node', 'jsdom', 'happy-dom'
* Supports 'node', 'jsdom', 'happy-dom', 'edge-runtime'
*
* @default 'node'
*/

24
pnpm-lock.yaml generated
View File

@ -648,6 +648,7 @@ importers:
packages/vitest:
specifiers:
'@antfu/install-pkg': ^0.1.0
'@edge-runtime/vm': 1.1.0-beta.10
'@sinonjs/fake-timers': ^9.1.2
'@types/chai': ^4.3.1
'@types/chai-subset': ^1.3.3
@ -706,6 +707,7 @@ importers:
vite: 3.0.0-beta.4
devDependencies:
'@antfu/install-pkg': 0.1.0
'@edge-runtime/vm': 1.1.0-beta.10
'@sinonjs/fake-timers': 9.1.2
'@types/diff': 5.0.2
'@types/jsdom': 16.2.14
@ -880,6 +882,16 @@ importers:
pathe: 0.2.0
vitest: link:../../packages/vitest
test/vite-edge:
specifiers:
'@edge-runtime/vm': 1.1.0-beta.10
'@vitest/web-worker': workspace:*
vitest: workspace:*
devDependencies:
'@edge-runtime/vm': 1.1.0-beta.10
'@vitest/web-worker': link:../../packages/web-worker
vitest: link:../../packages/vitest
test/vite-node:
specifiers:
vite-node: workspace:*
@ -3057,6 +3069,16 @@ packages:
react-dom: 18.1.0_react@18.1.0
dev: true
/@edge-runtime/primitives/1.1.0-beta.10:
resolution: {integrity: sha512-hv0i2ce35yspqlnmcSKV/GEKfk8WyB9aTSOk0ZMK6gOR6ZvBDCzDpdacIcGuktaTuDinwon2DLxXRhiAS2PjUg==}
dev: true
/@edge-runtime/vm/1.1.0-beta.10:
resolution: {integrity: sha512-AHeIdWp1OUf4kvA4to56shXZzM66bR84LMNTbYGY7G3+BBr+VkUcvU+hWr8JYCOj/iAHi/fuE9r1TyrMm2pQaw==}
dependencies:
'@edge-runtime/primitives': 1.1.0-beta.10
dev: true
/@emotion/babel-plugin/11.9.2_@babel+core@7.18.2:
resolution: {integrity: sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==}
peerDependencies:
@ -6301,7 +6323,7 @@ packages:
dev: true
/@types/form-data/0.0.33:
resolution: {integrity: sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=}
resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==}
dependencies:
'@types/node': 17.0.40
dev: true

View File

@ -0,0 +1,14 @@
{
"name": "@vitest/test-edge-runtime",
"type": "module",
"private": true,
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
},
"devDependencies": {
"@edge-runtime/vm": "1.1.0-beta.10",
"@vitest/web-worker": "workspace:*",
"vitest": "workspace:*"
}
}

View File

@ -0,0 +1,19 @@
/**
* @vitest-environment edge-runtime
*/
import { describe, expect, it } from 'vitest'
describe('edge runtime api', () => {
it('TextEncoder references the same global Uint8Array constructor', () => {
expect(new TextEncoder().encode('abc')).toBeInstanceOf(Uint8Array)
})
it('allows to run fetch', async () => {
const response = await fetch('https://vitest.dev')
expect(response.status).toEqual(200)
})
it('allows to run crypto', async () => {
const array = new Uint32Array(10)
expect(crypto.getRandomValues(array)).toHaveLength(array.length)
})
})

View File

@ -0,0 +1,5 @@
import { expect, test } from 'vitest'
test('node env should not have crypto', () => {
expect(global).not.toHaveProperty('crypto')
})

View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
export default defineConfig({
test: {
environment: 'node',
},
})