Skip to content

Commit 570c639

Browse files
authoredFeb 6, 2023
perf: don't inline Vitest entry (#2819)
* perf: don't inline Vitest entry * build: remove runners-chunk * chore: import environment via executor * chore: pass down path correct path on windows * chore: remove "initializeSpyModule" since it is no longer required
1 parent aa1aa4d commit 570c639

File tree

9 files changed

+54
-70
lines changed

9 files changed

+54
-70
lines changed
 

‎packages/vitest/rollup.config.js

-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ export default ({ watch }) => defineConfig([
7676
let id = chunkInfo.facadeModuleId || Object.keys(chunkInfo.modules).find(i => !i.includes('node_modules') && (i.includes('src/') || i.includes('src\\')))
7777
if (id) {
7878
id = normalize(id)
79-
if (id.includes('runtime/runners'))
80-
return 'runners-chunk.js'
8179
const parts = Array.from(
8280
new Set(relative(process.cwd(), id).split(/\//g)
8381
.map(i => i.replace(/\..*$/, ''))

‎packages/vitest/src/node/config.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ const extraInlineDeps = [
1818
// Vite client
1919
/vite\w*\/dist\/client\/env.mjs/,
2020
// Vitest
21-
/\/vitest\/dist\/(runners-chunk|entry)\.js/,
21+
/\/vitest\/dist\/runners\.js/,
2222
// yarn's .store folder
23-
/vitest-virtual-\w+\/dist\/(runners-chunk|entry)\.js/,
23+
/vitest-virtual-\w+\/dist\/runners\.js/,
2424
// cnpm
25-
/@vitest\/dist\/(runners-chunk|entry)\.js/,
25+
/@vitest\/dist\/runners\.js/,
2626
// Nuxt
2727
'@nuxt/test-utils',
2828
]

‎packages/vitest/src/node/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export { createVitest } from './create'
33
export { VitestPlugin } from './plugins'
44
export { startVitest } from './cli-api'
55

6-
export { VitestRunner } from '../runtime/execute'
6+
export { VitestExecutor } from '../runtime/execute'
77
export type { ExecuteOptions } from '../runtime/execute'
88

99
export type { TestSequencer, TestSequencerConstructor } from './sequencers/types'

‎packages/vitest/src/runtime/entry.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import { promises as fs } from 'node:fs'
22
import mm from 'micromatch'
33
import type { VitestRunner, VitestRunnerConstructor } from '@vitest/runner'
44
import { startTests } from '@vitest/runner'
5+
import { resolve } from 'pathe'
56
import type { EnvironmentOptions, ResolvedConfig, VitestEnvironment } from '../types'
67
import { getWorkerState, resetModules } from '../utils'
78
import { vi } from '../integrations/vi'
89
import { envs } from '../integrations/env'
910
import { takeCoverageInsideWorker } from '../integrations/coverage'
11+
import { distDir } from '../constants'
1012
import { setupGlobalEnv, withEnv } from './setup.node'
11-
import { VitestTestRunner } from './runners/test'
12-
import { NodeBenchmarkRunner } from './runners/benchmark'
1313
import { rpc } from './rpc'
14+
import type { VitestExecutor } from './execute'
1415

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

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

33-
async function getTestRunner(config: ResolvedConfig): Promise<VitestRunner> {
34-
const TestRunner = await getTestRunnerConstructor(config)
36+
async function getTestRunner(config: ResolvedConfig, executor: VitestExecutor): Promise<VitestRunner> {
37+
const TestRunner = await getTestRunnerConstructor(config, executor)
3538
const testRunner = new TestRunner(config)
3639

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

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

7174
const workerState = getWorkerState()
7275

73-
const runner = await getTestRunner(config)
76+
const runner = await getTestRunner(config, executor)
7477

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

126-
await withEnv(environment, files[0].envOptions || config.environmentOptions || {}, async () => {
129+
await withEnv(environment, files[0].envOptions || config.environmentOptions || {}, executor, async () => {
127130
for (const { file } of files) {
128131
// it doesn't matter if running with --threads
129132
// if running with --no-threads, we usually want to reset everything before running a test

‎packages/vitest/src/runtime/execute.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,15 @@ export interface ExecuteOptions extends ViteNodeRunnerOptions {
1111
mockMap: MockMap
1212
}
1313

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

1717
await runner.executeId('/@vite/env')
18-
await runner.mocker.initializeSpyModule()
1918

20-
const result: any[] = []
21-
for (const file of options.files)
22-
result.push(await runner.executeFile(file))
23-
24-
return result
19+
return runner
2520
}
2621

27-
export class VitestRunner extends ViteNodeRunner {
22+
export class VitestExecutor extends ViteNodeRunner {
2823
public mocker: VitestMocker
2924

3025
constructor(public options: ExecuteOptions) {

‎packages/vitest/src/runtime/mocker.ts

+13-29
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { basename, dirname, extname, isAbsolute, join, resolve } from 'pathe'
44
import { getColors, getType } from '@vitest/utils'
55
import { getWorkerState } from '../utils/global'
66
import { getAllMockableProperties } from '../utils/base'
7-
import { distDir } from '../constants'
7+
import { spyOn } from '../integrations/spy'
88
import type { MockFactory, PendingSuiteMock } from '../types/mocker'
9-
import type { VitestRunner } from './execute'
9+
import type { VitestExecutor } from './execute'
1010

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

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

4543
constructor(
46-
public runner: VitestRunner,
44+
public executor: VitestExecutor,
4745
) {}
4846

4947
private get root() {
50-
return this.runner.options.root
48+
return this.executor.options.root
5149
}
5250

5351
private get base() {
54-
return this.runner.options.base
52+
return this.executor.options.base
5553
}
5654

5755
private get mockMap() {
58-
return this.runner.options.mockMap
56+
return this.executor.options.mockMap
5957
}
6058

6159
private get moduleCache() {
62-
return this.runner.moduleCache
60+
return this.executor.moduleCache
6361
}
6462

6563
public getSuiteFilepath(): string {
@@ -78,7 +76,7 @@ export class VitestMocker {
7876
}
7977

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

204202
public mockObject(object: Record<Key, any>, mockExports: Record<Key, any> = {}) {
205-
if (!VitestMocker.spyModule) {
206-
throw new Error(
207-
'Error: Spy module is not defined. '
208-
+ 'This is likely an internal bug in Vitest. '
209-
+ 'Please report it to https://github.com/vitest-dev/vitest/issues')
210-
}
211-
const spyModule = VitestMocker.spyModule
212-
213203
const finalizers = new Array<() => void>()
214204
const refs = new RefTracker()
215205

@@ -271,7 +261,7 @@ export class VitestMocker {
271261
continue
272262

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

322312
public async importActual<T>(rawId: string, importee: string): Promise<T> {
323313
const { id, fsPath } = await this.resolvePath(rawId, importee)
324-
const result = await this.runner.cachedRequest(id, fsPath, [importee])
314+
const result = await this.executor.cachedRequest(id, fsPath, [importee])
325315
return result as T
326316
}
327317

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

337327
if (mock === null) {
338-
const mod = await this.runner.cachedRequest(id, fsPath, [importee])
328+
const mod = await this.executor.cachedRequest(id, fsPath, [importee])
339329
return this.mockObject(mod)
340330
}
341331

342332
if (typeof mock === 'function')
343333
return this.callFunctionMock(fsPath, mock)
344-
return this.runner.dependencyRequest(mock, mock, [importee])
345-
}
346-
347-
public async initializeSpyModule() {
348-
if (VitestMocker.spyModule)
349-
return
350-
VitestMocker.spyModule = await this.runner.executeId(VitestMocker.spyModulePath)
334+
return this.executor.dependencyRequest(mock, mock, [importee])
351335
}
352336

353337
public async requestWithMock(url: string, callstack: string[]) {
@@ -367,7 +351,7 @@ export class VitestMocker {
367351
const exports = {}
368352
// Assign the empty exports object early to allow for cycles to work. The object will be filled by mockObject()
369353
this.moduleCache.set(mockPath, { exports })
370-
const mod = await this.runner.directRequest(url, url, callstack)
354+
const mod = await this.executor.directRequest(url, url, callstack)
371355
this.mockObject(mod, exports)
372356
return exports
373357
}

‎packages/vitest/src/runtime/setup.node.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { setupSnapshotEnvironment } from '../integrations/snapshot/env'
1212
import { NodeSnapshotEnvironment } from '../integrations/snapshot/environments/node'
1313
import { rpc } from './rpc'
1414
import { setupCommonEnv } from './setup.common'
15+
import type { VitestExecutor } from './execute'
1516

1617
// this should only be used in Node
1718
let globalSetup = false
@@ -154,8 +155,8 @@ export async function setupConsoleLogSpy() {
154155
})
155156
}
156157

157-
async function loadEnvironment(name: string) {
158-
const pkg = await import(`vitest-environment-${name}`)
158+
async function loadEnvironment(name: string, executor: VitestExecutor) {
159+
const pkg = await executor.executeId(`vitest-environment-${name}`)
159160
if (!pkg || !pkg.default || typeof pkg.default !== 'object' || typeof pkg.default.setup !== 'function') {
160161
throw new Error(
161162
`Environment "${name}" is not a valid environment. `
@@ -168,9 +169,10 @@ async function loadEnvironment(name: string) {
168169
export async function withEnv(
169170
name: ResolvedConfig['environment'],
170171
options: ResolvedConfig['environmentOptions'],
172+
executor: VitestExecutor,
171173
fn: () => Promise<void>,
172174
) {
173-
const config: Environment = (environments as any)[name] || await loadEnvironment(name)
175+
const config: Environment = (environments as any)[name] || await loadEnvironment(name, executor)
174176
// @ts-expect-error untyped global
175177
globalThis.__vitest_environment__ = config.name || name
176178
expect.setState({

‎packages/vitest/src/runtime/worker.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { pathToFileURL } from 'node:url'
12
import { relative, resolve } from 'pathe'
23
import { createBirpc } from 'birpc'
34
import { workerId as poolId } from 'tinypool'
@@ -8,11 +9,13 @@ import type { ResolvedConfig, WorkerContext, WorkerRPC } from '../types'
89
import { distDir } from '../constants'
910
import { getWorkerState } from '../utils/global'
1011
import type { MockMap } from '../types/mocker'
11-
import { executeInViteNode } from './execute'
12+
import type { VitestExecutor } from './execute'
13+
import { createVitestExecutor } from './execute'
1214
import { rpc } from './rpc'
1315

1416
let _viteNode: {
15-
run: (files: string[], config: ResolvedConfig) => Promise<void>
17+
run: (files: string[], config: ResolvedConfig, executor: VitestExecutor) => Promise<void>
18+
executor: VitestExecutor
1619
}
1720

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

48-
const { run } = (await executeInViteNode({
49-
files: [
50-
resolve(distDir, 'entry.js'),
51-
],
51+
const executor = await createVitestExecutor({
5252
fetchModule(id) {
5353
return rpc().fetch(id)
5454
},
@@ -60,9 +60,11 @@ async function startViteNode(ctx: WorkerContext) {
6060
interopDefault: config.deps.interopDefault,
6161
root: config.root,
6262
base: config.base,
63-
}))[0]
63+
})
6464

65-
_viteNode = { run }
65+
const { run } = await import(pathToFileURL(resolve(distDir, 'entry.js')).href)
66+
67+
_viteNode = { run, executor }
6668

6769
return _viteNode
6870
}
@@ -106,6 +108,6 @@ function init(ctx: WorkerContext) {
106108

107109
export async function run(ctx: WorkerContext) {
108110
init(ctx)
109-
const { run } = await startViteNode(ctx)
110-
return run(ctx.files, ctx.config)
111+
const { run, executor } = await startViteNode(ctx)
112+
return run(ctx.files, ctx.config, executor)
111113
}

‎packages/web-worker/src/runner.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { VitestRunner } from 'vitest/node'
1+
import { VitestExecutor } from 'vitest/node'
22

3-
export class InlineWorkerRunner extends VitestRunner {
3+
export class InlineWorkerRunner extends VitestExecutor {
44
constructor(options: any, private context: any) {
55
super(options)
66
}

0 commit comments

Comments
 (0)
Please sign in to comment.