From 03faf6db6aba5b4d5e08d08606fdcc96de3be6d0 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 25 May 2026 18:00:32 +0200 Subject: [PATCH] perf: improve performance in hot paths (#10446) --- packages/browser/src/node/rpc.ts | 12 +++++----- packages/runner/src/utils/collect.ts | 10 ++++++--- packages/vitest/src/node/ast-collect.ts | 12 +++++++++- packages/vitest/src/node/pools/pool.ts | 2 +- packages/vitest/src/node/reporters/base.ts | 26 ++++++++++++++-------- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/packages/browser/src/node/rpc.ts b/packages/browser/src/node/rpc.ts index 3566a7848..80af962ed 100644 --- a/packages/browser/src/node/rpc.ts +++ b/packages/browser/src/node/rpc.ts @@ -409,13 +409,11 @@ export function setupBrowserRpc(globalServer: ParentBrowserProject, defaultMocke function cloneByOwnProperties(value: any) { // Clones the value's properties into a new Object. The simpler approach of // Object.assign() won't work in the case that properties are not enumerable. - return Object.getOwnPropertyNames(value).reduce( - (clone, prop) => ({ - ...clone, - [prop]: value[prop], - }), - {}, - ) + const clone: Record = {} + for (const prop of Object.getOwnPropertyNames(value)) { + clone[prop] = value[prop] + } + return clone } /** diff --git a/packages/runner/src/utils/collect.ts b/packages/runner/src/utils/collect.ts index 0a7274887..961503a3d 100644 --- a/packages/runner/src/utils/collect.ts +++ b/packages/runner/src/utils/collect.ts @@ -18,6 +18,10 @@ export function interpretTaskModes( allowOnly?: boolean, ): void { const matchedLocations: number[] = [] + const testLocationsSet = testLocations !== undefined && testLocations.length !== 0 + ? new Set(testLocations) + : undefined + const testIdsSet = testIds ? new Set(testIds) : undefined const traverseSuite = (suite: Suite, parentIsOnly?: boolean, parentMatchedWithLocation?: boolean) => { const suiteIsOnly = parentIsOnly || suite.mode === 'only' @@ -54,8 +58,8 @@ export function interpretTaskModes( // Match test location against provided locations, only run if present // in `testLocations`. Note: if `includeTaskLocation` is not enabled, // all test will be skipped. - if (testLocations !== undefined && testLocations.length !== 0) { - if (t.location && testLocations?.includes(t.location.line)) { + if (testLocationsSet !== undefined) { + if (t.location && testLocationsSet.has(t.location.line)) { t.mode = 'run' matchedLocations.push(t.location.line) hasLocationMatch = true @@ -72,7 +76,7 @@ export function interpretTaskModes( if (namePattern && !getTaskFullName(t).match(namePattern)) { t.mode = 'skip' } - if (testIds && !testIds.includes(t.id)) { + if (testIdsSet && !testIdsSet.has(t.id)) { t.mode = 'skip' } if (testTagsFilter && !testTagsFilter(t.tags || [])) { diff --git a/packages/vitest/src/node/ast-collect.ts b/packages/vitest/src/node/ast-collect.ts index 724d27fae..78488ccc5 100644 --- a/packages/vitest/src/node/ast-collect.ts +++ b/packages/vitest/src/node/ast-collect.ts @@ -49,6 +49,16 @@ interface LocalCallDefinition { const debug = createDebugger('vitest:ast-collect-info') const verbose = createDebugger('vitest:ast-collect-verbose') +const INTERMEDIATE_CALL_PROPERTIES = new Set([ + 'each', + 'for', + 'skipIf', + 'runIf', + 'extend', + 'scoped', + 'override', +]) + function isTestFunctionName(name: string) { return name === 'it' || name === 'test' || name.startsWith('test') || name.endsWith('Test') } @@ -153,7 +163,7 @@ function astParseFile(filepath: string, code: string) { const properties = getProperties(callee) const property = callee?.property?.name // intermediate calls like .each(), .for() will be picked up in the next iteration - if (property && ['each', 'for', 'skipIf', 'runIf', 'extend', 'scoped', 'override'].includes(property)) { + if (property && INTERMEDIATE_CALL_PROPERTIES.has(property)) { return } // skip properties on return values of calls - e.g., test('name', fn).skip() diff --git a/packages/vitest/src/node/pools/pool.ts b/packages/vitest/src/node/pools/pool.ts index eeccdc8aa..bb6cab29c 100644 --- a/packages/vitest/src/node/pools/pool.ts +++ b/packages/vitest/src/node/pools/pool.ts @@ -346,7 +346,7 @@ function deepEqual(obj1: any, obj2: any): boolean { } for (const key of keys1) { - if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { + if (!Object.prototype.hasOwnProperty.call(obj2, key) || !deepEqual(obj1[key], obj2[key])) { return false } } diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index b1543f1f0..b5442314c 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -679,10 +679,21 @@ export abstract class BaseReporter implements Reporter { return } - const dangerImports = allImports.filter(imp => imp.totalTime >= thresholds.danger) - const warnImports = allImports.filter(imp => imp.totalTime >= thresholds.warn) - const hasDangerImports = dangerImports.length > 0 - const hasWarnImports = warnImports.length > 0 + let dangerImportsCount = 0 + let hasWarnImports = false + let totalSelfTime = 0 + let totalTotalTime = 0 + for (const imp of allImports) { + if (imp.totalTime >= thresholds.danger) { + dangerImportsCount++ + } + if (imp.totalTime >= thresholds.warn) { + hasWarnImports = true + } + totalSelfTime += imp.selfTime + totalTotalTime += imp.totalTime + } + const hasDangerImports = dangerImportsCount > 0 // Determine if we should print const shouldFail = failOnDanger && hasDangerImports @@ -695,9 +706,6 @@ export abstract class BaseReporter implements Reporter { const maxTotalTime = sortedImports[0].totalTime const limit = this.ctx.config.experimental.importDurations.limit const topImports = sortedImports.slice(0, limit) - - const totalSelfTime = allImports.reduce((sum, imp) => sum + imp.selfTime, 0) - const totalTotalTime = allImports.reduce((sum, imp) => sum + imp.totalTime, 0) const slowestImport = sortedImports[0] this.log() @@ -750,7 +758,7 @@ export abstract class BaseReporter implements Reporter { if (shouldFail) { this.log() this.ctx.logger.error( - `ERROR: ${dangerImports.length} import(s) exceeded the danger threshold of ${thresholds.danger}ms`, + `ERROR: ${dangerImportsCount} import(s) exceeded the danger threshold of ${thresholds.danger}ms`, ) process.exitCode = 1 } @@ -993,7 +1001,7 @@ function deepEqual(a: any, b: any): boolean { } for (const key of keysA) { - if (!keysB.includes(key) || !deepEqual(a[key], b[key])) { + if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) { return false } } -- 2.51.2