diff --git a/docs/site/content/docs/guides/tools/vitest.mdx b/docs/site/content/docs/guides/tools/vitest.mdx
index 43eeb32d9729f..927e914bdb784 100644
--- a/docs/site/content/docs/guides/tools/vitest.mdx
+++ b/docs/site/content/docs/guides/tools/vitest.mdx
@@ -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)).
+
+
+ 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.
+
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
@@ -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.
The [`with-vitest`
@@ -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`.
-### 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"
{
@@ -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
+ }
}
}
```
@@ -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**.
diff --git a/examples/with-vitest/README.md b/examples/with-vitest/README.md
index 7d3c4bea05476..b8be0eb4c8fe3 100644
--- a/examples/with-vitest/README.md
+++ b/examples/with-vitest/README.md
@@ -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
diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json
index 8331d47434f0e..0354441262265 100644
--- a/examples/with-vitest/package.json
+++ b/examples/with-vitest/package.json
@@ -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": {
diff --git a/examples/with-vitest/packages/math/vitest.config.ts b/examples/with-vitest/packages/math/vitest.config.ts
index 879033a74554c..979f41c3a09a6 100644
--- a/examples/with-vitest/packages/math/vitest.config.ts
+++ b/examples/with-vitest/packages/math/vitest.config.ts
@@ -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
+ }
+});
diff --git a/examples/with-vitest/packages/vitest-config/package.json b/examples/with-vitest/packages/vitest-config/package.json
index 108c8e24efb87..6f4d3e0ebea14 100644
--- a/examples/with-vitest/packages/vitest-config/package.json
+++ b/examples/with-vitest/packages/vitest-config/package.json
@@ -1,17 +1,24 @@
{
"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",
@@ -19,6 +26,6 @@
"glob": "^11.0.1",
"jsdom": "^26.0.0",
"nyc": "^17.1.0",
- "vitest": "^3.0.7"
+ "typescript": "latest"
}
}
diff --git a/examples/with-vitest/packages/vitest-config/configs/base-config.ts b/examples/with-vitest/packages/vitest-config/src/configs/base-config.ts
similarity index 89%
rename from examples/with-vitest/packages/vitest-config/configs/base-config.ts
rename to examples/with-vitest/packages/vitest-config/src/configs/base-config.ts
index af0de7ac6a5dc..918cb41a36a6c 100644
--- a/examples/with-vitest/packages/vitest-config/configs/base-config.ts
+++ b/examples/with-vitest/packages/vitest-config/src/configs/base-config.ts
@@ -1,5 +1,4 @@
import { defineConfig } from "vitest/config";
-import path from "node:path";
export const baseConfig = defineConfig({
test: {
@@ -16,4 +15,4 @@ export const baseConfig = defineConfig({
enabled: true,
},
},
-});
+});
\ No newline at end of file
diff --git a/examples/with-vitest/packages/vitest-config/configs/ui-config.ts b/examples/with-vitest/packages/vitest-config/src/configs/ui-config.ts
similarity index 98%
rename from examples/with-vitest/packages/vitest-config/configs/ui-config.ts
rename to examples/with-vitest/packages/vitest-config/src/configs/ui-config.ts
index b3afdf89916b2..b1b8fbe8805ba 100644
--- a/examples/with-vitest/packages/vitest-config/configs/ui-config.ts
+++ b/examples/with-vitest/packages/vitest-config/src/configs/ui-config.ts
@@ -8,4 +8,4 @@ export const uiConfig = mergeConfig(
environment: "jsdom",
},
})
-);
+);
\ No newline at end of file
diff --git a/examples/with-vitest/packages/vitest-config/src/index.ts b/examples/with-vitest/packages/vitest-config/src/index.ts
new file mode 100644
index 0000000000000..4b25ef2def89b
--- /dev/null
+++ b/examples/with-vitest/packages/vitest-config/src/index.ts
@@ -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';
\ No newline at end of file
diff --git a/examples/with-vitest/packages/vitest-config/scripts/collect-json-outputs.ts b/examples/with-vitest/packages/vitest-config/src/scripts/collect-json-outputs.ts
similarity index 98%
rename from examples/with-vitest/packages/vitest-config/scripts/collect-json-outputs.ts
rename to examples/with-vitest/packages/vitest-config/src/scripts/collect-json-outputs.ts
index 40f1f6db1daac..3d136ea3cd945 100644
--- a/examples/with-vitest/packages/vitest-config/scripts/collect-json-outputs.ts
+++ b/examples/with-vitest/packages/vitest-config/src/scripts/collect-json-outputs.ts
@@ -70,4 +70,4 @@ async function collectCoverageFiles() {
}
// Run the function
-collectCoverageFiles();
+collectCoverageFiles();
\ No newline at end of file
diff --git a/examples/with-vitest/packages/vitest-config/tsconfig.json b/examples/with-vitest/packages/vitest-config/tsconfig.json
index c4b88cba4ba18..35ad0d9dd8785 100644
--- a/examples/with-vitest/packages/vitest-config/tsconfig.json
+++ b/examples/with-vitest/packages/vitest-config/tsconfig.json
@@ -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"]
}
diff --git a/examples/with-vitest/vitest.config.ts b/examples/with-vitest/vitest.config.ts
new file mode 100644
index 0000000000000..0027165150cb7
--- /dev/null
+++ b/examples/with-vitest/vitest.config.ts
@@ -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',
+ }
+ }
+ ]
+});
\ No newline at end of file