Why the top-level bench import stops working
Vitest’s own migration guide states this plainly under “Benchmarking API Rewrite”:
benchis no longer a top-level import fromvitest; it is a test-context fixture accessed from inside a regulartest().
That’s a structural change, not a rename. The old form:
import { bench } from "vitest";
bench("sort", () => {
[3, 1, 2].sort();
});no longer resolves to a working benchmark call in Vitest 5, because vitest stops exporting a bench symbol at the module level at all. Exactly how that surfaces depends on your bundler and TypeScript setup: some configs throw at import time, others fail type-checking first, and a few just silently run nothing because bench resolves to undefined. Vitest’s guide doesn’t publish one canonical error string for this case, so treat the exact wording you see as environment-specific rather than a fixed message to search for. The mechanical cause is the same either way: bench isn’t there to import anymore.
The guide also confirms bench.skip, bench.only, and bench.todo are removed in the same rewrite. Any suite using those modifiers needs the same migration as a plain bench call.
What happens to benchmark.reporters, outputFile, compare, and outputJson
This is the part of the change that breaks a CI pipeline quietly instead of loudly. A benchmark suite that imports bench correctly can still fail to produce the output a workflow expects, because four benchmark.* config options are removed entirely in the same rewrite, not renamed:
benchmark.reportersandbenchmark.outputFileare gone. The migration guide says that output now ships through the standard reporter pipeline instead of a dedicated benchmark reporter, so there’s no separate config to point at a custom output stream anymore.benchmark.compareand the matching--compareCLI flag are removed, with no direct 1:1 replacement documented.benchmark.outputJsonand the--outputJsonCLI flag are removed. The guide’s replacement is--reporter=json --outputFile=<path>, run through Vitest’s general reporter system instead of a benchmark-specific option.
None of these throw an import error the way the bench removal does. A vitest.config.ts that still sets benchmark.compare just has that setting silently ignored, so the first sign of trouble is usually a CI step that expects a comparison artifact or a JSON file and doesn’t get one.
Fix it: move bench into the test context fixture
Rewrite each benchmark to destructure bench from the test() callback’s context object, call it, then call .run() on the result:
import { bench } from "vitest"; // BROKEN: no longer exported at module scope
bench("sort", () => {
[3, 1, 2].sort();
});import { test } from "vitest";
test("sort", async ({ bench }) => {
// FIXED: bench is a fixture on the test context now, and .run() is required
await bench("sort", () => {
[3, 1, 2].sort();
}).run();
});The .run() call is not optional decoration. Without it, bench(...) just constructs the benchmark and returns without ever executing it, so a half-migrated call site can look correct while producing no timing data at all.
Update CI scripts alongside the code
If a workflow file passes --compare or --outputJson to the vitest bench
command, update it in the same change as the source migration. A CI script
that still passes a removed flag either errors immediately or, depending on
how strict the CLI parsing is, gets silently ignored, and either way the
comparison or JSON artifact a later step depends on won’t exist.
Confirmed version range
This is documented in Vitest’s own current migration guide, under “Benchmarking API Rewrite,” as an intentional Vitest 5 change, corroborated as in-window against the beta release available at research time (v5.0.0-beta.7). This repo’s own vitest.config.ts is pinned to vitest@^3.0.0 and doesn’t use the benchmarking API, so this specific removal wasn’t independently reproducible against this repo’s own build. Treat the mechanics above as documented behavior from Vitest’s primary source, not a first-party reproduction. Browse more posts like this in the Guides & Fixes archive.







