Benchmarking (v5)
In v5 the benchmark API was rewritten: bench is no longer a top-level import. It is a test-context fixture used inside a regular test(), available only in files matched by benchmark.include (default **/*.{bench,benchmark}.?(c|m)[jt]s?(x)). Benchmarks are powered by Tinybench.
Defining & Running
import { expect, test } from 'vitest'
test('parse performance', async ({ bench }) => {
// bench() registers; .run() executes and returns the result
const result = await bench('parse', () => {
const data = JSON.parse('{"key":"value"}')
use(data) // consume the result — engines may eliminate dead code
}).run()
expect(result.throughput.mean).toBeGreaterThan(10_000)
})Run benchmarks:
vitest bench # only benchmarks (implicitly enables them)
vitest bench parser # filter by filename
vitest bench -t JSON # filter by test nameSet benchmark: { enabled: true } to run them alongside regular tests in a separate isolated group.
Comparing Implementations
test('compare parsers', async ({ bench }) => {
const result = await bench.compare(
bench('JSON.parse', () => { JSON.parse(input) }),
bench('custom', { beforeEach: () => reset() }, () => { customParse(input) }),
{ iterations: 100, time: 1000 }, // shared Tinybench options (last arg)
)
// Assertion matchers (delta avoids flaky failures)
expect(result.get('JSON.parse')).toBeFasterThan(result.get('custom'), { delta: 0.1 })
expect(result.get('custom')).toBeSlowerThan(result.get('JSON.parse'))
})bench.compare interleaves iterations to reduce environmental bias and prints a comparison table after the test.
Storing & Replaying Baselines
test('compare against baseline', async ({ bench }) => {
await bench.compare(
bench('current', { writeResult: './benchmarks/parse.json' }, () => parse(input)),
bench.from('previous', './benchmarks/parse.json'), // reads a stored result, no run
bench.from('remote', () => fetch(url).then(r => r.json())),
)
})writeResultoverwrites the JSON file on every successful run (no skip-when-cached).bench.from(name, source)reads a stored result without invoking any function.- For multi-project workspaces, pass
{ perProject: true }and use${projectName}inwriteResultpaths to collect a cross-project comparison table.
Stability Notes
- Benchmark files run sequentially and never in parallel;
retryand thedeltaoption reduce flakiness. - Consume the result inside the bench fn — JS engines eliminate side-effect-free code.
- In Node mode every imported binding goes through Vite's module-runner getter; store hot references locally (
const _parse = parse), benchmark the built package, or disableexperimental.viteModuleRunnerfor the bench project.
v5 Migration
benchtop-level import →({ bench })from the test contextbench.skip/only/todoremoved → usetest.skip/only/todoon the surrounding testbenchmark.reporters/outputFile/compare/outputJsonand--compare/--outputJsonremoved → use--reporter=json --outputFile(JSON now has abenchmarksfield)
Key Points
- Benchmarks live in
*.bench.tsfiles and run insidetest()via{ bench } - Use
bench.compare+toBeFasterThan/toBeSlowerThan(withdelta) for relative perf - Persist baselines with
writeResultand replay withbench.from
<!-- Source references:
- https://vitest.dev/guide/benchmarking
- https://vitest.dev/guide/test-context#bench
-->