Skip to content

Commit d8304bb

Browse files
authored
perf: improve performance of forks pool (#5592)
1 parent 3a0adec commit d8304bb

File tree

10 files changed

+122
-17
lines changed

10 files changed

+122
-17
lines changed

packages/vite-node/src/server.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export class ViteNodeServer {
3131
web: new Map<string, Promise<TransformResult | null | undefined>>(),
3232
}
3333

34+
private durations = {
35+
ssr: new Map<string, number[]>(),
36+
web: new Map<string, number[]>(),
37+
}
38+
3439
private existingOptimizedDeps = new Set<string>()
3540

3641
fetchCaches = {
@@ -102,6 +107,12 @@ export class ViteNodeServer {
102107
return shouldExternalize(id, this.options.deps, this.externalizeCache)
103108
}
104109

110+
public getTotalDuration() {
111+
const ssrDurations = [...this.durations.ssr.values()].flat()
112+
const webDurations = [...this.durations.web.values()].flat()
113+
return [...ssrDurations, ...webDurations].reduce((a, b) => a + b, 0)
114+
}
115+
105116
private async ensureExists(id: string): Promise<boolean> {
106117
if (this.existingOptimizedDeps.has(id))
107118
return true
@@ -138,16 +149,20 @@ export class ViteNodeServer {
138149
}
139150

140151
async fetchModule(id: string, transformMode?: 'web' | 'ssr'): Promise<FetchResult> {
141-
const moduleId = normalizeModuleId(id)
142152
const mode = transformMode || this.getTransformMode(id)
153+
return this.fetchResult(id, mode)
154+
.then((r) => {
155+
return this.options.sourcemap !== true ? { ...r, map: undefined } : r
156+
})
157+
}
158+
159+
async fetchResult(id: string, mode: 'web' | 'ssr') {
160+
const moduleId = normalizeModuleId(id)
143161
this.assertMode(mode)
144162
const promiseMap = this.fetchPromiseMap[mode]
145163
// reuse transform for concurrent requests
146164
if (!promiseMap.has(moduleId)) {
147165
promiseMap.set(moduleId, this._fetchModule(moduleId, mode)
148-
.then((r) => {
149-
return this.options.sourcemap !== true ? { ...r, map: undefined } : r
150-
})
151166
.finally(() => {
152167
promiseMap.delete(moduleId)
153168
}))
@@ -268,6 +283,12 @@ export class ViteNodeServer {
268283
result,
269284
}
270285

286+
const durations = this.durations[transformMode].get(filePath) || []
287+
this.durations[transformMode].set(
288+
filePath,
289+
[...durations, duration ?? 0],
290+
)
291+
271292
this.fetchCaches[transformMode].set(filePath, cacheEntry)
272293
this.fetchCache.set(filePath, cacheEntry)
273294

packages/vitest/src/integrations/env/loader.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { readFileSync } from 'node:fs'
12
import { normalize, resolve } from 'pathe'
23
import { ViteNodeRunner } from 'vite-node/client'
34
import type { ViteNodeRunnerOptions } from 'vite-node'
@@ -26,7 +27,12 @@ export async function loadEnvironment(ctx: ContextRPC, rpc: WorkerRPC): Promise<
2627
return environments[name]
2728
const loader = await createEnvironmentLoader({
2829
root: ctx.config.root,
29-
fetchModule: id => rpc.fetch(id, 'ssr'),
30+
fetchModule: async (id) => {
31+
const result = await rpc.fetch(id, 'ssr')
32+
if (result.id)
33+
return { code: readFileSync(result.id, 'utf-8') }
34+
return result
35+
},
3036
resolveId: (id, importer) => rpc.resolveId(id, importer, 'ssr'),
3137
})
3238
const root = loader.root

packages/vitest/src/node/pools/rpc.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import { mkdir, writeFile } from 'node:fs/promises'
12
import type { RawSourceMap } from 'vite-node'
3+
import { join } from 'pathe'
24
import type { RuntimeRPC } from '../../types'
35
import type { WorkspaceProject } from '../workspace'
46

7+
const created = new Set()
8+
const promises = new Map<string, Promise<void>>()
9+
510
export function createMethodsRPC(project: WorkspaceProject): RuntimeRPC {
611
const ctx = project.ctx
712
return {
@@ -20,8 +25,31 @@ export function createMethodsRPC(project: WorkspaceProject): RuntimeRPC {
2025
const r = await project.vitenode.transformRequest(id)
2126
return r?.map as RawSourceMap | undefined
2227
},
23-
fetch(id, transformMode) {
24-
return project.vitenode.fetchModule(id, transformMode)
28+
async fetch(id, transformMode) {
29+
const result = await project.vitenode.fetchResult(id, transformMode)
30+
const code = result.code
31+
if (result.externalize)
32+
return result
33+
if ('id' in result)
34+
return { id: result.id as string }
35+
36+
if (!code)
37+
throw new Error(`Failed to fetch module ${id}`)
38+
39+
const dir = join(project.tmpDir, transformMode)
40+
const tmp = join(dir, id.replace(/[/\\?%*:|"<>]/g, '_').replace('\0', '__x00__'))
41+
if (promises.has(tmp)) {
42+
await promises.get(tmp)
43+
return { id: tmp }
44+
}
45+
if (!created.has(dir)) {
46+
await mkdir(dir, { recursive: true })
47+
created.add(dir)
48+
}
49+
promises.set(tmp, writeFile(tmp, code, 'utf-8').finally(() => promises.delete(tmp)))
50+
await promises.get(tmp)
51+
Object.assign(result, { id: tmp })
52+
return { id: tmp }
2553
},
2654
resolveId(id, importer, transformMode) {
2755
return project.vitenode.resolveId(id, importer, transformMode)

packages/vitest/src/node/reporters/base.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export abstract class BaseReporter implements Reporter {
232232
const setupTime = files.reduce((acc, test) => acc + Math.max(0, test.setupDuration || 0), 0)
233233
const testsTime = files.reduce((acc, test) => acc + Math.max(0, test.result?.duration || 0), 0)
234234
const transformTime = this.ctx.projects
235-
.flatMap(w => Array.from(w.vitenode.fetchCache.values()).map(i => i.duration || 0))
235+
.flatMap(w => w.vitenode.getTotalDuration())
236236
.reduce((a, b) => a + b, 0)
237237
const environmentTime = files.reduce((acc, file) => acc + Math.max(0, file.environmentLoad || 0), 0)
238238
const prepareTime = files.reduce((acc, file) => acc + Math.max(0, file.prepareDuration || 0), 0)

packages/vitest/src/node/workspace.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { promises as fs } from 'node:fs'
2+
import { rm } from 'node:fs/promises'
3+
import { tmpdir } from 'node:os'
24
import fg from 'fast-glob'
35
import mm from 'micromatch'
46
import { dirname, isAbsolute, join, relative, resolve, toNamespacedPath } from 'pathe'
@@ -8,10 +10,10 @@ import { ViteNodeServer } from 'vite-node/server'
810
import c from 'picocolors'
911
import { createBrowserServer } from '../integrations/browser/server'
1012
import type { ProvidedContext, ResolvedConfig, UserConfig, UserWorkspaceConfig, Vitest } from '../types'
11-
import { deepMerge } from '../utils'
1213
import type { Typechecker } from '../typecheck/typechecker'
1314
import type { BrowserProvider } from '../types/browser'
1415
import { getBrowserProvider } from '../integrations/browser'
16+
import { deepMerge, nanoid } from '../utils/base'
1517
import { isBrowserEnabled, resolveConfig } from './config'
1618
import { WorkspaceVitestPlugin } from './plugins/workspace'
1719
import { createViteServer } from './vite'
@@ -78,6 +80,9 @@ export class WorkspaceProject {
7880

7981
testFilesList: string[] | null = null
8082

83+
public readonly id = nanoid()
84+
public readonly tmpDir = join(tmpdir(), this.id)
85+
8186
private _globalSetups: GlobalSetupFile[] | undefined
8287
private _provided: ProvidedContext = {} as any
8388

@@ -402,11 +407,19 @@ export class WorkspaceProject {
402407
this.server.close(),
403408
this.typechecker?.stop(),
404409
this.browser?.close(),
410+
this.clearTmpDir(),
405411
].filter(Boolean)).then(() => this._provided = {} as any)
406412
}
407413
return this.closingPromise
408414
}
409415

416+
private async clearTmpDir() {
417+
try {
418+
await rm(this.tmpDir, { force: true, recursive: true })
419+
}
420+
catch {}
421+
}
422+
410423
async initBrowserProvider() {
411424
if (!this.isBrowserEnabled())
412425
return

packages/vitest/src/runtime/execute.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import vm from 'node:vm'
22
import { pathToFileURL } from 'node:url'
3+
import { readFileSync } from 'node:fs'
34
import type { ModuleCacheMap } from 'vite-node/client'
45
import { DEFAULT_REQUEST_STUBS, ViteNodeRunner } from 'vite-node/client'
56
import { isInternalRequest, isNodeBuiltin, isPrimitive, toFilePath } from 'vite-node/utils'
@@ -104,7 +105,12 @@ export async function startVitestExecutor(options: ContextExecutorOptions) {
104105
return { externalize: id }
105106
}
106107

107-
return rpc().fetch(id, getTransformMode())
108+
const result = await rpc().fetch(id, getTransformMode())
109+
if (result.id && !result.externalize) {
110+
const code = readFileSync(result.id, 'utf-8')
111+
return { code }
112+
}
113+
return result
108114
},
109115
resolveId(id, importer) {
110116
return rpc().resolveId(id, importer, getTransformMode())

packages/vitest/src/types/rpc.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import type { AfterSuiteRunMeta } from './worker'
99
type TransformMode = 'web' | 'ssr'
1010

1111
export interface RuntimeRPC {
12-
fetch: (id: string, environment: TransformMode) => Promise<FetchResult>
12+
fetch: (id: string, environment: TransformMode) => Promise<{
13+
externalize?: string
14+
id?: string
15+
}>
1316
transform: (id: string, environment: TransformMode) => Promise<FetchResult>
1417
resolveId: (id: string, importer: string | undefined, environment: TransformMode) => Promise<ViteNodeResolveId | null>
1518
getSourceMap: (id: string, force?: boolean) => Promise<RawSourceMap | undefined>

packages/vitest/src/utils/base.ts

+11
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,14 @@ export function escapeRegExp(s: string) {
169169
export function wildcardPatternToRegExp(pattern: string): RegExp {
170170
return new RegExp(`^${pattern.split('*').map(escapeRegExp).join('.*')}$`, 'i')
171171
}
172+
173+
// port from nanoid
174+
// https://github.com/ai/nanoid
175+
const urlAlphabet
176+
= 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'
177+
export function nanoid(size = 21) {
178+
let id = ''
179+
let i = size
180+
while (i--) id += urlAlphabet[(Math.random() * 64) | 0]
181+
return id
182+
}

packages/web-worker/src/utils.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { readFileSync } from 'node:fs'
12
import type { WorkerGlobalState } from 'vitest'
23
import ponyfillStructuredClone from '@ungap/structured-clone'
34
import createDebug from 'debug'
@@ -65,8 +66,13 @@ export function getRunnerOptions(): any {
6566
const { config, rpc, mockMap, moduleCache } = state
6667

6768
return {
68-
fetchModule(id: string) {
69-
return rpc.fetch(id, 'web')
69+
async fetchModule(id: string) {
70+
const result = await rpc.fetch(id, 'web')
71+
if (result.id && !result.externalize) {
72+
const code = readFileSync(result.id, 'utf-8')
73+
return { code }
74+
}
75+
return result
7076
},
7177
resolveId(id: string, importer?: string) {
7278
return rpc.resolveId(id, importer, 'web')

pnpm-lock.yaml

+15-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)