Skip to content

Commit 8e15bc8

Browse files
authored
perf: create only one fetcher per project (#8762)
1 parent 3dfe095 commit 8e15bc8

File tree

5 files changed

+67
-46
lines changed

5 files changed

+67
-46
lines changed

packages/vitest/src/node/core.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ import type { ModuleRunner } from 'vite/module-runner'
66
import type { SerializedCoverageConfig } from '../runtime/config'
77
import type { ArgumentsType, ProvidedContext, UserConsoleLog } from '../types/general'
88
import type { CliOptions } from './cli/cli-api'
9+
import type { VitestFetchFunction } from './environments/fetchModule'
910
import type { ProcessPool } from './pool'
1011
import type { TestModule } from './reporters/reported-tasks'
1112
import type { TestSpecification } from './spec'
1213
import type { ResolvedConfig, TestProjectConfiguration, UserConfig, VitestRunMode } from './types/config'
1314
import type { CoverageProvider, ResolvedCoverageOptions } from './types/coverage'
1415
import type { Reporter } from './types/reporter'
1516
import type { TestRunResult } from './types/tests'
16-
import os from 'node:os'
17+
import os, { tmpdir } from 'node:os'
1718
import { getTasks, hasFailed, limitConcurrency } from '@vitest/runner/utils'
1819
import { SnapshotManager } from '@vitest/snapshot/manager'
19-
import { deepClone, deepMerge, noop, toArray } from '@vitest/utils/helpers'
20-
import { normalize, relative } from 'pathe'
20+
import { deepClone, deepMerge, nanoid, noop, toArray } from '@vitest/utils/helpers'
21+
import { join, normalize, relative } from 'pathe'
2122
import { version } from '../../package.json' with { type: 'json' }
2223
import { WebSocketReporter } from '../api/setup'
2324
import { distDir } from '../paths'
@@ -28,6 +29,7 @@ import { BrowserSessions } from './browser/sessions'
2829
import { VitestCache } from './cache'
2930
import { resolveConfig } from './config/resolveConfig'
3031
import { getCoverageProvider } from './coverage'
32+
import { createFetchModuleFunction } from './environments/fetchModule'
3133
import { ServerModuleRunner } from './environments/serverRunner'
3234
import { FilesNotFoundError } from './errors'
3335
import { Logger } from './logger'
@@ -105,6 +107,8 @@ export class Vitest {
105107
/** @internal */ runner!: ModuleRunner
106108
/** @internal */ _testRun: TestRun = undefined!
107109
/** @internal */ _resolver!: VitestResolver
110+
/** @internal */ _fetcher!: VitestFetchFunction
111+
/** @internal */ _tmpDir = join(tmpdir(), nanoid())
108112

109113
private isFirstRun = true
110114
private restartsCount = 0
@@ -212,10 +216,18 @@ export class Vitest {
212216
}
213217

214218
this._resolver = new VitestResolver(server.config.cacheDir, resolved)
219+
this._fetcher = createFetchModuleFunction(
220+
this._resolver,
221+
this._tmpDir,
222+
{
223+
dumpFolder: this.config.dumpDir,
224+
readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null,
225+
},
226+
)
215227
const environment = server.environments.__vitest__
216228
this.runner = new ServerModuleRunner(
217229
environment,
218-
this._resolver,
230+
this._fetcher,
219231
resolved,
220232
)
221233

packages/vitest/src/node/environments/fetchModule.ts

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,26 @@ interface DumpOptions {
1818
readFromDump?: boolean
1919
}
2020

21+
export interface VitestFetchFunction {
22+
(
23+
url: string,
24+
importer: string | undefined,
25+
environment: DevEnvironment,
26+
cacheFs: boolean,
27+
options?: FetchFunctionOptions
28+
): Promise<FetchResult | FetchCachedFileSystemResult>
29+
}
30+
2131
export function createFetchModuleFunction(
2232
resolver: VitestResolver,
23-
cacheFs: boolean = false,
2433
tmpDir: string = join(tmpdir(), nanoid()),
2534
dump?: DumpOptions,
26-
): (
27-
url: string,
28-
importer: string | undefined,
29-
environment: DevEnvironment,
30-
options?: FetchFunctionOptions
31-
) => Promise<FetchResult | FetchCachedFileSystemResult> {
32-
const cachedFsResults = new Map<string, string>()
35+
): VitestFetchFunction {
3336
return async (
3437
url,
3538
importer,
3639
environment,
40+
cacheFs,
3741
options,
3842
) => {
3943
// We are copy pasting Vite's externalization logic from `fetchModule` because
@@ -117,10 +121,14 @@ export function createFetchModuleFunction(
117121
}
118122

119123
const code = result.code
124+
const transformResult = result.transformResult!
125+
if (!transformResult) {
126+
throw new Error(`"transformResult" in not defined. This is a bug in Vitest.`)
127+
}
120128
// to avoid serialising large chunks of code,
121129
// we store them in a tmp file and read in the test thread
122-
if (cachedFsResults.has(result.id)) {
123-
return getCachedResult(result, cachedFsResults)
130+
if ('_vitestTmp' in transformResult) {
131+
return getCachedResult(result, Reflect.get(transformResult as any, '_vitestTmp'))
124132
}
125133
const dir = join(tmpDir, environment.name)
126134
const name = hash('sha1', result.id, 'hex')
@@ -131,8 +139,8 @@ export function createFetchModuleFunction(
131139
}
132140
if (promises.has(tmp)) {
133141
await promises.get(tmp)
134-
cachedFsResults.set(result.id, tmp)
135-
return getCachedResult(result, cachedFsResults)
142+
Reflect.set(transformResult, '_vitestTmp', tmp)
143+
return getCachedResult(result, tmp)
136144
}
137145
promises.set(
138146
tmp,
@@ -143,8 +151,7 @@ export function createFetchModuleFunction(
143151
.finally(() => promises.delete(tmp)),
144152
)
145153
await promises.get(tmp)
146-
cachedFsResults.set(result.id, tmp)
147-
return getCachedResult(result, cachedFsResults)
154+
return getCachedResult(result, tmp)
148155
}
149156
}
150157

@@ -153,7 +160,9 @@ SOURCEMAPPING_URL += 'ppingURL'
153160

154161
const MODULE_RUNNER_SOURCEMAPPING_SOURCE = '//# sourceMappingSource=vite-generated'
155162

156-
function processResultSource(environment: DevEnvironment, result: FetchResult): FetchResult {
163+
function processResultSource(environment: DevEnvironment, result: FetchResult): FetchResult & {
164+
transformResult?: TransformResult | null
165+
} {
157166
if (!('code' in result)) {
158167
return result
159168
}
@@ -168,6 +177,7 @@ function processResultSource(environment: DevEnvironment, result: FetchResult):
168177
return {
169178
...result,
170179
code: node?.transformResult?.code || result.code,
180+
transformResult: node?.transformResult,
171181
}
172182
}
173183

@@ -220,11 +230,7 @@ function genSourceMapUrl(map: Rollup.SourceMap | string): string {
220230
return `data:application/json;base64,${Buffer.from(map).toString('base64')}`
221231
}
222232

223-
function getCachedResult(result: Extract<FetchResult, { code: string }>, cachedFsResults: Map<string, string>): FetchCachedFileSystemResult {
224-
const tmp = cachedFsResults.get(result.id)
225-
if (!tmp) {
226-
throw new Error(`The cached result was returned too early for ${result.id}.`)
227-
}
233+
function getCachedResult(result: Extract<FetchResult, { code: string }>, tmp: string): FetchCachedFileSystemResult {
228234
return {
229235
cached: true as const,
230236
file: result.file,

packages/vitest/src/node/environments/serverRunner.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import type { DevEnvironment } from 'vite'
2-
import type { VitestResolver } from '../resolver'
32
import type { ResolvedConfig } from '../types/config'
3+
import type { VitestFetchFunction } from './fetchModule'
44
import { VitestModuleEvaluator } from '#module-evaluator'
55
import { ModuleRunner } from 'vite/module-runner'
6-
import { createFetchModuleFunction } from './fetchModule'
76
import { normalizeResolvedIdToUrl } from './normalizeUrl'
87

98
export class ServerModuleRunner extends ModuleRunner {
109
constructor(
1110
private environment: DevEnvironment,
12-
resolver: VitestResolver,
11+
fetcher: VitestFetchFunction,
1312
private config: ResolvedConfig,
1413
) {
15-
const fetchModule = createFetchModuleFunction(
16-
resolver,
17-
false,
18-
)
1914
super(
2015
{
2116
hmr: false,
@@ -26,7 +21,7 @@ export class ServerModuleRunner extends ModuleRunner {
2621
}
2722
const { data } = event.data
2823
try {
29-
const result = await fetchModule(data[0], data[1], environment, data[2])
24+
const result = await fetcher(data[0], data[1], environment, false, data[2])
3025
return { result }
3126
}
3227
catch (error) {

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

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ResolveSnapshotPathHandlerContext } from '../types/config'
44
import { existsSync, mkdirSync } from 'node:fs'
55
import { fileURLToPath } from 'node:url'
66
import { cleanUrl } from '@vitest/utils/helpers'
7-
import { createFetchModuleFunction, handleRollupError } from '../environments/fetchModule'
7+
import { handleRollupError } from '../environments/fetchModule'
88
import { normalizeResolvedIdToUrl } from '../environments/normalizeUrl'
99

1010
interface MethodsOptions {
@@ -25,15 +25,6 @@ export function createMethodsRPC(project: TestProject, options: MethodsOptions =
2525
mkdirSync(project.config.dumpDir, { recursive: true })
2626
}
2727
project.vitest.state.metadata[project.name].dumpDir = project.config.dumpDir
28-
const fetch = createFetchModuleFunction(
29-
project._resolver,
30-
cacheFs,
31-
project.tmpDir,
32-
{
33-
dumpFolder: project.config.dumpDir,
34-
readFromDump: project.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null,
35-
},
36-
)
3728
return {
3829
async fetch(
3930
url,
@@ -48,7 +39,7 @@ export function createMethodsRPC(project: TestProject, options: MethodsOptions =
4839

4940
const start = performance.now()
5041

51-
return await fetch(url, importer, environment, options).then((result) => {
42+
return await project._fetcher(url, importer, environment, cacheFs, options).then((result) => {
5243
const duration = performance.now() - start
5344
project.vitest.state.transformTime += duration
5445
const metadata = project.vitest.state.metadata[project.name]

packages/vitest/src/node/project.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ModuleRunner } from 'vite/module-runner'
44
import type { Typechecker } from '../typecheck/typechecker'
55
import type { ProvidedContext } from '../types/general'
66
import type { OnTestsRerunHandler, Vitest } from './core'
7+
import type { VitestFetchFunction } from './environments/fetchModule'
78
import type { GlobalSetupFile } from './globalSetup'
89
import type { ParentProjectBrowser, ProjectBrowser } from './types/browser'
910
import type {
@@ -25,6 +26,7 @@ import { setup } from '../api/setup'
2526
import { createDefinesScript } from '../utils/config-helpers'
2627
import { isBrowserEnabled, resolveConfig } from './config/resolveConfig'
2728
import { serializeConfig } from './config/serializeConfig'
29+
import { createFetchModuleFunction } from './environments/fetchModule'
2830
import { ServerModuleRunner } from './environments/serverRunner'
2931
import { loadGlobalSetupFiles } from './globalSetup'
3032
import { CoverageTransform } from './plugins/coverageTransform'
@@ -55,13 +57,14 @@ export class TestProject {
5557
/**
5658
* Temporary directory for the project. This is unique for each project. Vitest stores transformed content here.
5759
*/
58-
public readonly tmpDir: string = join(tmpdir(), nanoid())
60+
public readonly tmpDir: string
5961

6062
/** @internal */ typechecker?: Typechecker
6163
/** @internal */ _config?: ResolvedConfig
6264
/** @internal */ _vite?: ViteDevServer
6365
/** @internal */ _hash?: string
6466
/** @internal */ _resolver!: VitestResolver
67+
/** @internal */ _fetcher!: VitestFetchFunction
6568
/** @internal */ _serializedDefines?: string
6669
/** @inetrnal */ testFilesList: string[] | null = null
6770

@@ -77,9 +80,11 @@ export class TestProject {
7780
constructor(
7881
vitest: Vitest,
7982
public options?: InitializeProjectOptions | undefined,
83+
tmpDir?: string,
8084
) {
8185
this.vitest = vitest
8286
this.globalConfig = vitest.config
87+
this.tmpDir = tmpDir || join(tmpdir(), nanoid())
8388
}
8489

8590
/**
@@ -545,11 +550,19 @@ export class TestProject {
545550
this._resolver = new VitestResolver(server.config.cacheDir, this._config)
546551
this._vite = server
547552
this._serializedDefines = createDefinesScript(server.config.define)
553+
this._fetcher = createFetchModuleFunction(
554+
this._resolver,
555+
this.tmpDir,
556+
{
557+
dumpFolder: this.config.dumpDir,
558+
readFromDump: this.config.server.debug?.load ?? process.env.VITEST_DEBUG_LOAD_DUMP != null,
559+
},
560+
)
548561

549562
const environment = server.environments.__vitest__
550563
this.runner = new ServerModuleRunner(
551564
environment,
552-
this._resolver,
565+
this._fetcher,
553566
this._config,
554567
)
555568
}
@@ -600,11 +613,14 @@ export class TestProject {
600613
static _createBasicProject(vitest: Vitest): TestProject {
601614
const project = new TestProject(
602615
vitest,
616+
undefined,
617+
vitest._tmpDir,
603618
)
604619
project.runner = vitest.runner
605620
project._vite = vitest.vite
606621
project._config = vitest.config
607622
project._resolver = vitest._resolver
623+
project._fetcher = vitest._fetcher
608624
project._serializedDefines = createDefinesScript(vitest.vite.config.define)
609625
project._setHash()
610626
project._provideObject(vitest.config.provide)
@@ -613,10 +629,11 @@ export class TestProject {
613629

614630
/** @internal */
615631
static _cloneBrowserProject(parent: TestProject, config: ResolvedConfig): TestProject {
616-
const clone = new TestProject(parent.vitest)
632+
const clone = new TestProject(parent.vitest, undefined, parent.tmpDir)
617633
clone.runner = parent.runner
618634
clone._vite = parent._vite
619635
clone._resolver = parent._resolver
636+
clone._fetcher = parent._fetcher
620637
clone._config = config
621638
clone._setHash()
622639
clone._parent = parent

0 commit comments

Comments
 (0)