Skip to content

Commit 6c85c5a

Browse files
docs: update Vitest recommendations (#10621)
### Description Updates the Vitest documentation to address GitHub Issue #10548. This PR: * Replaces deprecated Vitest "workspaces" with "projects" terminology and configuration. * Introduces a new `@repo/vitest-config` package for shared Vitest configuration, eliminating relative path traversals and promoting monorepo best practices. * Updates example code snippets and `turbo.json` to reflect the new package-based approach for improved cacheability and maintainability. ### Testing Instructions Review the updated `docs/site/content/docs/guides/tools/vitest.mdx` file. * Verify that "workspaces" references are correctly replaced with "projects". * Ensure the new shared configuration package (`@repo/vitest-config`) is clearly explained and demonstrated. * Check the code snippets for accuracy and adherence to the recommended monorepo structure. --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent 6f9c0c8 commit 6c85c5a

File tree

11 files changed

+221
-29
lines changed

11 files changed

+221
-29
lines changed

docs/site/content/docs/guides/tools/vitest.mdx

Lines changed: 117 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ import { Tab, Tabs } from '#components/tabs';
1010

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

13-
[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)).
13+
[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)).
14+
15+
<Callout type="warning">
16+
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.
17+
</Callout>
1418

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

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

2024
### Leveraging Turborepo for caching
2125

@@ -143,7 +147,7 @@ turbo run test:watch
143147

144148
#### Creating merged coverage reports
145149

146-
[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.
150+
[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.
147151

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

192-
### Using Vitest's Workspace feature
196+
### Using Vitest's Projects feature
193197

194-
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.
198+
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.
195199

196200
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.
197201

198-
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:
202+
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:
199203

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

216220
### Using a hybrid approach
217221

218-
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.
222+
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.
223+
224+
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:
225+
226+
```json title="./packages/vitest-config/package.json"
227+
{
228+
"name": "@repo/vitest-config",
229+
"version": "0.0.0",
230+
"main": "dist/index.js",
231+
"types": "dist/index.d.ts",
232+
"scripts": {
233+
"build": "tsc",
234+
"dev": "tsc --watch"
235+
},
236+
"dependencies": {
237+
"vitest": "latest"
238+
},
239+
"devDependencies": {
240+
"@repo/typescript-config": "workspace:*",
241+
"typescript": "latest"
242+
}
243+
}
244+
```
245+
246+
```json title="./packages/vitest-config/tsconfig.json"
247+
{
248+
"extends": "@repo/typescript-config/base.json",
249+
"compilerOptions": {
250+
"outDir": "dist",
251+
"rootDir": "src"
252+
},
253+
"include": ["src"],
254+
"exclude": ["dist", "node_modules"]
255+
}
256+
```
219257

220-
```ts title="./vitest.workspace.ts"
221-
import { defineWorkspace } from 'vitest/config';
258+
```ts title="./packages/vitest-config/src/index.ts"
259+
export const sharedConfig = {
260+
test: {
261+
globals: true,
262+
environment: 'jsdom',
263+
setupFiles: ['./src/test/setup.ts'],
264+
// Other shared configuration
265+
}
266+
};
267+
```
222268

223-
export default defineWorkspace(['packages/*']);
269+
Then, create your root Vitest configuration using projects:
270+
271+
```ts title="./vitest.config.ts"
272+
import { defineConfig } from 'vitest/config';
273+
import { sharedConfig } from '@repo/vitest-config';
274+
275+
export default defineConfig({
276+
...sharedConfig,
277+
projects: [
278+
{
279+
name: 'packages',
280+
root: './packages/*',
281+
test: {
282+
...sharedConfig.test,
283+
// Project-specific configuration
284+
}
285+
}
286+
]
287+
});
224288
```
225289

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

228292
```json title="./packages/ui/package.json"
229293
{
230294
"scripts": {
231295
"test": "vitest run",
232296
"test:watch": "vitest --watch"
297+
},
298+
"devDependencies": {
299+
"@repo/vitest-config": "workspace:*",
300+
"vitest": "latest"
301+
}
302+
}
303+
```
304+
305+
Then create the Vitest configuration:
306+
307+
```ts title="./packages/ui/vitest.config.ts"
308+
import { defineConfig } from 'vitest/config';
309+
import { sharedConfig } from '@repo/vitest-config';
310+
311+
export default defineConfig({
312+
...sharedConfig,
313+
test: {
314+
...sharedConfig.test,
315+
// Package-specific overrides if needed
316+
}
317+
});
318+
```
319+
320+
Make sure to update your `turbo.json` to include the new configuration package in the dependency graph:
321+
322+
```json title="./turbo.json"
323+
{
324+
"tasks": {
325+
"build": {
326+
"dependsOn": ["^build"],
327+
"outputs": ["dist/**"]
328+
},
329+
"test": {
330+
"dependsOn": ["^test", "@repo/vitest-config#build"]
331+
},
332+
"test:watch": {
333+
"cache": false,
334+
"persistent": true
335+
}
233336
}
234337
}
235338
```
@@ -239,10 +342,10 @@ While your root `package.json` includes scripts for running tests globally:
239342
```json title="./package.json"
240343
{
241344
"scripts": {
242-
"test:workspace": "turbo run test",
243-
"test:workspace:watch": "vitest --watch"
345+
"test:projects": "turbo run test",
346+
"test:projects:watch": "vitest --watch"
244347
}
245348
}
246349
```
247350

248-
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**.
351+
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**.

examples/with-vitest/README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,35 @@ This Turborepo starter is maintained by the Turborepo core team.
66

77
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.
88

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

11-
`turbo run test`: Runs the test in each package using Turborepo.
12-
`turbo run view-report`: Collects coverage from each package and shows it in a merged report.
11+
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.
12+
13+
2. **Vitest Projects**: A root `vitest.config.ts` uses Vitest's projects feature for unified test running during development.
14+
15+
## Getting Started
16+
17+
First, install dependencies and build the shared configuration package:
18+
19+
```bash
20+
pnpm install
21+
pnpm build --filter=@repo/vitest-config
22+
```
23+
24+
## Available Commands
25+
26+
- `pnpm test`: Runs tests in each package using Turborepo (leverages caching)
27+
- `pnpm test:projects`: Same as above, explicitly named for the package-level approach
28+
- `pnpm test:projects:watch`: Runs tests using Vitest's projects feature in watch mode
29+
- `pnpm view-report`: Collects coverage from each package and shows it in a merged report
30+
31+
## Configuration Structure
32+
33+
The example uses a shared `@repo/vitest-config` package that exports:
34+
35+
- `sharedConfig`: Base configuration with coverage settings
36+
- `baseConfig`: For Node.js packages (like `math`)
37+
- `uiConfig`: For packages requiring jsdom environment (like `web`, `docs`)
1338

1439
### Remote Caching
1540

examples/with-vitest/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
"private": true,
44
"scripts": {
55
"test": "turbo run test",
6+
"test:projects": "turbo run test",
7+
"test:projects:watch": "vitest --watch",
68
"view-report": "turbo run view-report"
79
},
810
"devDependencies": {
11+
"@repo/vitest-config": "workspace:*",
912
"prettier": "^3.5.0",
1013
"turbo": "^2.5.0",
11-
"typescript": "5.7.3"
14+
"typescript": "5.7.3",
15+
"vitest": "^3.0.7"
1216
},
1317
"packageManager": "[email protected]",
1418
"engines": {
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1-
import { baseConfig } from "@repo/vitest-config/base";
1+
import { defineConfig } from 'vitest/config';
2+
import { sharedConfig } from '@repo/vitest-config';
23

3-
export default baseConfig;
4+
export default defineConfig({
5+
...sharedConfig,
6+
test: {
7+
...sharedConfig.test,
8+
// Package-specific overrides if needed
9+
}
10+
});
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
{
22
"name": "@repo/vitest-config",
33
"type": "module",
4+
"main": "dist/index.js",
5+
"types": "dist/index.d.ts",
46
"exports": {
7+
".": "./dist/index.js",
58
"./base": "./dist/configs/base-config.js",
69
"./ui": "./dist/configs/ui-config.js"
710
},
811
"scripts": {
912
"build": "tsc",
13+
"dev": "tsc --watch",
1014
"collect-json-reports": "node dist/scripts/collect-json-outputs.js",
1115
"merge-json-reports": "nyc merge coverage/raw coverage/merged/merged-coverage.json",
1216
"report": "nyc report -t coverage/merged --report-dir coverage/report --reporter=html --exclude-after-remap false",
1317
"view-report": "open coverage/report/index.html"
1418
},
19+
"dependencies": {
20+
"vitest": "^3.0.7"
21+
},
1522
"devDependencies": {
1623
"@repo/typescript-config": "workspace:*",
1724
"@vitest/coverage-istanbul": "^3.0.7",
1825
"@vitest/ui": "3.0.7",
1926
"glob": "^11.0.1",
2027
"jsdom": "^26.0.0",
2128
"nyc": "^17.1.0",
22-
"vitest": "^3.0.7"
29+
"typescript": "latest"
2330
}
2431
}

examples/with-vitest/packages/vitest-config/configs/base-config.ts renamed to examples/with-vitest/packages/vitest-config/src/configs/base-config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { defineConfig } from "vitest/config";
2-
import path from "node:path";
32

43
export const baseConfig = defineConfig({
54
test: {
@@ -16,4 +15,4 @@ export const baseConfig = defineConfig({
1615
enabled: true,
1716
},
1817
},
19-
});
18+
});

examples/with-vitest/packages/vitest-config/configs/ui-config.ts renamed to examples/with-vitest/packages/vitest-config/src/configs/ui-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ export const uiConfig = mergeConfig(
88
environment: "jsdom",
99
},
1010
})
11-
);
11+
);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const sharedConfig = {
2+
test: {
3+
globals: true,
4+
coverage: {
5+
provider: "istanbul" as const,
6+
reporter: [
7+
[
8+
"json",
9+
{
10+
file: `../coverage.json`,
11+
},
12+
],
13+
] as const,
14+
enabled: true,
15+
},
16+
},
17+
};
18+
19+
// Re-export specific configs for backwards compatibility
20+
export { baseConfig } from './configs/base-config.js';
21+
export { uiConfig } from './configs/ui-config.js';

examples/with-vitest/packages/vitest-config/scripts/collect-json-outputs.ts renamed to examples/with-vitest/packages/vitest-config/src/scripts/collect-json-outputs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,4 @@ async function collectCoverageFiles() {
7070
}
7171

7272
// Run the function
73-
collectCoverageFiles();
73+
collectCoverageFiles();
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
2-
"extends": ["@repo/typescript-config/base.json"],
2+
"extends": "@repo/typescript-config/base.json",
33
"compilerOptions": {
4-
"outDir": "dist"
4+
"outDir": "dist",
5+
"rootDir": "src"
56
},
6-
"include": ["configs", "scripts"],
7-
"exclude": ["node_modules", "dist"]
7+
"include": ["src"],
8+
"exclude": ["dist", "node_modules"]
89
}

0 commit comments

Comments
 (0)