这是indexloc提供的服务,不要输入任何密码
Skip to content

docs: update Vitest recommendations #10621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 117 additions & 14 deletions docs/site/content/docs/guides/tools/vitest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import { Tab, Tabs } from '#components/tabs';

[Vitest](https://vitest.dev/) is a test runner from the Vite ecosystem. Integrating it with Turborepo will lead to enormous speed-ups.

[The Vitest documentation](https://vitest.dev/guide/workspace) shows how to create a "Vitest Workspace" that runs all tests in the monorepo from one root command, enabling behavior like merged coverage reports out-of-the-box. This feature doesn't follow modern best practices for monorepos, since its designed for compatibility with Jest (whose Workspace feature was built before [package manager Workspaces](/docs/crafting-your-repository/structuring-a-repository)).
[The Vitest documentation](https://vitest.dev/guide/workspace) shows how to create a "Vitest Projects" configuration that runs all tests in the monorepo from one root command, enabling behavior like merged coverage reports out-of-the-box. This feature doesn't follow modern best practices for monorepos, since its designed for compatibility with Jest (whose Workspace feature was built before [package manager Workspaces](/docs/crafting-your-repository/structuring-a-repository)).

<Callout type="warning">
Vitest has deprecated workspaces in favor of projects. When using projects, individual project vitest configs can't extend the root config anymore since they would inherit the projects configuration. Instead, a separate shared file like `vitest.shared.ts` is needed.
</Callout>

Because of this you have two options, each with their own tradeoffs:

- [Leveraging Turborepo for caching](#leveraging-turborepo-for-caching)
- [Using Vitest's Workspace feature](#using-vitests-workspace-feature)
- [Using Vitest's Projects feature](#using-vitests-projects-feature)

### Leveraging Turborepo for caching

Expand Down Expand Up @@ -143,7 +147,7 @@ turbo run test:watch

#### Creating merged coverage reports

[Vitest's Workspace feature](#using-vitests-workspace-feature) creates an out-of-the-box coverage report that merges all of your packages' tests coverage reports. Following the Turborepo strategy, though, you'll have to merge the coverage reports yourself.
[Vitest's Projects feature](#using-vitests-projects-feature) creates an out-of-the-box coverage report that merges all of your packages' tests coverage reports. Following the Turborepo strategy, though, you'll have to merge the coverage reports yourself.

<Callout type="info">
The [`with-vitest`
Expand Down Expand Up @@ -189,13 +193,13 @@ With this in place, run `turbo test && turbo report` to create a merged coverage
started with it quickly using `npx create-turbo@latest --example with-vitest`.
</Callout>

### Using Vitest's Workspace feature
### Using Vitest's Projects feature

The Vitest Workspace feature doesn't follow the same model as a [package manager Workspace](/docs/crafting-your-repository/structuring-a-repository). Instead, it uses a root script that then reaches out into each package in the repository to handle the tests in that respective package.
The Vitest Projects feature doesn't follow the same model as a [package manager Workspace](/docs/crafting-your-repository/structuring-a-repository). Instead, it uses a root script that then reaches out into each package in the repository to handle the tests in that respective package.

In this model, there aren't package boundaries, from a modern JavaScript ecosystem perspective. This means you can't rely on Turborepo's caching, since Turborepo leans on those package boundaries.

Because of this, you'll need to use [Root Tasks](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks) if you want to run the tests using Turborepo. Once you've configured [a Vitest Workspace](https://vitest.dev/guide/workspace), create the Root Tasks for Turborepo:
Because of this, you'll need to use [Root Tasks](/docs/crafting-your-repository/configuring-tasks#registering-root-tasks) if you want to run the tests using Turborepo. Once you've configured [a Vitest Projects setup](https://vitest.dev/guide/workspace), create the Root Tasks for Turborepo:

```json title="./turbo.json"
{
Expand All @@ -215,21 +219,120 @@ Because of this, you'll need to use [Root Tasks](/docs/crafting-your-repository/

### Using a hybrid approach

You can combine the benefits of both approaches by implementing a hybrid solution.This approach unifies local development using Vitest's Workspace approach while preserving Turborepo's caching in CI. This comes at the tradeoff of slightly more configuration and a mixed task running model in the repository.
You can combine the benefits of both approaches by implementing a hybrid solution. This approach unifies local development using Vitest's Projects feature while preserving Turborepo's caching in CI. This comes at the tradeoff of slightly more configuration and a mixed task running model in the repository.

First, create a shared configuration package since individual projects can't extend the root config when using projects. Create a new package for your shared Vitest configuration:

```json title="./packages/vitest-config/package.json"
{
"name": "@repo/vitest-config",
"version": "0.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"dependencies": {
"vitest": "latest"
},
"devDependencies": {
"@repo/typescript-config": "workspace:*",
"typescript": "latest"
}
}
```

```json title="./packages/vitest-config/tsconfig.json"
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}
```

```ts title="./vitest.workspace.ts"
import { defineWorkspace } from 'vitest/config';
```ts title="./packages/vitest-config/src/index.ts"
export const sharedConfig = {
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
// Other shared configuration
}
};
```

export default defineWorkspace(['packages/*']);
Then, create your root Vitest configuration using projects:

```ts title="./vitest.config.ts"
import { defineConfig } from 'vitest/config';
import { sharedConfig } from '@repo/vitest-config';

export default defineConfig({
...sharedConfig,
projects: [
{
name: 'packages',
root: './packages/*',
test: {
...sharedConfig.test,
// Project-specific configuration
}
}
]
});
```

In this setup, your packages maintain their individual Vitest configurations and scripts:
In this setup, your packages maintain their individual Vitest configurations that import the shared config. First, install the shared config package:

```json title="./packages/ui/package.json"
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest --watch"
},
"devDependencies": {
"@repo/vitest-config": "workspace:*",
"vitest": "latest"
}
}
```

Then create the Vitest configuration:

```ts title="./packages/ui/vitest.config.ts"
import { defineConfig } from 'vitest/config';
import { sharedConfig } from '@repo/vitest-config';

export default defineConfig({
...sharedConfig,
test: {
...sharedConfig.test,
// Package-specific overrides if needed
}
});
```

Make sure to update your `turbo.json` to include the new configuration package in the dependency graph:

```json title="./turbo.json"
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["^test", "@repo/vitest-config#build"]
},
"test:watch": {
"cache": false,
"persistent": true
}
}
}
```
Expand All @@ -239,10 +342,10 @@ While your root `package.json` includes scripts for running tests globally:
```json title="./package.json"
{
"scripts": {
"test:workspace": "turbo run test",
"test:workspace:watch": "vitest --watch"
"test:projects": "turbo run test",
"test:projects:watch": "vitest --watch"
}
}
```

This configuration allows developers to run `pnpm test:workspace:watch` at the root for a seamless local development experience, while CI continues to use `turbo run test` to leverage package-level caching. **You'll still need to handle merged coverage reports manually as described in the previous section**.
This configuration allows developers to run `pnpm test:projects:watch` at the root for a seamless local development experience using Vitest projects, while CI continues to use `turbo run test` to leverage package-level caching. **You'll still need to handle merged coverage reports manually as described in the previous section**.
31 changes: 28 additions & 3 deletions examples/with-vitest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,35 @@ This Turborepo starter is maintained by the Turborepo core team.

This example is based on the `basic` example (`npx create-turbo@latest`) to demonstrate how to use Vitest and get the most out of Turborepo's caching.

For this reason, the only commands in the root package.json are `turbo run test` and `turbo run view-report`.
This example demonstrates two approaches to Vitest configuration:

`turbo run test`: Runs the test in each package using Turborepo.
`turbo run view-report`: Collects coverage from each package and shows it in a merged report.
1. **Package-level caching (Recommended)**: Each package has its own Vitest configuration that imports shared settings from `@repo/vitest-config`. This approach leverages Turborepo's caching effectively.

2. **Vitest Projects**: A root `vitest.config.ts` uses Vitest's projects feature for unified test running during development.

## Getting Started

First, install dependencies and build the shared configuration package:

```bash
pnpm install
pnpm build --filter=@repo/vitest-config
```

## Available Commands

- `pnpm test`: Runs tests in each package using Turborepo (leverages caching)
- `pnpm test:projects`: Same as above, explicitly named for the package-level approach
- `pnpm test:projects:watch`: Runs tests using Vitest's projects feature in watch mode
- `pnpm view-report`: Collects coverage from each package and shows it in a merged report

## Configuration Structure

The example uses a shared `@repo/vitest-config` package that exports:

- `sharedConfig`: Base configuration with coverage settings
- `baseConfig`: For Node.js packages (like `math`)
- `uiConfig`: For packages requiring jsdom environment (like `web`, `docs`)

### Remote Caching

Expand Down
6 changes: 5 additions & 1 deletion examples/with-vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
"private": true,
"scripts": {
"test": "turbo run test",
"test:projects": "turbo run test",
"test:projects:watch": "vitest --watch",
"view-report": "turbo run view-report"
},
"devDependencies": {
"@repo/vitest-config": "workspace:*",
"prettier": "^3.5.0",
"turbo": "^2.5.0",
"typescript": "5.7.3"
"typescript": "5.7.3",
"vitest": "^3.0.7"
},
"packageManager": "pnpm@9.0.0",
"engines": {
Expand Down
11 changes: 9 additions & 2 deletions examples/with-vitest/packages/math/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { baseConfig } from "@repo/vitest-config/base";
import { defineConfig } from 'vitest/config';
import { sharedConfig } from '@repo/vitest-config';

export default baseConfig;
export default defineConfig({
...sharedConfig,
test: {
...sharedConfig.test,
// Package-specific overrides if needed
}
});
9 changes: 8 additions & 1 deletion examples/with-vitest/packages/vitest-config/package.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
{
"name": "@repo/vitest-config",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./base": "./dist/configs/base-config.js",
"./ui": "./dist/configs/ui-config.js"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"collect-json-reports": "node dist/scripts/collect-json-outputs.js",
"merge-json-reports": "nyc merge coverage/raw coverage/merged/merged-coverage.json",
"report": "nyc report -t coverage/merged --report-dir coverage/report --reporter=html --exclude-after-remap false",
"view-report": "open coverage/report/index.html"
},
"dependencies": {
"vitest": "^3.0.7"
},
"devDependencies": {
"@repo/typescript-config": "workspace:*",
"@vitest/coverage-istanbul": "^3.0.7",
"@vitest/ui": "3.0.7",
"glob": "^11.0.1",
"jsdom": "^26.0.0",
"nyc": "^17.1.0",
"vitest": "^3.0.7"
"typescript": "latest"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { defineConfig } from "vitest/config";
import path from "node:path";

export const baseConfig = defineConfig({
test: {
Expand All @@ -16,4 +15,4 @@ export const baseConfig = defineConfig({
enabled: true,
},
},
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export const uiConfig = mergeConfig(
environment: "jsdom",
},
})
);
);
21 changes: 21 additions & 0 deletions examples/with-vitest/packages/vitest-config/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const sharedConfig = {
test: {
globals: true,
coverage: {
provider: "istanbul" as const,
reporter: [
[
"json",
{
file: `../coverage.json`,
},
],
] as const,
enabled: true,
},
},
};

// Re-export specific configs for backwards compatibility
export { baseConfig } from './configs/base-config.js';
export { uiConfig } from './configs/ui-config.js';
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ async function collectCoverageFiles() {
}

// Run the function
collectCoverageFiles();
collectCoverageFiles();
9 changes: 5 additions & 4 deletions examples/with-vitest/packages/vitest-config/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"extends": ["@repo/typescript-config/base.json"],
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist"
"outDir": "dist",
"rootDir": "src"
},
"include": ["configs", "scripts"],
"exclude": ["node_modules", "dist"]
"include": ["src"],
"exclude": ["dist", "node_modules"]
}
25 changes: 25 additions & 0 deletions examples/with-vitest/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from 'vitest/config';
import { sharedConfig } from '@repo/vitest-config';

export default defineConfig({
...sharedConfig,
projects: [
{
name: 'packages',
root: './packages/*',
test: {
...sharedConfig.test,
// Project-specific configuration for packages
}
},
{
name: 'apps',
root: './apps/*',
test: {
...sharedConfig.test,
// Project-specific configuration for apps
environment: 'jsdom',
}
}
]
});
Loading