Skip to content

Commit d459434

Browse files
committed
docs: document shared configurations
1 parent 3764bf6 commit d459434

6 files changed

Lines changed: 538 additions & 2 deletions

File tree

‎website/docs/en/guide/api-reference.mdx‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Rstack CLI provides a unified configuration API and re-exports the public APIs o
1111
| Import path | Contents | Use case |
1212
| ------------------------ | ------------------------------------------------- | --------------------------------------- |
1313
| `rstack` | Rstack CLI configuration API | Register tool configurations |
14+
| `rstack/config` | Configuration loader and its types | Load shared and project configurations |
1415
| `rstack/app` | Public APIs from `@rsbuild/core` | Build applications and extend Rsbuild |
1516
| `rstack/lib` | Public APIs from `@rslib/core` | Build libraries and extend Rslib |
1617
| `rstack/test` | Public APIs from `@rstest/core` | Write tests and configure test projects |
@@ -25,6 +26,68 @@ Rstack CLI provides a unified configuration API and re-exports the public APIs o
2526

2627
Import `define` from `rstack` to register tool configurations in `rstack.config.ts`; see [Configuration APIs](./configuration#configuration-apis) for details.
2728

29+
### `define.extends()` \{#define-extends}
30+
31+
Registers shared configurations to apply before the project's own tool configurations:
32+
33+
```ts
34+
import { define } from 'rstack';
35+
import { sharedConfig } from './shared.ts';
36+
37+
define.extends([sharedConfig]);
38+
```
39+
40+
- **Type:** `(configs: readonly RstackConfig[]) => void`
41+
- Accepts configuration objects. For a parameterized preset, call the preset function and pass its return value.
42+
- Can be called at most once per configuration load, including calls with an empty array.
43+
- Applies shared configurations from left to right, then project definitions, regardless of call order.
44+
45+
See [Shared configurations](./configuration#shared-configurations) for nested inheritance, merge rules, and examples.
46+
47+
### `RstackConfig`
48+
49+
A type for authoring shared configurations. Its optional `app`, `lib`, `doc`, `test`, `lint`, `fmt`, and `staged` fields accept the same inputs as the corresponding `define.*()` APIs. Its optional `extends` field accepts `readonly RstackConfig[]` for nested inheritance.
50+
51+
```ts
52+
import type { RstackConfig } from 'rstack';
53+
54+
export const sharedConfig: RstackConfig = {
55+
fmt: {
56+
singleQuote: true,
57+
},
58+
};
59+
```
60+
61+
## Loading configurations
62+
63+
### `loadRstackConfig()` \{#loadrstackconfig}
64+
65+
Import `loadRstackConfig` from `rstack/config` to load configurations programmatically:
66+
67+
```ts
68+
import { loadRstackConfig } from 'rstack/config';
69+
70+
const { configs, filePath, dependencies } = await loadRstackConfig({
71+
cwd: process.cwd(),
72+
configFilePath: './rstack.config.ts',
73+
});
74+
```
75+
76+
Both options are optional:
77+
78+
- `cwd`: the directory used to search for a configuration file and resolve relative configuration file paths. Defaults to the current working directory.
79+
- `configFilePath`: a relative or absolute configuration file path. If omitted, uses the CLI's `--config` path when set; otherwise, searches for the [default configuration file names](./configuration#configuration-file) in `cwd`.
80+
81+
The returned object contains:
82+
83+
- `configs`: effective tool definitions, including shared configurations and project definitions. A tool's field is absent if it was never defined.
84+
- `filePath`: the loaded configuration file's path, or `null` if no file was found.
85+
- `dependencies`: configuration dependencies reported by the loader.
86+
87+
Configuration functions remain unevaluated. A field may be an async function even when all its inputs were objects, because merging is deferred until that tool is needed. Consumers must resolve definitions with the appropriate tool parameters and follow the tool's configuration and execution flow; this API does not return fully resolved runtime configurations. In particular, loading alone does not apply Rstack's automatic test inheritance or formatting normalization. Staged functions remain task generators that receive file names.
88+
89+
`rstack/config` also exports the `Configs`, `LoadedRstackConfig`, and `LoadRstackConfigOptions` types.
90+
2891
## Re-exports
2992

3093
The tool-specific subpaths below re-export the public APIs from their corresponding core packages. Using these entry points keeps imports unified and APIs aligned with the tool versions integrated by Rstack CLI.

‎website/docs/en/guide/configuration.mdx‎

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,171 @@ define.fmt({
7474
});
7575
```
7676

77+
## Shared configurations
78+
79+
Use `define.extends()` to reuse configurations across projects. A shared configuration is a plain object typed as `RstackConfig`. It can contain `app`, `lib`, `doc`, `test`, `lint`, `fmt`, and `staged`, each accepting the same input as its corresponding `define.*()` API.
80+
81+
```ts title="shared.ts"
82+
import type { RstackConfig } from 'rstack';
83+
84+
export const sharedConfig: RstackConfig = {
85+
test: {
86+
retry: 2,
87+
},
88+
lint: ({ js, ts }) => [js.configs.recommended, ts.configs.recommended],
89+
fmt: {
90+
singleQuote: true,
91+
printWidth: 80,
92+
},
93+
};
94+
```
95+
96+
Import the object from a local module or an npm package, then customize the settings your project needs:
97+
98+
```ts title="rstack.config.ts"
99+
import { define } from 'rstack';
100+
import { sharedConfig } from './shared.ts';
101+
102+
define.extends([sharedConfig]);
103+
104+
define.fmt({
105+
printWidth: 100,
106+
});
107+
```
108+
109+
This project inherits the test and lint configurations and formats with `singleQuote: true` and `printWidth: 100`. You do not need to call `define.test()` or `define.lint()` to enable inherited settings.
110+
111+
Call `define.extends()` at most once per configuration load, with all shared configurations in one array. A second call throws, even if the first call used an empty array. Call it while loading `rstack.config.*`, just like the other `define.*()` APIs.
112+
113+
### Merge order
114+
115+
Configurations are applied in this order:
116+
117+
1. Shared configurations in the `define.extends()` array, from left to right.
118+
2. Project configurations registered with `define.*()`, regardless of where those calls appear relative to `define.extends()`.
119+
120+
Later configurations take precedence according to each tool's [merge rules](#merge-rules).
121+
122+
Shared configurations can also extend other shared configurations:
123+
124+
```ts title="team.ts"
125+
import type { RstackConfig } from 'rstack';
126+
import { sharedConfig } from './shared.ts';
127+
128+
export const teamConfig: RstackConfig = {
129+
extends: [sharedConfig],
130+
test: {
131+
retry: 3,
132+
},
133+
};
134+
```
135+
136+
Inherited configurations are applied before the object that extends them. Here, the order is `sharedConfig → teamConfig → project configuration`.
137+
138+
Each occurrence of a shared configuration is applied. For example, if both `a` and `b` extend `base`, `define.extends([a, b])` applies `base → a → base → b → project configuration`. This can append arrays more than once; plugin deduplication follows the underlying tool's behavior. Circular inheritance throws an error that identifies the reference path.
139+
140+
### Merge rules
141+
142+
Rstack merges configurations separately for each tool:
143+
144+
| Field | Merge behavior |
145+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
146+
| `app` | Uses Rsbuild's `mergeRsbuildConfig`. |
147+
| `lib` | Uses Rslib's `mergeRslibConfig`. |
148+
| `test` | Uses Rstest's `mergeRstestConfig`, then determines whether to inherit the merged App or Lib configuration. See [Test configuration inheritance](./testing#configuration-inheritance). |
149+
| `lint` | Concatenates configuration arrays in order; Rslint applies their rules. |
150+
| `fmt` | Shallow merge: later values replace earlier values for the same option. Arrays and objects, including `overrides`, are replaced as a whole. |
151+
| `doc` | Uses Rspress's `mergeDocConfig`: recursively merges objects, appends arrays, and replaces ordinary functions with later functions. |
152+
| `staged` | Shallow merge by glob pattern: later tasks replace earlier tasks for the same pattern, including command arrays and task functions. |
153+
154+
For `doc`, when both values are defined and either is an array, Rspress treats the other value as a one-item array before concatenating them. These rules also apply to `builderConfig`: Rstack does not additionally call `mergeRsbuildConfig`. For example, a later `builderConfig.tools.rspack` callback replaces an earlier callback, while callback arrays are concatenated.
155+
156+
For `staged`, if either the accumulated configuration or the next configuration is a top-level task-generator function, the next configuration replaces the accumulated configuration entirely. Subsequent glob mappings merge from that replacement; they do not restore earlier tasks. Task functions run when lint-staged supplies the staged file list, not while merging configurations.
157+
158+
### App and lib presets
159+
160+
A developer infrastructure team can maintain two presets: one for App + Test + Lint + Format, and another for Lib + Test + Lint + Format. Both can extend the `sharedConfig` above.
161+
162+
A preset can be a regular function when consumers need options. Call it to produce an object before passing it to `define.extends()`:
163+
164+
```ts title="app-preset.ts"
165+
import type { RstackConfig } from 'rstack';
166+
import { sharedConfig } from './shared.ts';
167+
168+
export function appPreset(options: { title: string }): RstackConfig {
169+
return {
170+
extends: [sharedConfig],
171+
app: async () => {
172+
const { pluginReact } = await import('@rsbuild/plugin-react');
173+
return {
174+
plugins: [pluginReact()],
175+
html: {
176+
title: options.title,
177+
},
178+
};
179+
},
180+
test: {
181+
testEnvironment: 'happy-dom',
182+
},
183+
};
184+
}
185+
```
186+
187+
```ts title="lib-preset.ts"
188+
import type { RstackConfig } from 'rstack';
189+
import { sharedConfig } from './shared.ts';
190+
191+
export const libPreset: RstackConfig = {
192+
extends: [sharedConfig],
193+
lib: {
194+
format: 'esm',
195+
dts: true,
196+
},
197+
test: {
198+
testEnvironment: 'node',
199+
},
200+
};
201+
```
202+
203+
After publishing these presets as `@biz/app-preset` and `@biz/lib-preset`, an application can use:
204+
205+
```ts title="rstack.config.ts (application)"
206+
import { appPreset } from '@biz/app-preset';
207+
import { define } from 'rstack';
208+
209+
define.extends([appPreset({ title: 'My App' })]);
210+
```
211+
212+
A library can use:
213+
214+
```ts title="rstack.config.ts (library)"
215+
import { libPreset } from '@biz/lib-preset';
216+
import { define } from 'rstack';
217+
218+
define.extends([libPreset]);
219+
```
220+
221+
Make the presets' dependencies available: for this example, the App preset needs `@rsbuild/plugin-react`, and application projects need `happy-dom` for their tests.
222+
223+
### Loading dependencies and resolving paths
224+
225+
Shared configurations follow the same [dependency loading guidance](#loading-dependencies-on-demand) as project configurations. Only the tool configuration functions needed by a command are evaluated. In the App preset above, `rs lint` and `rs fmt` do not load the React plugin; `rs test` may load it when inheriting the App configuration. The outer `appPreset()` function runs when the configuration file is loaded, so keep tool-specific imports inside the relevant configuration function.
226+
227+
Relative paths follow each tool's existing rules. They are not automatically resolved relative to the shared configuration module. For a file shipped with a preset, use an absolute path derived from that module:
228+
229+
```ts title="shared.ts"
230+
import { fileURLToPath } from 'node:url';
231+
import type { RstackConfig } from 'rstack';
232+
233+
export const sharedConfig: RstackConfig = {
234+
test: {
235+
setupFiles: [fileURLToPath(new URL('./setup.ts', import.meta.url))],
236+
},
237+
};
238+
```
239+
240+
Include `setup.ts` in the published package at that location, or adjust the path to the built file. Existing local configuration dependency tracking also applies to shared modules: editing an imported local shared configuration reloads App, Lib, and Doc configurations in their development or watch modes.
241+
77242
## Configuration APIs
78243

79244
Configuration options follow the formats of the underlying tools. When using APIs and helpers that Rstack CLI re-exports, prefer the `rstack/app`, `rstack/lib`, `rstack/test`, and `rstack/lint` entry points.
@@ -158,6 +323,8 @@ When `extends` is omitted, Rstack CLI automatically connects the test configurat
158323

159324
If the root test configuration does not define `extends` and contains `projects`, Rstack CLI applies automatic inheritance to each inline project that omits its own `extends`. A function-based application or library configuration is resolved once and shared by those projects. String project entries are passed to Rstest unchanged; they load their external configurations independently and do not inherit the current application or library configuration.
160325

326+
When using shared configurations, these rules apply after merging all test configurations and use the merged App or Lib configuration. See [Shared test configurations](./testing#shared-test-configurations).
327+
161328
> For more guidance on testing, see [Testing](./testing).
162329
163330
### `define.lint()` \{#define-lint}
@@ -201,4 +368,4 @@ define.staged({
201368
});
202369
```
203370

204-
Unlike the other commands, `rs staged` requires a `define.staged()` configuration and reports an error when it is missing.
371+
`rs staged` requires a staged configuration, provided by `define.staged()` or a shared configuration, and reports an error when it is missing.

‎website/docs/en/guide/testing.mdx‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,44 @@ define.test({
107107

108108
For multiple projects, setting `extends` on the root `define.test()` configuration disables automatic inheritance for every project. Setting it on an inline project disables inheritance only for that project.
109109

110+
### Shared test configurations
111+
112+
[`define.extends()`](./configuration#shared-configurations) shares configurations across tools. The `extends` field inside a test configuration is Rstest's own inheritance option. These operate at different levels.
113+
114+
Rstack first merges all shared and project test configurations using `mergeRstestConfig`. It then checks the resulting `extends` and `projects` fields to decide whether automatic inheritance is needed. If it is, Rstack uses the merged App configuration, or the merged Lib configuration when no App configuration is defined.
115+
116+
```ts title="shared.ts"
117+
import type { RstackConfig } from 'rstack';
118+
119+
export const sharedConfig: RstackConfig = {
120+
app: {
121+
resolve: {
122+
alias: {
123+
'@': './src',
124+
},
125+
},
126+
},
127+
test: {
128+
retry: 2,
129+
},
130+
};
131+
```
132+
133+
```ts title="rstack.config.ts"
134+
import { define } from 'rstack';
135+
import { sharedConfig } from './shared.ts';
136+
137+
define.extends([sharedConfig]);
138+
139+
define.test({
140+
testEnvironment: 'happy-dom',
141+
});
142+
```
143+
144+
Tests use `retry: 2`, run in happy-dom, and inherit the `@` alias from the shared App configuration. A local `define.app()` call is not required.
145+
146+
The same opt-out rules apply to shared configurations: an explicit `extends` in the merged test configuration, including `extends: undefined`, disables automatic inheritance. Inline projects are checked after merging; external string projects still load independently. If no project needs automatic inheritance, Rstack does not evaluate App or Lib configuration functions for it.
147+
110148
## Multiple projects
111149

112150
Set Rstest's [`projects`](https://rstest.rs/config/test/projects) option to run multiple test configurations together. Entries can be inline projects or strings that Rstest resolves as external projects.

‎website/docs/zh/guide/api-reference.mdx‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Rstack CLI 提供统一的配置 API,并通过专用子路径重导出 Rsbuild
1111
| 导入路径 | 内容 | 使用场景 |
1212
| ------------------------ | ----------------------------------------- | ------------------------ |
1313
| `rstack` | Rstack CLI 配置 API | 注册各项工具配置 |
14+
| `rstack/config` | 配置加载器及其类型 | 加载公共配置和项目配置 |
1415
| `rstack/app` | `@rsbuild/core` 的公开 API | 构建应用及扩展 Rsbuild |
1516
| `rstack/lib` | `@rslib/core` 的公开 API | 构建库及扩展 Rslib |
1617
| `rstack/test` | `@rstest/core` 的公开 API | 编写测试及配置测试项目 |
@@ -25,6 +26,68 @@ Rstack CLI 提供统一的配置 API,并通过专用子路径重导出 Rsbuild
2526

2627
从 `rstack` 导入 `define`,用于在 `rstack.config.ts` 中注册各项工具配置;详细用法请参阅[配置 API](./configuration#configuration-apis)。
2728

29+
### `define.extends()` \{#define-extends}
30+
31+
注册公共配置,在项目自身的工具配置之前应用:
32+
33+
```ts
34+
import { define } from 'rstack';
35+
import { sharedConfig } from './shared.ts';
36+
37+
define.extends([sharedConfig]);
38+
```
39+
40+
- **类型:** `(configs: readonly RstackConfig[]) => void`
41+
- 接受配置对象。对于带参数的预设,先调用预设函数,再传入返回值。
42+
- 每次加载配置时最多调用一次,传入空数组的调用也计入此限制。
43+
- 先从左到右应用公共配置,再应用项目配置,与调用位置无关。
44+
45+
嵌套继承、合并规则和示例请参阅[公共配置](./configuration#shared-configurations)。
46+
47+
### `RstackConfig`
48+
49+
用于编写公共配置的类型。可选字段 `app`、`lib`、`doc`、`test`、`lint`、`fmt` 和 `staged` 接受与对应 `define.*()` API 相同的输入。可选字段 `extends` 接受 `readonly RstackConfig[]`,用于嵌套继承。
50+
51+
```ts
52+
import type { RstackConfig } from 'rstack';
53+
54+
export const sharedConfig: RstackConfig = {
55+
fmt: {
56+
singleQuote: true,
57+
},
58+
};
59+
```
60+
61+
## 加载配置 \{#loading-configurations}
62+
63+
### `loadRstackConfig()` \{#loadrstackconfig}
64+
65+
从 `rstack/config` 导入 `loadRstackConfig`,以编程方式加载配置:
66+
67+
```ts
68+
import { loadRstackConfig } from 'rstack/config';
69+
70+
const { configs, filePath, dependencies } = await loadRstackConfig({
71+
cwd: process.cwd(),
72+
configFilePath: './rstack.config.ts',
73+
});
74+
```
75+
76+
两个选项均可省略:
77+
78+
- `cwd`:用于查找配置文件和解析相对配置文件路径的目录,默认为当前工作目录。
79+
- `configFilePath`:配置文件的相对或绝对路径。省略时,优先使用 CLI 的 `--config` 路径;如果未设置,则在 `cwd` 中查找[默认配置文件名](./configuration#configuration-file)。
80+
81+
返回对象包含:
82+
83+
- `configs`:包含公共配置和项目配置的有效工具配置定义。未定义过的工具不会出现在该对象中。
84+
- `filePath`:加载的配置文件路径,未找到文件时为 `null`。
85+
- `dependencies`:加载器报告的配置依赖。
86+
87+
配置函数保持未执行状态。即使输入全部是对象,返回的字段仍可能是异步函数,因为合并会推迟到需要该工具时进行。调用方需要传入相应工具的参数来解析配置定义,并遵循工具自身的配置和执行流程;此 API 不会返回完全解析后的运行时配置。尤其是,仅加载配置不会应用 Rstack 的测试自动继承或格式化配置标准化。Staged 函数仍是接收文件名的任务生成函数。
88+
89+
`rstack/config` 还导出 `Configs`、`LoadedRstackConfig` 和 `LoadRstackConfigOptions` 类型。
90+
2891
## 重导出 \{#re-exports}
2992

3093
以下工具子路径均会重导出对应 core 包的公开 API。通过这些入口导入,可以统一依赖入口,并确保 API 与 Rstack CLI 集成的工具版本匹配。

0 commit comments

Comments
 (0)