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

perf: don't inline Vitest entry #2819

Merged
merged 6 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions packages/vitest/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ export default ({ watch }) => defineConfig([
let id = chunkInfo.facadeModuleId || Object.keys(chunkInfo.modules).find(i => !i.includes('node_modules') && (i.includes('src/') || i.includes('src\\')))
if (id) {
id = normalize(id)
if (id.includes('runtime/runners'))
return 'runners-chunk.js'
const parts = Array.from(
new Set(relative(process.cwd(), id).split(/\//g)
.map(i => i.replace(/\..*$/, ''))
Expand Down
6 changes: 3 additions & 3 deletions packages/vitest/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ const extraInlineDeps = [
// Vite client
/vite\w*\/dist\/client\/env.mjs/,
// Vitest
/\/vitest\/dist\/(runners-chunk|entry)\.js/,
/\/vitest\/dist\/runners\.js/,
// yarn's .store folder
/vitest-virtual-\w+\/dist\/(runners-chunk|entry)\.js/,
/vitest-virtual-\w+\/dist\/runners\.js/,
// cnpm
/@vitest\/dist\/(runners-chunk|entry)\.js/,
/@vitest\/dist\/runners\.js/,
// Nuxt
'@nuxt/test-utils',
]
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export { createVitest } from './create'
export { VitestPlugin } from './plugins'
export { startVitest } from './cli-api'

export { VitestRunner } from '../runtime/execute'
export { VitestExecutor } from '../runtime/execute'
export type { ExecuteOptions } from '../runtime/execute'

export type { TestSequencer, TestSequencerConstructor } from './sequencers/types'
Expand Down
25 changes: 14 additions & 11 deletions packages/vitest/src/runtime/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { promises as fs } from 'node:fs'
import mm from 'micromatch'
import type { VitestRunner, VitestRunnerConstructor } from '@vitest/runner'
import { startTests } from '@vitest/runner'
import { resolve } from 'pathe'
import type { EnvironmentOptions, ResolvedConfig, VitestEnvironment } from '../types'
import { getWorkerState, resetModules } from '../utils'
import { vi } from '../integrations/vi'
import { envs } from '../integrations/env'
import { takeCoverageInsideWorker } from '../integrations/coverage'
import { distDir } from '../constants'
import { setupGlobalEnv, withEnv } from './setup.node'
import { VitestTestRunner } from './runners/test'
import { NodeBenchmarkRunner } from './runners/benchmark'
import { rpc } from './rpc'
import type { VitestExecutor } from './execute'

function groupBy<T, K extends string | number | symbol>(collection: T[], iteratee: (item: T) => K) {
return collection.reduce((acc, item) => {
Expand All @@ -21,17 +22,19 @@ function groupBy<T, K extends string | number | symbol>(collection: T[], iterate
}, {} as Record<K, T[]>)
}

async function getTestRunnerConstructor(config: ResolvedConfig): Promise<VitestRunnerConstructor> {
if (!config.runner)
return (config.mode === 'test' ? VitestTestRunner : NodeBenchmarkRunner) as any as VitestRunnerConstructor
const mod = await import(config.runner)
async function getTestRunnerConstructor(config: ResolvedConfig, executor: VitestExecutor): Promise<VitestRunnerConstructor> {
if (!config.runner) {
const { VitestTestRunner, NodeBenchmarkRunner } = await executor.executeFile(resolve(distDir, 'runners.js'))
return (config.mode === 'test' ? VitestTestRunner : NodeBenchmarkRunner) as VitestRunnerConstructor
}
const mod = await executor.executeId(config.runner)
if (!mod.default && typeof mod.default !== 'function')
throw new Error(`Runner must export a default function, but got ${typeof mod.default} imported from ${config.runner}`)
return mod.default as VitestRunnerConstructor
}

async function getTestRunner(config: ResolvedConfig): Promise<VitestRunner> {
const TestRunner = await getTestRunnerConstructor(config)
async function getTestRunner(config: ResolvedConfig, executor: VitestExecutor): Promise<VitestRunner> {
const TestRunner = await getTestRunnerConstructor(config, executor)
const testRunner = new TestRunner(config)

if (!testRunner.config)
Expand Down Expand Up @@ -65,12 +68,12 @@ async function getTestRunner(config: ResolvedConfig): Promise<VitestRunner> {
}

// browser shouldn't call this!
export async function run(files: string[], config: ResolvedConfig): Promise<void> {
export async function run(files: string[], config: ResolvedConfig, executor: VitestExecutor): Promise<void> {
await setupGlobalEnv(config)

const workerState = getWorkerState()

const runner = await getTestRunner(config)
const runner = await getTestRunner(config, executor)

// if calling from a worker, there will always be one file
// if calling with no-threads, this will be the whole suite
Expand Down Expand Up @@ -123,7 +126,7 @@ export async function run(files: string[], config: ResolvedConfig): Promise<void
if (!files || !files.length)
continue

await withEnv(environment, files[0].envOptions || config.environmentOptions || {}, async () => {
await withEnv(environment, files[0].envOptions || config.environmentOptions || {}, executor, async () => {
for (const { file } of files) {
// it doesn't matter if running with --threads
// if running with --no-threads, we usually want to reset everything before running a test
Expand Down
13 changes: 4 additions & 9 deletions packages/vitest/src/runtime/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,15 @@ export interface ExecuteOptions extends ViteNodeRunnerOptions {
mockMap: MockMap
}

export async function executeInViteNode(options: ExecuteOptions & { files: string[] }) {
const runner = new VitestRunner(options)
export async function createVitestExecutor(options: ExecuteOptions) {
const runner = new VitestExecutor(options)

await runner.executeId('/@vite/env')
await runner.mocker.initializeSpyModule()

const result: any[] = []
for (const file of options.files)
result.push(await runner.executeFile(file))

return result
return runner
}

export class VitestRunner extends ViteNodeRunner {
export class VitestExecutor extends ViteNodeRunner {
public mocker: VitestMocker

constructor(public options: ExecuteOptions) {
Expand Down
42 changes: 13 additions & 29 deletions packages/vitest/src/runtime/mocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { basename, dirname, extname, isAbsolute, join, resolve } from 'pathe'
import { getColors, getType } from '@vitest/utils'
import { getWorkerState } from '../utils/global'
import { getAllMockableProperties } from '../utils/base'
import { distDir } from '../constants'
import { spyOn } from '../integrations/spy'
import type { MockFactory, PendingSuiteMock } from '../types/mocker'
import type { VitestRunner } from './execute'
import type { VitestExecutor } from './execute'

class RefTracker {
private idMap = new Map<any, number>()
Expand Down Expand Up @@ -38,28 +38,26 @@ function isSpecialProp(prop: Key, parentType: string) {

export class VitestMocker {
private static pendingIds: PendingSuiteMock[] = []
private static spyModulePath = resolve(distDir, 'spy.js')
private static spyModule?: typeof import('../integrations/spy')
private resolveCache = new Map<string, Record<string, string>>()

constructor(
public runner: VitestRunner,
public executor: VitestExecutor,
) {}

private get root() {
return this.runner.options.root
return this.executor.options.root
}

private get base() {
return this.runner.options.base
return this.executor.options.base
}

private get mockMap() {
return this.runner.options.mockMap
return this.executor.options.mockMap
}

private get moduleCache() {
return this.runner.moduleCache
return this.executor.moduleCache
}

public getSuiteFilepath(): string {
Expand All @@ -78,7 +76,7 @@ export class VitestMocker {
}

private async resolvePath(rawId: string, importer: string) {
const [id, fsPath] = await this.runner.resolveUrl(rawId, importer)
const [id, fsPath] = await this.executor.resolveUrl(rawId, importer)
// external is node_module or unresolved module
// for example, some people mock "vscode" and don't have it installed
const external = !isAbsolute(fsPath) || fsPath.includes('/node_modules/') ? rawId : null
Expand Down Expand Up @@ -202,14 +200,6 @@ export class VitestMocker {
}

public mockObject(object: Record<Key, any>, mockExports: Record<Key, any> = {}) {
if (!VitestMocker.spyModule) {
throw new Error(
'Error: Spy module is not defined. '
+ 'This is likely an internal bug in Vitest. '
+ 'Please report it to https://github.com/vitest-dev/vitest/issues')
}
const spyModule = VitestMocker.spyModule

const finalizers = new Array<() => void>()
const refs = new RefTracker()

Expand Down Expand Up @@ -271,7 +261,7 @@ export class VitestMocker {
continue

if (isFunction) {
spyModule.spyOn(newContainer, property).mockImplementation(() => undefined)
spyOn(newContainer, property).mockImplementation(() => undefined)
// tinyspy retains length, but jest doesn't.
Object.defineProperty(newContainer[property], 'length', { value: 0 })
}
Expand Down Expand Up @@ -321,7 +311,7 @@ export class VitestMocker {

public async importActual<T>(rawId: string, importee: string): Promise<T> {
const { id, fsPath } = await this.resolvePath(rawId, importee)
const result = await this.runner.cachedRequest(id, fsPath, [importee])
const result = await this.executor.cachedRequest(id, fsPath, [importee])
return result as T
}

Expand All @@ -335,19 +325,13 @@ export class VitestMocker {
mock = this.resolveMockPath(fsPath, external)

if (mock === null) {
const mod = await this.runner.cachedRequest(id, fsPath, [importee])
const mod = await this.executor.cachedRequest(id, fsPath, [importee])
return this.mockObject(mod)
}

if (typeof mock === 'function')
return this.callFunctionMock(fsPath, mock)
return this.runner.dependencyRequest(mock, mock, [importee])
}

public async initializeSpyModule() {
if (VitestMocker.spyModule)
return
VitestMocker.spyModule = await this.runner.executeId(VitestMocker.spyModulePath)
return this.executor.dependencyRequest(mock, mock, [importee])
}

public async requestWithMock(url: string, callstack: string[]) {
Expand All @@ -367,7 +351,7 @@ export class VitestMocker {
const exports = {}
// Assign the empty exports object early to allow for cycles to work. The object will be filled by mockObject()
this.moduleCache.set(mockPath, { exports })
const mod = await this.runner.directRequest(url, url, callstack)
const mod = await this.executor.directRequest(url, url, callstack)
this.mockObject(mod, exports)
return exports
}
Expand Down
8 changes: 5 additions & 3 deletions packages/vitest/src/runtime/setup.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { setupSnapshotEnvironment } from '../integrations/snapshot/env'
import { NodeSnapshotEnvironment } from '../integrations/snapshot/environments/node'
import { rpc } from './rpc'
import { setupCommonEnv } from './setup.common'
import type { VitestExecutor } from './execute'

// this should only be used in Node
let globalSetup = false
Expand Down Expand Up @@ -154,8 +155,8 @@ export async function setupConsoleLogSpy() {
})
}

async function loadEnvironment(name: string) {
const pkg = await import(`vitest-environment-${name}`)
async function loadEnvironment(name: string, executor: VitestExecutor) {
const pkg = await executor.executeId(`vitest-environment-${name}`)
if (!pkg || !pkg.default || typeof pkg.default !== 'object' || typeof pkg.default.setup !== 'function') {
throw new Error(
`Environment "${name}" is not a valid environment. `
Expand All @@ -168,9 +169,10 @@ async function loadEnvironment(name: string) {
export async function withEnv(
name: ResolvedConfig['environment'],
options: ResolvedConfig['environmentOptions'],
executor: VitestExecutor,
fn: () => Promise<void>,
) {
const config: Environment = (environments as any)[name] || await loadEnvironment(name)
const config: Environment = (environments as any)[name] || await loadEnvironment(name, executor)
// @ts-expect-error untyped global
globalThis.__vitest_environment__ = config.name || name
expect.setState({
Expand Down
22 changes: 12 additions & 10 deletions packages/vitest/src/runtime/worker.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { pathToFileURL } from 'node:url'
import { relative, resolve } from 'pathe'
import { createBirpc } from 'birpc'
import { workerId as poolId } from 'tinypool'
Expand All @@ -8,11 +9,13 @@ import type { ResolvedConfig, WorkerContext, WorkerRPC } from '../types'
import { distDir } from '../constants'
import { getWorkerState } from '../utils/global'
import type { MockMap } from '../types/mocker'
import { executeInViteNode } from './execute'
import type { VitestExecutor } from './execute'
import { createVitestExecutor } from './execute'
import { rpc } from './rpc'

let _viteNode: {
run: (files: string[], config: ResolvedConfig) => Promise<void>
run: (files: string[], config: ResolvedConfig, executor: VitestExecutor) => Promise<void>
executor: VitestExecutor
}

const moduleCache = new ModuleCacheMap()
Expand Down Expand Up @@ -45,10 +48,7 @@ async function startViteNode(ctx: WorkerContext) {
process.on('uncaughtException', e => catchError(e, 'Uncaught Exception'))
process.on('unhandledRejection', e => catchError(e, 'Unhandled Rejection'))

const { run } = (await executeInViteNode({
files: [
resolve(distDir, 'entry.js'),
],
const executor = await createVitestExecutor({
fetchModule(id) {
return rpc().fetch(id)
},
Expand All @@ -60,9 +60,11 @@ async function startViteNode(ctx: WorkerContext) {
interopDefault: config.deps.interopDefault,
root: config.root,
base: config.base,
}))[0]
})

_viteNode = { run }
const { run } = await import(pathToFileURL(resolve(distDir, 'entry.js')).href)

_viteNode = { run, executor }

return _viteNode
}
Expand Down Expand Up @@ -106,6 +108,6 @@ function init(ctx: WorkerContext) {

export async function run(ctx: WorkerContext) {
init(ctx)
const { run } = await startViteNode(ctx)
return run(ctx.files, ctx.config)
const { run, executor } = await startViteNode(ctx)
return run(ctx.files, ctx.config, executor)
}
4 changes: 2 additions & 2 deletions packages/web-worker/src/runner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { VitestRunner } from 'vitest/node'
import { VitestExecutor } from 'vitest/node'

export class InlineWorkerRunner extends VitestRunner {
export class InlineWorkerRunner extends VitestExecutor {
constructor(options: any, private context: any) {
super(options)
}
Expand Down