Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: init @vitest/browser package #1302

Merged
merged 62 commits into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
3fb433d
feat: @vitest/web pacakge
antfu May 13, 2022
a7132a5
chore: test
antfu May 13, 2022
1698d57
chore: api
antfu May 13, 2022
3418f53
Merge branch 'main' into feat/web-runner
antfu May 13, 2022
064c134
Merge branch 'main' into feat/web-runner
antfu May 13, 2022
4d13a9d
chore: lint
antfu May 13, 2022
82c82a8
chore: rename package
antfu May 13, 2022
75daf6a
chore: update
antfu May 14, 2022
89eb5cb
chore: update
antfu May 14, 2022
bc12de6
chore: update
antfu May 14, 2022
948a115
chore: update
antfu May 14, 2022
2abbcc7
Merge branch 'main' into feat/web-runner
antfu May 14, 2022
ba6d38f
chore: lint
antfu May 14, 2022
953c6fc
chore: update packing
antfu May 14, 2022
e3ae867
chore: update
antfu May 14, 2022
965dfb0
chore: update
antfu May 14, 2022
5ba7615
chore: lint
antfu May 14, 2022
9beb276
Merge branch 'main' into feat/web-runner
antfu May 14, 2022
2e50b09
Merge branch 'main' into feat/web-runner
antfu May 14, 2022
6fb61cf
chore: ui on browser
userquin May 14, 2022
4898931
Merge branch 'main' into feat/web-runner
userquin May 14, 2022
8fe28f8
chore: add `node:util` polyfill
userquin May 14, 2022
acca53c
chore: fix lint `util.js` polyfill
userquin May 14, 2022
10e9043
chore: fix lint tsconfig.json on test/browser
userquin May 14, 2022
9b6fc09
chore: exclude browser test from `test:ci`
userquin May 14, 2022
c810b3e
chore: exclude also browser test from `test:ci:no-threads`
userquin May 14, 2022
b771df8
fix: include `rollup-plugin-node-polyfills` to resolve client polyfills
userquin May 15, 2022
63b3ef4
chore: more browser config and is node exclusions
userquin May 15, 2022
0e0e22b
update
Aslemammad Jul 10, 2022
efbd3ef
update
Aslemammad Jul 10, 2022
1eac7dd
fix conflicts
Aslemammad Jul 10, 2022
f38468f
fix conflicts
Aslemammad Jul 10, 2022
6d3a1d0
update
Aslemammad Jul 10, 2022
7a6da8b
fix
Aslemammad Jul 11, 2022
4ba14fc
fix
Aslemammad Jul 11, 2022
49b95c5
fix
Aslemammad Jul 11, 2022
c346177
fix
Aslemammad Jul 11, 2022
3448849
fix
Aslemammad Jul 11, 2022
12fa279
upate
Aslemammad Jul 11, 2022
3dc9799
upate
Aslemammad Jul 11, 2022
ed8fa0f
upate
Aslemammad Jul 11, 2022
f48fa9f
remove log
Aslemammad Jul 11, 2022
fe8cbb7
lint
Aslemammad Jul 11, 2022
a31f635
lint
Aslemammad Jul 11, 2022
7cef14e
fix: use `fs.promises` on `saveSnapshotFile`
userquin Jul 11, 2022
2227919
docs: add `--browser` to `CLI` entry
userquin Jul 11, 2022
2e40c87
fix: uncomment some code
Aslemammad Jul 15, 2022
1c157e9
feat: modern-node-polyfills
Aslemammad Jul 17, 2022
0c24f09
update
Aslemammad Jul 17, 2022
cc3e336
update
Aslemammad Jul 17, 2022
65a3312
update
Aslemammad Jul 18, 2022
99a9764
chore: add retries to load config
userquin Jul 18, 2022
9d0b5ee
merge pnpm lock
Aslemammad Jul 18, 2022
49cb7e1
merge
Aslemammad Jul 18, 2022
b881e2b
pnpm lock
Aslemammad Jul 18, 2022
89dfe3d
update
Aslemammad Jul 19, 2022
753dfa3
chore: update
antfu Jul 19, 2022
ccb4aaf
chore: wording
antfu Jul 19, 2022
7c3caaf
Merge remote-tracking branch 'upstream/main' into feat/web-runner
Aslemammad Jul 19, 2022
141b37b
update
Aslemammad Jul 19, 2022
5972bae
update
Aslemammad Jul 19, 2022
d30799f
docs: update readme
antfu Jul 19, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ vitest related /src/index.ts /src/hello-world.js
| `--mode <name>` | Override Vite mode (default: `test`) |
| `--globals` | Inject APIs globally |
| `--dom` | Mock browser api with happy-dom |
| `--browser` | Run tests in browser |
| `--environment <env>` | Runner environment (default: `node`) |
| `--passWithNoTests` | Pass when no tests found |
| `--allowOnly` | Allow tests and suites that are marked as `only` (default: false in CI, true otherwise) |
Expand Down
4 changes: 4 additions & 0 deletions examples/vitesse/src/auto-import.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ declare global {
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
Expand Down Expand Up @@ -48,4 +50,6 @@ declare global {
const useSlots: typeof import('vue')['useSlots']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
2 changes: 1 addition & 1 deletion examples/vitesse/src/components.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'

declare module '@vue/runtime-core' {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"test": "vitest --api -r test/core",
"test:run": "vitest run -r test/core",
"test:all": "cross-env CI=true pnpm -r --stream run test --allowOnly",
"test:ci": "cross-env CI=true pnpm -r --stream --filter !test-fails run test --allowOnly",
"test:ci": "cross-env CI=true pnpm -r --stream --filter !test-fails --filter !test-browser run test --allowOnly",
"test:ci:single-thread": "cross-env CI=true pnpm -r --stream --filter !test-fails run test --allowOnly --no-threads",
"typecheck": "tsc --noEmit",
"ui:build": "vite build packages/ui",
Expand All @@ -40,6 +40,8 @@
"@types/lodash": "^4.14.182",
"@types/node": "^18.0.4",
"@types/ws": "^8.5.3",
"@vitest/browser": "workspace:*",
"@vitest/ui": "workspace:*",
"bumpp": "^8.2.1",
"c8": "^7.11.3",
"cross-env": "^7.0.3",
Expand Down
33 changes: 33 additions & 0 deletions packages/browser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# @vitest/browser

Browser runner for Vitest.

> ⚠️ This package is **not yet ready** and it's for preview only. While this package will be released along with other packages, it will not follow semver for breaking changes until we mark it as ready. **Do not use it in production**.

## Progress

Current Status: **Working in progress**

- [x] Init package and integration
- [x] Stub node packages for Vitest runtime
- [ ] Works in development mode
- [ ] Better log in terminal
- [ ] Fulfill tests (using Browser only APIs, Vue and React components)
- [ ] Show progress and error on the browser page
- [ ] Headless mode in CI
- [ ] Docs

Related PRs

- [#1302](https://github.com/vitest-dev/vitest/pull/1302)

## Development Setup

At project root:

```bash
pnpm dev

cd test/browser
pnpm vitest --browser
```
88 changes: 88 additions & 0 deletions packages/browser/client/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { VitestClient } from '@vitest/ws-client'
import { createClient } from '@vitest/ws-client'
// eslint-disable-next-line no-restricted-imports
import type { ResolvedConfig } from 'vitest'

// @ts-expect-error mocking some node apis
globalThis.process = { env: {}, argv: [], stdout: { write: () => {} } }
globalThis.global = globalThis

export const PORT = import.meta.hot ? '51204' : location.port
export const HOST = [location.hostname, PORT].filter(Boolean).join(':')
export const ENTRY_URL = `${
location.protocol === 'https:' ? 'wss:' : 'ws:'
}//${HOST}/__vitest_api__`

let config: ResolvedConfig | undefined
const browserHashMap = new Map<string, string>()

export const client = createClient(ENTRY_URL, {
handlers: {
async onPathsCollected(paths) {
if (!paths)
return

// const config = __vitest_worker__.config
const now = `${new Date().getTime()}`
paths.forEach((i) => {
browserHashMap.set(i, now)
})

await runTests(paths, config, client)
},
},
})

const ws = client.ws

async function loadConfig() {
let retries = 5
do {
try {
await new Promise(resolve => setTimeout(resolve, 150))
config = await client.rpc.getConfig()
return
}
catch (_) {
// just ignore
}
}
while (--retries > 0)

throw new Error('cannot load configuration after 5 retries')
}

ws.addEventListener('open', async () => {
await loadConfig()

// @ts-expect-error mocking vitest apis
globalThis.__vitest_worker__ = {
config,
browserHashMap,
rpc: client.rpc,
}

// @ts-expect-error mocking vitest apis
globalThis.__vitest_mocker__ = {}
const paths = await client.rpc.getPaths()

const now = `${new Date().getTime()}`
paths.forEach(i => browserHashMap.set(i, now))

const iFrame = document.getElementById('vitest-ui') as HTMLIFrameElement
iFrame.setAttribute('src', '/__vitest__/')

await runTests(paths, config, client)
})

async function runTests(paths: string[], config: any, client: VitestClient) {
const name = '/__vitest_index__'
const { startTests, setupGlobalEnv } = (await import(name)) as unknown as typeof import('vitest/browser')

await setupGlobalEnv(config as any)

await startTests(paths, config as any)

await client.rpc.onFinished()
await client.rpc.onWatcherStart()
}
29 changes: 29 additions & 0 deletions packages/browser/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vitest Browser Runner</title>
<style>
html {
overflow: hidden;
padding: 0;
margin: 0;
}
body {
padding: 0;
margin: 0;
}
#vitest-ui {
width: 100vw;
height: 100vh;
border: none;
}
</style>
</head>
<body>
<iframe id="vitest-ui" src=""></iframe>
<script type="module" src="./client/main.ts"></script>
</body>
</html>
85 changes: 85 additions & 0 deletions packages/browser/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { fileURLToPath } from 'url'
// eslint-disable-next-line no-restricted-imports
import { resolve } from 'path'
import { builtinModules } from 'module'
import { polyfillPath } from 'modern-node-polyfills'
import sirv from 'sirv'
import type { Plugin } from 'vite'
import { resolvePath } from 'mlly'

const stubs = [
'fs',
'local-pkg',
'module',
'noop',
'perf_hooks',
]

const polyfills = [
'util',
'tty',
'process',
'path',
'buffer',
]

export default (base = '/'): Plugin[] => {
const pkgRoot = resolve(fileURLToPath(import.meta.url), '../..')
const distRoot = resolve(pkgRoot, 'dist')

return [
{
enforce: 'pre',
name: 'vitest:browser',
async resolveId(id, _, ctx) {
if (ctx.ssr)
return

if (id === '/__vitest_index__') {
const result = await resolvePath('vitest/browser')
return result
}

if (stubs.includes(id))
return resolve(pkgRoot, 'stubs', id)

if (polyfills.includes(id))
return polyfillPath(normalizeId(id))

return null
},
async configureServer(server) {
server.middlewares.use(
base,
sirv(resolve(distRoot, 'client'), {
single: false,
dev: true,
}),
)
},
},
{
name: 'modern-node-polyfills',
async resolveId(id, _, ctx) {
if (ctx.ssr || !builtinModules.includes(id))
return

id = normalizeId(id)
return { id: await polyfillPath(id), moduleSideEffects: false }
},
},
]
}

function normalizeId(id: string, base?: string): string {
if (base && id.startsWith(base))
id = `/${id.slice(base.length)}`

return id
.replace(/^\/@id\/__x00__/, '\0') // virtual modules start with `\0`
.replace(/^\/@id\//, '')
.replace(/^__vite-browser-external:/, '')
.replace(/^node:/, '')
.replace(/[?&]v=\w+/, '?') // remove ?v= query
.replace(/\?$/, '') // remove end query mark
}
49 changes: 49 additions & 0 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@vitest/browser",
"type": "module",
"version": "0.4.1",
"description": "Browser running for Vitest",
"repository": {
"type": "git",
"url": "git+https://github.com/vitest-dev/vitest.git",
"directory": "packages/browser"
},
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./*": "./*"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"stubs"
],
"scripts": {
"build": "rimraf dist && pnpm build:node && pnpm build:client && pnpm copy",
"build:client": "vite build",
"build:node": "rollup -c",
"dev:client": "vite build --watch",
"dev:node": "rollup -c --watch --watch.include=node/**",
"dev": "rimraf dist && run-p dev:node dev:client",
"copy": "esmo scripts/copy-ui-to-browser.ts"
},
"dependencies": {
"local-pkg": "^0.4.1",
"mlly": "^0.5.2",
"modern-node-polyfills": "^0.0.7",
"rollup-plugin-node-polyfills": "^0.2.1",
"sirv": "^2.0.2"
},
"devDependencies": {
"@types/ws": "^8.2.2",
"@vitest/ws-client": "workspace:*",
"picocolors": "^1.0.0",
"rollup": "^2.67.2",
"vitest": "workspace:*"
}
}
5 changes: 5 additions & 0 deletions packages/browser/public/favicon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions packages/browser/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import esbuild from 'rollup-plugin-esbuild'
import dts from 'rollup-plugin-dts'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import alias from '@rollup/plugin-alias'
import pkg from './package.json'

const external = [
...Object.keys(pkg.dependencies),
...Object.keys(pkg.peerDependencies || {}),
'worker_threads',
]

const plugins = [
alias({
entries: [{ find: /^node:(.+)$/, replacement: '$1' }],
}),
resolve({
preferBuiltins: true,
}),
json(),
commonjs(),
esbuild({
target: 'node14',
}),
]

export default () => [
{
input: [
'./node/index.ts',
],
output: {
dir: 'dist',
format: 'esm',
},
external,
plugins,
},
{
input: './node/index.ts',
output: {
file: 'dist/index.d.ts',
format: 'esm',
},
external,
plugins: [dts()],
},
]