Skip to content

Commit 562e1b1

Browse files
authored
feat: allow inline workspace configuration (#6923)
1 parent 78f07c0 commit 562e1b1

File tree

20 files changed

+256
-86
lines changed

20 files changed

+256
-86
lines changed

docs/advanced/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export default function setup({ provide }) {
140140
```
141141
:::
142142
143-
## TestProject <Version>2.2.0</Version>
143+
## TestProject <Version>2.2.0</Version> {#testproject}
144144
145145
- **Alias**: `WorkspaceProject` before 2.2.0
146146

docs/config/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2437,12 +2437,14 @@ Tells fake timers to clear "native" (i.e. not fake) timers by delegating to thei
24372437

24382438
### workspace<NonProjectOption /> {#workspace}
24392439

2440-
- **Type:** `string`
2440+
- **Type:** `string | TestProjectConfiguration`
24412441
- **CLI:** `--workspace=./file.js`
24422442
- **Default:** `vitest.{workspace,projects}.{js,ts,json}` close to the config file or root
24432443

24442444
Path to a [workspace](/guide/workspace) config file relative to [root](#root).
24452445

2446+
Since Vitest 2.2, you can also define the workspace array in the root config. If the `workspace` is defined in the config manually, Vitest will ignore the `vitest.workspace` file in the root.
2447+
24462448
### isolate
24472449

24482450
- **Type:** `boolean`

docs/guide/workspace.md

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,31 @@ Vitest provides a way to define multiple project configurations within a single
1414

1515
## Defining a Workspace
1616

17-
A workspace must include a `vitest.workspace` or `vitest.projects` file in its root directory (located in the same folder as your root configuration file or working directory if it doesn't exist). Vitest supports `ts`, `js`, and `json` extensions for this file.
17+
A workspace must include a `vitest.workspace` or `vitest.projects` file in its root directory (located in the same folder as your root configuration file or working directory if it doesn't exist). Note that `projects` is just an alias and does not change the behavior or semantics of this feature. Vitest supports `ts`, `js`, and `json` extensions for this file.
18+
19+
Since Vitest 2.2, you can also define a workspace in the root config. In this case, Vitest will ignore the `vitest.workspace` file in the root, if one exists.
1820

1921
::: tip NAMING
2022
Please note that this feature is named `workspace`, not `workspaces` (without an "s" at the end).
2123
:::
2224

23-
Workspace configuration file must have a default export with a list of files or glob patterns referencing your projects. For example, if you have a folder named `packages` that contains your projects, you can define a workspace with this config file:
25+
A workspace is a list of inlined configs, files, or glob patterns referencing your projects. For example, if you have a folder named `packages` that contains your projects, you can either create a workspace file or define an array in the root config:
2426

2527
:::code-group
2628
```ts [vitest.workspace.ts]
2729
export default [
2830
'packages/*'
2931
]
3032
```
33+
```ts [vitest.config.ts <Version>2.2.0</Version>]
34+
import { defineConfig } from 'vitest/config'
35+
36+
export default defineConfig({
37+
test: {
38+
workspace: ['packages/*'],
39+
},
40+
})
41+
```
3142
:::
3243

3344
Vitest will treat every folder in `packages` as a separate project even if it doesn't have a config file inside. Since Vitest 2.1, if this glob pattern matches any file it will be considered a Vitest config even if it doesn't have a `vitest` in its name.
@@ -44,6 +55,15 @@ export default [
4455
'packages/*/vitest.config.{e2e,unit}.ts'
4556
]
4657
```
58+
```ts [vitest.config.ts <Version>2.2.0</Version>]
59+
import { defineConfig } from 'vitest/config'
60+
61+
export default defineConfig({
62+
test: {
63+
workspace: ['packages/*/vitest.config.{e2e,unit}.ts'],
64+
},
65+
})
66+
```
4767
:::
4868

4969
This pattern will only include projects with a `vitest.config` file that contains `e2e` or `unit` before the extension.
@@ -77,20 +97,58 @@ export default defineWorkspace([
7797
}
7898
])
7999
```
100+
```ts [vitest.config.ts <Version>2.2.0</Version>]
101+
import { defineConfig } from 'vitest/config'
102+
103+
export default defineConfig({
104+
test: {
105+
workspace: [
106+
// matches every folder and file inside the `packages` folder
107+
'packages/*',
108+
{
109+
// add "extends: true" to inherit the options from the root config
110+
extends: true,
111+
test: {
112+
include: ['tests/**/*.{browser}.test.{ts,js}'],
113+
// it is recommended to define a name when using inline configs
114+
name: 'happy-dom',
115+
environment: 'happy-dom',
116+
}
117+
},
118+
{
119+
test: {
120+
include: ['tests/**/*.{node}.test.{ts,js}'],
121+
name: 'node',
122+
environment: 'node',
123+
}
124+
}
125+
]
126+
}
127+
})
128+
```
80129
:::
81130

82131
::: warning
83132
All projects must have unique names; otherwise, Vitest will throw an error. If a name is not provided in the inline configuration, Vitest will assign a number. For project configurations defined with glob syntax, Vitest will default to using the "name" property in the nearest `package.json` file or, if none exists, the folder name.
84133
:::
85134

86-
If you do not use inline configurations, you can create a small JSON file in your root directory:
135+
If you do not use inline configurations, you can create a small JSON file in your root directory or just specify it in the root config:
87136

88137
:::code-group
89138
```json [vitest.workspace.json]
90139
[
91140
"packages/*"
92141
]
93142
```
143+
```ts [vitest.config.ts <Version>2.2.0</Version>]
144+
import { defineConfig } from 'vitest/config'
145+
146+
export default defineConfig({
147+
test: {
148+
workspace: ['packages/*'],
149+
},
150+
})
151+
```
94152
:::
95153

96154
Workspace projects do not support all configuration properties. For better type safety, use the `defineProject` method instead of `defineConfig` within project configuration files:
@@ -195,7 +253,7 @@ export default mergeConfig(
195253
```
196254
:::
197255

198-
At the `defineWorkspace` level, you can use the `extends` option to inherit from your root-level configuration. All options will be merged.
256+
Additionally, at the `defineWorkspace` level, you can use the `extends` option to inherit from your root-level configuration. All options will be merged.
199257

200258
::: code-group
201259
```ts [vitest.workspace.ts]
@@ -218,6 +276,36 @@ export default defineWorkspace([
218276
},
219277
])
220278
```
279+
```ts [vitest.config.ts <Version>2.2.0</Version>]
280+
import { defineConfig } from 'vitest/config'
281+
import react from '@vitejs/plugin-react'
282+
283+
export default defineConfig({
284+
plugins: [react()],
285+
test: {
286+
pool: 'threads',
287+
workspace: [
288+
{
289+
// will inherit options from this config like plugins and pool
290+
extends: true,
291+
test: {
292+
name: 'unit',
293+
include: ['**/*.unit.test.ts'],
294+
},
295+
},
296+
{
297+
// won't inherit any options from this config
298+
// this is the default behaviour
299+
extends: false,
300+
test: {
301+
name: 'integration',
302+
include: ['**/*.integration.test.ts'],
303+
},
304+
},
305+
],
306+
},
307+
})
308+
```
221309
:::
222310

223311
Some of the configuration options are not allowed in a project config. Most notably:

packages/browser/src/node/pool.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function createBrowserPool(ctx: Vitest): ProcessPool {
3333

3434
if (!origin) {
3535
throw new Error(
36-
`Can't find browser origin URL for project "${project.getName()}" when running tests for files "${files.join('", "')}"`,
36+
`Can't find browser origin URL for project "${project.name}" when running tests for files "${files.join('", "')}"`,
3737
)
3838
}
3939

@@ -67,7 +67,7 @@ export function createBrowserPool(ctx: Vitest): ProcessPool {
6767

6868
debug?.(
6969
`[%s] Running %s tests in %s chunks (%s threads)`,
70-
project.getName() || 'core',
70+
project.name || 'core',
7171
files.length,
7272
chunks.length,
7373
threadsCount,

packages/browser/src/node/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ export class BrowserServer implements IBrowserServer {
174174
const browser = this.project.config.browser.name
175175
if (!browser) {
176176
throw new Error(
177-
`[${this.project.getName()}] Browser name is required. Please, set \`test.browser.name\` option manually.`,
177+
`[${this.project.name}] Browser name is required. Please, set \`test.browser.name\` option manually.`,
178178
)
179179
}
180180
const supportedBrowsers = this.provider.getSupportedBrowsers()
181181
if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
182182
throw new Error(
183-
`[${this.project.getName()}] Browser "${browser}" is not supported by the browser provider "${
183+
`[${this.project.name}] Browser "${browser}" is not supported by the browser provider "${
184184
this.provider.name
185185
}". Supported browsers: ${supportedBrowsers.join(', ')}.`,
186186
)

packages/vitest/src/node/cache/files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class FilesStatsCache {
1414

1515
public async populateStats(root: string, specs: WorkspaceSpec[]) {
1616
const promises = specs.map((spec) => {
17-
const key = `${spec[0].getName()}:${relative(root, spec[1])}`
17+
const key = `${spec[0].name}:${relative(root, spec[1])}`
1818
return this.updateStats(spec[1], key)
1919
})
2020
await Promise.all(promises)

packages/vitest/src/node/config/resolveConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,10 +521,10 @@ export function resolveConfig(
521521
}
522522
}
523523

524-
if (resolved.workspace) {
524+
if (typeof resolved.workspace === 'string') {
525525
// if passed down from the CLI and it's relative, resolve relative to CWD
526526
resolved.workspace
527-
= options.workspace && options.workspace[0] === '.'
527+
= typeof options.workspace === 'string' && options.workspace[0] === '.'
528528
? resolve(process.cwd(), options.workspace)
529529
: resolvePath(resolved.workspace, resolved.root)
530530
}

packages/vitest/src/node/core.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ export class Vitest {
9494
/** @private */
9595
public _browserLastPort = defaultBrowserPort
9696

97+
/** @internal */
98+
public _options: UserConfig = {}
99+
97100
constructor(
98101
public readonly mode: VitestRunMode,
99102
options: VitestOptions = {},
@@ -109,6 +112,7 @@ export class Vitest {
109112
private _onUserTestsRerun: OnTestsRerunHandler[] = []
110113

111114
async setServer(options: UserConfig, server: ViteDevServer, cliOptions: UserConfig) {
115+
this._options = options
112116
this.unregisterWatcher?.()
113117
clearTimeout(this._rerunTimer)
114118
this.restartsCount += 1
@@ -164,7 +168,7 @@ export class Vitest {
164168
server.watcher.on('change', async (file) => {
165169
file = normalize(file)
166170
const isConfig = file === server.config.configFile
167-
|| this.resolvedProjects.some(p => p.server.config.configFile === file)
171+
|| this.resolvedProjects.some(p => p.vite.config.configFile === file)
168172
|| file === this._workspaceConfigPath
169173
if (isConfig) {
170174
await Promise.all(this._onRestartListeners.map(fn => fn('config')))
@@ -191,7 +195,7 @@ export class Vitest {
191195
const filters = toArray(resolved.project).map(s => wildcardPatternToRegExp(s))
192196
if (filters.length > 0) {
193197
this.projects = this.projects.filter(p =>
194-
filters.some(pattern => pattern.test(p.getName())),
198+
filters.some(pattern => pattern.test(p.name)),
195199
)
196200
}
197201
if (!this.coreWorkspaceProject) {
@@ -212,7 +216,7 @@ export class Vitest {
212216
/**
213217
* @internal
214218
*/
215-
_createCoreProject() {
219+
_createRootProject() {
216220
this.coreWorkspaceProject = TestProject._createBasicProject(this)
217221
return this.coreWorkspaceProject
218222
}
@@ -241,8 +245,8 @@ export class Vitest {
241245
|| this.projects[0]
242246
}
243247

244-
private async getWorkspaceConfigPath(): Promise<string | undefined> {
245-
if (this.config.workspace) {
248+
private async resolveWorkspaceConfigPath(): Promise<string | undefined> {
249+
if (typeof this.config.workspace === 'string') {
246250
return this.config.workspace
247251
}
248252

@@ -264,12 +268,21 @@ export class Vitest {
264268
}
265269

266270
private async resolveWorkspace(cliOptions: UserConfig) {
267-
const workspaceConfigPath = await this.getWorkspaceConfigPath()
271+
if (Array.isArray(this.config.workspace)) {
272+
return resolveWorkspace(
273+
this,
274+
cliOptions,
275+
undefined,
276+
this.config.workspace,
277+
)
278+
}
279+
280+
const workspaceConfigPath = await this.resolveWorkspaceConfigPath()
268281

269282
this._workspaceConfigPath = workspaceConfigPath
270283

271284
if (!workspaceConfigPath) {
272-
return [this._createCoreProject()]
285+
return [this._createRootProject()]
273286
}
274287

275288
const workspaceModule = await this.runner.executeFile(workspaceConfigPath) as {
@@ -731,7 +744,7 @@ export class Vitest {
731744
this.configOverride.project = pattern
732745
}
733746

734-
this.projects = this.resolvedProjects.filter(p => p.getName() === pattern)
747+
this.projects = this.resolvedProjects.filter(p => p.name === pattern)
735748
const files = (await this.globTestSpecs()).map(spec => spec.moduleId)
736749
await this.rerunFiles(files, 'change project filter', pattern === '')
737750
}

packages/vitest/src/node/pools/forks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function createForksPool(
116116
invalidates,
117117
environment,
118118
workerId,
119-
projectName: project.getName(),
119+
projectName: project.name,
120120
providedContext: project.getProvidedContext(),
121121
}
122122
try {
@@ -199,7 +199,7 @@ export function createForksPool(
199199
const grouped = groupBy(
200200
files,
201201
({ project, environment }) =>
202-
project.getName()
202+
project.name
203203
+ environment.name
204204
+ JSON.stringify(environment.options),
205205
)
@@ -256,7 +256,7 @@ export function createForksPool(
256256
const filesByOptions = groupBy(
257257
files,
258258
({ project, environment }) =>
259-
project.getName() + JSON.stringify(environment.options),
259+
project.name + JSON.stringify(environment.options),
260260
)
261261

262262
for (const files of Object.values(filesByOptions)) {

packages/vitest/src/node/pools/threads.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export function createThreadsPool(
111111
invalidates,
112112
environment,
113113
workerId,
114-
projectName: project.getName(),
114+
projectName: project.name,
115115
providedContext: project.getProvidedContext(),
116116
}
117117
try {
@@ -195,7 +195,7 @@ export function createThreadsPool(
195195
const grouped = groupBy(
196196
files,
197197
({ project, environment }) =>
198-
project.getName()
198+
project.name
199199
+ environment.name
200200
+ JSON.stringify(environment.options),
201201
)
@@ -252,7 +252,7 @@ export function createThreadsPool(
252252
const filesByOptions = groupBy(
253253
files,
254254
({ project, environment }) =>
255-
project.getName() + JSON.stringify(environment.options),
255+
project.name + JSON.stringify(environment.options),
256256
)
257257

258258
for (const files of Object.values(filesByOptions)) {

0 commit comments

Comments
 (0)