From d459434c85bce245cbb6d4267841b4fc2cde4874 Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 9 Oct 2026 12:04:58 +0800 Subject: [PATCH 1/6] docs: document shared configurations --- website/docs/en/guide/api-reference.mdx | 63 +++++++++ website/docs/en/guide/configuration.mdx | 169 +++++++++++++++++++++++- website/docs/en/guide/testing.mdx | 38 ++++++ website/docs/zh/guide/api-reference.mdx | 63 +++++++++ website/docs/zh/guide/configuration.mdx | 169 +++++++++++++++++++++++- website/docs/zh/guide/testing.mdx | 38 ++++++ 6 files changed, 538 insertions(+), 2 deletions(-) diff --git a/website/docs/en/guide/api-reference.mdx b/website/docs/en/guide/api-reference.mdx index 4a28006e..1032bc40 100644 --- a/website/docs/en/guide/api-reference.mdx +++ b/website/docs/en/guide/api-reference.mdx @@ -11,6 +11,7 @@ Rstack CLI provides a unified configuration API and re-exports the public APIs o | Import path | Contents | Use case | | ------------------------ | ------------------------------------------------- | --------------------------------------- | | `rstack` | Rstack CLI configuration API | Register tool configurations | +| `rstack/config` | Configuration loader and its types | Load shared and project configurations | | `rstack/app` | Public APIs from `@rsbuild/core` | Build applications and extend Rsbuild | | `rstack/lib` | Public APIs from `@rslib/core` | Build libraries and extend Rslib | | `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 Import `define` from `rstack` to register tool configurations in `rstack.config.ts`; see [Configuration APIs](./configuration#configuration-apis) for details. +### `define.extends()` \{#define-extends} + +Registers shared configurations to apply before the project's own tool configurations: + +```ts +import { define } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +define.extends([sharedConfig]); +``` + +- **Type:** `(configs: readonly RstackConfig[]) => void` +- Accepts configuration objects. For a parameterized preset, call the preset function and pass its return value. +- Can be called at most once per configuration load, including calls with an empty array. +- Applies shared configurations from left to right, then project definitions, regardless of call order. + +See [Shared configurations](./configuration#shared-configurations) for nested inheritance, merge rules, and examples. + +### `RstackConfig` + +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. + +```ts +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + fmt: { + singleQuote: true, + }, +}; +``` + +## Loading configurations + +### `loadRstackConfig()` \{#loadrstackconfig} + +Import `loadRstackConfig` from `rstack/config` to load configurations programmatically: + +```ts +import { loadRstackConfig } from 'rstack/config'; + +const { configs, filePath, dependencies } = await loadRstackConfig({ + cwd: process.cwd(), + configFilePath: './rstack.config.ts', +}); +``` + +Both options are optional: + +- `cwd`: the directory used to search for a configuration file and resolve relative configuration file paths. Defaults to the current working directory. +- `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`. + +The returned object contains: + +- `configs`: effective tool definitions, including shared configurations and project definitions. A tool's field is absent if it was never defined. +- `filePath`: the loaded configuration file's path, or `null` if no file was found. +- `dependencies`: configuration dependencies reported by the loader. + +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. + +`rstack/config` also exports the `Configs`, `LoadedRstackConfig`, and `LoadRstackConfigOptions` types. + ## Re-exports 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. diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index 5ac15d1a..60797985 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -74,6 +74,171 @@ define.fmt({ }); ``` +## Shared configurations + +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. + +```ts title="shared.ts" +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + test: { + retry: 2, + }, + lint: ({ js, ts }) => [js.configs.recommended, ts.configs.recommended], + fmt: { + singleQuote: true, + printWidth: 80, + }, +}; +``` + +Import the object from a local module or an npm package, then customize the settings your project needs: + +```ts title="rstack.config.ts" +import { define } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +define.extends([sharedConfig]); + +define.fmt({ + printWidth: 100, +}); +``` + +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. + +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. + +### Merge order + +Configurations are applied in this order: + +1. Shared configurations in the `define.extends()` array, from left to right. +2. Project configurations registered with `define.*()`, regardless of where those calls appear relative to `define.extends()`. + +Later configurations take precedence according to each tool's [merge rules](#merge-rules). + +Shared configurations can also extend other shared configurations: + +```ts title="team.ts" +import type { RstackConfig } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +export const teamConfig: RstackConfig = { + extends: [sharedConfig], + test: { + retry: 3, + }, +}; +``` + +Inherited configurations are applied before the object that extends them. Here, the order is `sharedConfig → teamConfig → project configuration`. + +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. + +### Merge rules + +Rstack merges configurations separately for each tool: + +| Field | Merge behavior | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `app` | Uses Rsbuild's `mergeRsbuildConfig`. | +| `lib` | Uses Rslib's `mergeRslibConfig`. | +| `test` | Uses Rstest's `mergeRstestConfig`, then determines whether to inherit the merged App or Lib configuration. See [Test configuration inheritance](./testing#configuration-inheritance). | +| `lint` | Concatenates configuration arrays in order; Rslint applies their rules. | +| `fmt` | Shallow merge: later values replace earlier values for the same option. Arrays and objects, including `overrides`, are replaced as a whole. | +| `doc` | Uses Rspress's `mergeDocConfig`: recursively merges objects, appends arrays, and replaces ordinary functions with later functions. | +| `staged` | Shallow merge by glob pattern: later tasks replace earlier tasks for the same pattern, including command arrays and task functions. | + +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. + +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. + +### App and lib presets + +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. + +A preset can be a regular function when consumers need options. Call it to produce an object before passing it to `define.extends()`: + +```ts title="app-preset.ts" +import type { RstackConfig } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +export function appPreset(options: { title: string }): RstackConfig { + return { + extends: [sharedConfig], + app: async () => { + const { pluginReact } = await import('@rsbuild/plugin-react'); + return { + plugins: [pluginReact()], + html: { + title: options.title, + }, + }; + }, + test: { + testEnvironment: 'happy-dom', + }, + }; +} +``` + +```ts title="lib-preset.ts" +import type { RstackConfig } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +export const libPreset: RstackConfig = { + extends: [sharedConfig], + lib: { + format: 'esm', + dts: true, + }, + test: { + testEnvironment: 'node', + }, +}; +``` + +After publishing these presets as `@biz/app-preset` and `@biz/lib-preset`, an application can use: + +```ts title="rstack.config.ts (application)" +import { appPreset } from '@biz/app-preset'; +import { define } from 'rstack'; + +define.extends([appPreset({ title: 'My App' })]); +``` + +A library can use: + +```ts title="rstack.config.ts (library)" +import { libPreset } from '@biz/lib-preset'; +import { define } from 'rstack'; + +define.extends([libPreset]); +``` + +Make the presets' dependencies available: for this example, the App preset needs `@rsbuild/plugin-react`, and application projects need `happy-dom` for their tests. + +### Loading dependencies and resolving paths + +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. + +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: + +```ts title="shared.ts" +import { fileURLToPath } from 'node:url'; +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + test: { + setupFiles: [fileURLToPath(new URL('./setup.ts', import.meta.url))], + }, +}; +``` + +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. + ## Configuration APIs 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 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. +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). + > For more guidance on testing, see [Testing](./testing). ### `define.lint()` \{#define-lint} @@ -201,4 +368,4 @@ define.staged({ }); ``` -Unlike the other commands, `rs staged` requires a `define.staged()` configuration and reports an error when it is missing. +`rs staged` requires a staged configuration, provided by `define.staged()` or a shared configuration, and reports an error when it is missing. diff --git a/website/docs/en/guide/testing.mdx b/website/docs/en/guide/testing.mdx index db90404c..e4e5b91b 100644 --- a/website/docs/en/guide/testing.mdx +++ b/website/docs/en/guide/testing.mdx @@ -107,6 +107,44 @@ define.test({ 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. +### Shared test configurations + +[`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. + +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. + +```ts title="shared.ts" +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + app: { + resolve: { + alias: { + '@': './src', + }, + }, + }, + test: { + retry: 2, + }, +}; +``` + +```ts title="rstack.config.ts" +import { define } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +define.extends([sharedConfig]); + +define.test({ + testEnvironment: 'happy-dom', +}); +``` + +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. + +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. + ## Multiple projects 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. diff --git a/website/docs/zh/guide/api-reference.mdx b/website/docs/zh/guide/api-reference.mdx index 16c373fc..43bd8015 100644 --- a/website/docs/zh/guide/api-reference.mdx +++ b/website/docs/zh/guide/api-reference.mdx @@ -11,6 +11,7 @@ Rstack CLI 提供统一的配置 API,并通过专用子路径重导出 Rsbuild | 导入路径 | 内容 | 使用场景 | | ------------------------ | ----------------------------------------- | ------------------------ | | `rstack` | Rstack CLI 配置 API | 注册各项工具配置 | +| `rstack/config` | 配置加载器及其类型 | 加载公共配置和项目配置 | | `rstack/app` | `@rsbuild/core` 的公开 API | 构建应用及扩展 Rsbuild | | `rstack/lib` | `@rslib/core` 的公开 API | 构建库及扩展 Rslib | | `rstack/test` | `@rstest/core` 的公开 API | 编写测试及配置测试项目 | @@ -25,6 +26,68 @@ Rstack CLI 提供统一的配置 API,并通过专用子路径重导出 Rsbuild 从 `rstack` 导入 `define`,用于在 `rstack.config.ts` 中注册各项工具配置;详细用法请参阅[配置 API](./configuration#configuration-apis)。 +### `define.extends()` \{#define-extends} + +注册公共配置,在项目自身的工具配置之前应用: + +```ts +import { define } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +define.extends([sharedConfig]); +``` + +- **类型:** `(configs: readonly RstackConfig[]) => void` +- 接受配置对象。对于带参数的预设,先调用预设函数,再传入返回值。 +- 每次加载配置时最多调用一次,传入空数组的调用也计入此限制。 +- 先从左到右应用公共配置,再应用项目配置,与调用位置无关。 + +嵌套继承、合并规则和示例请参阅[公共配置](./configuration#shared-configurations)。 + +### `RstackConfig` + +用于编写公共配置的类型。可选字段 `app`、`lib`、`doc`、`test`、`lint`、`fmt` 和 `staged` 接受与对应 `define.*()` API 相同的输入。可选字段 `extends` 接受 `readonly RstackConfig[]`,用于嵌套继承。 + +```ts +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + fmt: { + singleQuote: true, + }, +}; +``` + +## 加载配置 \{#loading-configurations} + +### `loadRstackConfig()` \{#loadrstackconfig} + +从 `rstack/config` 导入 `loadRstackConfig`,以编程方式加载配置: + +```ts +import { loadRstackConfig } from 'rstack/config'; + +const { configs, filePath, dependencies } = await loadRstackConfig({ + cwd: process.cwd(), + configFilePath: './rstack.config.ts', +}); +``` + +两个选项均可省略: + +- `cwd`:用于查找配置文件和解析相对配置文件路径的目录,默认为当前工作目录。 +- `configFilePath`:配置文件的相对或绝对路径。省略时,优先使用 CLI 的 `--config` 路径;如果未设置,则在 `cwd` 中查找[默认配置文件名](./configuration#configuration-file)。 + +返回对象包含: + +- `configs`:包含公共配置和项目配置的有效工具配置定义。未定义过的工具不会出现在该对象中。 +- `filePath`:加载的配置文件路径,未找到文件时为 `null`。 +- `dependencies`:加载器报告的配置依赖。 + +配置函数保持未执行状态。即使输入全部是对象,返回的字段仍可能是异步函数,因为合并会推迟到需要该工具时进行。调用方需要传入相应工具的参数来解析配置定义,并遵循工具自身的配置和执行流程;此 API 不会返回完全解析后的运行时配置。尤其是,仅加载配置不会应用 Rstack 的测试自动继承或格式化配置标准化。Staged 函数仍是接收文件名的任务生成函数。 + +`rstack/config` 还导出 `Configs`、`LoadedRstackConfig` 和 `LoadRstackConfigOptions` 类型。 + ## 重导出 \{#re-exports} 以下工具子路径均会重导出对应 core 包的公开 API。通过这些入口导入,可以统一依赖入口,并确保 API 与 Rstack CLI 集成的工具版本匹配。 diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index dc47fa55..be090b75 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -74,6 +74,171 @@ define.fmt({ }); ``` +## 公共配置 \{#shared-configurations} + +使用 `define.extends()` 在多个项目之间复用配置。公共配置是一个使用 `RstackConfig` 类型的普通对象,可以包含 `app`、`lib`、`doc`、`test`、`lint`、`fmt` 和 `staged`,每个字段接受与对应 `define.*()` API 相同的输入。 + +```ts title="shared.ts" +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + test: { + retry: 2, + }, + lint: ({ js, ts }) => [js.configs.recommended, ts.configs.recommended], + fmt: { + singleQuote: true, + printWidth: 80, + }, +}; +``` + +从本地模块或 npm 包导入公共配置对象,再按项目需求调整配置: + +```ts title="rstack.config.ts" +import { define } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +define.extends([sharedConfig]); + +define.fmt({ + printWidth: 100, +}); +``` + +这个项目会继承测试和 lint 配置,并使用 `singleQuote: true` 和 `printWidth: 100` 进行格式化。无需额外调用 `define.test()` 或 `define.lint()`,继承的配置就会生效。 + +每次加载配置时,`define.extends()` 最多调用一次,所有公共配置应放在同一个数组中。即使第一次传入空数组,第二次调用也会抛出错误。与其他 `define.*()` API 一样,请在加载 `rstack.config.*` 的过程中调用它。 + +### 合并顺序 \{#merge-order} + +配置按以下顺序应用: + +1. `define.extends()` 数组中的公共配置,从左到右。 +2. 通过 `define.*()` 注册的项目配置,与这些调用在 `define.extends()` 前后的位置无关。 + +后面的配置按照各工具的[合并规则](#merge-rules)覆盖前面的配置。 + +公共配置也可以继承其他公共配置: + +```ts title="team.ts" +import type { RstackConfig } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +export const teamConfig: RstackConfig = { + extends: [sharedConfig], + test: { + retry: 3, + }, +}; +``` + +先应用被继承的配置,再应用当前对象。上例的顺序为 `sharedConfig → teamConfig → 项目配置`。 + +公共配置每出现一次就应用一次。例如,`a` 和 `b` 都继承 `base` 时,`define.extends([a, b])` 的顺序为 `base → a → base → b → 项目配置`。这可能导致数组被重复追加,插件是否去重由底层工具决定。循环继承会抛出错误,并指出引用位置。 + +### 合并规则 \{#merge-rules} + +Rstack 分别合并各个工具的配置: + +| 字段 | 合并方式 | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `app` | 使用 Rsbuild 的 `mergeRsbuildConfig`。 | +| `lib` | 使用 Rslib 的 `mergeRslibConfig`。 | +| `test` | 使用 Rstest 的 `mergeRstestConfig`,再判断是否继承合并后的 App 或 Lib 配置,详见[测试配置继承](./testing#configuration-inheritance)。 | +| `lint` | 按顺序拼接配置数组,由 Rslint 应用其中的规则。 | +| `fmt` | 浅合并,同名选项由后面的值覆盖。数组和对象(包括 `overrides`)整体替换。 | +| `doc` | 使用 Rspress 的 `mergeDocConfig`:递归合并对象、追加数组,普通函数由后面的函数覆盖。 | +| `staged` | 按 glob 模式浅合并,同名模式的任务整体替换,包括命令数组和任务函数。 | + +对于 `doc`,前后值均已定义且任一值为数组时,Rspress 会先将另一个值视为单项数组,再拼接。`builderConfig` 同样遵循这些规则,Rstack 不会额外调用 `mergeRsbuildConfig`。例如,后面的 `builderConfig.tools.rspack` 回调会覆盖前面的回调,而回调数组则按顺序拼接。 + +对于 `staged`,如果当前合并结果或下一份配置是顶层任务生成函数,下一份配置就会整体替换当前结果。后续 glob 映射从替换后的结果继续合并,不会恢复更早的任务。任务函数由 lint-staged 在获得暂存文件列表后调用,不会在配置合并期间执行。 + +### App 和 lib 预设 \{#app-and-lib-presets} + +开发者基础设施团队可以维护两套预设:一套涵盖 App + Test + Lint + Format,另一套涵盖 Lib + Test + Lint + Format。两者都可以继承上面的 `sharedConfig`。 + +如果使用者需要传入选项,可以将预设设计为普通函数。调用它得到配置对象后,再传给 `define.extends()`: + +```ts title="app-preset.ts" +import type { RstackConfig } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +export function appPreset(options: { title: string }): RstackConfig { + return { + extends: [sharedConfig], + app: async () => { + const { pluginReact } = await import('@rsbuild/plugin-react'); + return { + plugins: [pluginReact()], + html: { + title: options.title, + }, + }; + }, + test: { + testEnvironment: 'happy-dom', + }, + }; +} +``` + +```ts title="lib-preset.ts" +import type { RstackConfig } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +export const libPreset: RstackConfig = { + extends: [sharedConfig], + lib: { + format: 'esm', + dts: true, + }, + test: { + testEnvironment: 'node', + }, +}; +``` + +将两套预设发布为 `@biz/app-preset` 和 `@biz/lib-preset` 后,应用项目可以这样使用: + +```ts title="rstack.config.ts(应用项目)" +import { appPreset } from '@biz/app-preset'; +import { define } from 'rstack'; + +define.extends([appPreset({ title: 'My App' })]); +``` + +库项目可以这样使用: + +```ts title="rstack.config.ts(库项目)" +import { libPreset } from '@biz/lib-preset'; +import { define } from 'rstack'; + +define.extends([libPreset]); +``` + +请确保预设依赖的包可用:在本例中,App 预设需要 `@rsbuild/plugin-react`,应用项目需要安装 `happy-dom` 来运行测试。 + +### 依赖加载与路径解析 \{#loading-dependencies-and-resolving-paths} + +公共配置与项目配置遵循相同的[依赖加载建议](#loading-dependencies-on-demand),仅执行当前命令需要的工具配置函数。在上面的 App 预设中,`rs lint` 和 `rs fmt` 不会加载 React 插件;`rs test` 在继承 App 配置时可能会加载它。外层的 `appPreset()` 函数会在加载配置文件时执行,因此请将工具专用依赖的导入放在对应的配置函数内。 + +相对路径遵循各工具现有的解析规则,不会自动以公共配置模块所在目录为基准。对于随预设发布的文件,请使用相对于该模块生成的绝对路径: + +```ts title="shared.ts" +import { fileURLToPath } from 'node:url'; +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + test: { + setupFiles: [fileURLToPath(new URL('./setup.ts', import.meta.url))], + }, +}; +``` + +请将 `setup.ts` 发布到包中的对应位置,或将路径调整为构建后的文件。现有的本地配置依赖追踪也适用于公共模块:修改导入的本地公共配置时,App、Lib 和 Doc 会在开发或监听模式下重新加载配置。 + ## 配置 API \{#configuration-apis} 各 API 沿用底层工具的配置格式。使用 Rstack CLI 已重导出的 API 和辅助函数时,推荐从 `rstack/app`、`rstack/lib`、`rstack/test` 和 `rstack/lint` 入口导入。 @@ -158,6 +323,8 @@ define.test({ 如果测试根配置未定义 `extends` 且包含 `projects`,Rstack CLI 会为每个未自行设置 `extends` 的内联项目应用自动继承。函数形式的应用或库配置只会解析一次,并由这些项目共享。字符串形式的项目会原样传给 Rstest;它们会独立加载外部配置,不继承当前应用或库的配置。 +使用公共配置时,以上规则在合并所有测试配置后应用,并使用合并后的 App 或 Lib 配置。详见[公共测试配置](./testing#shared-test-configurations)。 + > 如需了解更多测试相关用法,请参阅[测试](./testing)。 ### `define.lint()` \{#define-lint} @@ -201,4 +368,4 @@ define.staged({ }); ``` -与其他命令不同,`rs staged` 必须配置 `define.staged()`;缺少配置时会报错。 +`rs staged` 必须通过 `define.staged()` 或公共配置提供 staged 配置,缺少配置时会报错。 diff --git a/website/docs/zh/guide/testing.mdx b/website/docs/zh/guide/testing.mdx index 43474a71..9992d054 100644 --- a/website/docs/zh/guide/testing.mdx +++ b/website/docs/zh/guide/testing.mdx @@ -107,6 +107,44 @@ define.test({ 使用多项目配置时,在 `define.test()` 根配置中设置 `extends` 会关闭所有项目的自动继承;在某个内联项目中设置 `extends` 则只会关闭该项目的自动继承。 +### 公共测试配置 \{#shared-test-configurations} + +[`define.extends()`](./configuration#shared-configurations) 用于跨工具共享配置,而测试配置内的 `extends` 字段是 Rstest 自身的继承选项,两者作用于不同层级。 + +Rstack 先使用 `mergeRstestConfig` 合并所有公共配置和项目中的测试配置,再根据结果中的 `extends` 和 `projects` 判断是否需要自动继承。如果需要,则使用合并后的 App 配置;未定义任何 App 配置时,使用合并后的 Lib 配置。 + +```ts title="shared.ts" +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + app: { + resolve: { + alias: { + '@': './src', + }, + }, + }, + test: { + retry: 2, + }, +}; +``` + +```ts title="rstack.config.ts" +import { define } from 'rstack'; +import { sharedConfig } from './shared.ts'; + +define.extends([sharedConfig]); + +define.test({ + testEnvironment: 'happy-dom', +}); +``` + +测试会使用 `retry: 2`,在 happy-dom 中运行,并继承公共 App 配置中的 `@` 别名,无需在项目中调用 `define.app()`。 + +公共配置同样遵循关闭自动继承的规则:合并后的测试配置中存在显式 `extends` 时(包括 `extends: undefined`),会关闭自动继承。内联项目在合并后判断,字符串形式的外部项目仍独立加载。如果没有项目需要自动继承,Rstack 不会为此执行 App 或 Lib 配置函数。 + ## 多项目 \{#multiple-projects} 通过 Rstest 的 [`projects`](https://rstest.rs/zh/config/test/projects) 选项可以同时运行多套测试配置。每一项既可以是内联项目,也可以是由 Rstest 解析为外部项目的字符串。 From 2843c8931cefdcc42f4ee8640e11529af3ca5cfd Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 9 Oct 2026 14:04:17 +0800 Subject: [PATCH 2/6] docs: split and polish shared configuration guide --- website/docs/en/guide/api-reference.mdx | 63 ------------------------- website/docs/en/guide/configuration.mdx | 36 ++++++++------ website/docs/en/guide/testing.mdx | 38 --------------- website/docs/zh/guide/api-reference.mdx | 63 ------------------------- website/docs/zh/guide/configuration.mdx | 36 ++++++++------ website/docs/zh/guide/testing.mdx | 38 --------------- 6 files changed, 42 insertions(+), 232 deletions(-) diff --git a/website/docs/en/guide/api-reference.mdx b/website/docs/en/guide/api-reference.mdx index 1032bc40..4a28006e 100644 --- a/website/docs/en/guide/api-reference.mdx +++ b/website/docs/en/guide/api-reference.mdx @@ -11,7 +11,6 @@ Rstack CLI provides a unified configuration API and re-exports the public APIs o | Import path | Contents | Use case | | ------------------------ | ------------------------------------------------- | --------------------------------------- | | `rstack` | Rstack CLI configuration API | Register tool configurations | -| `rstack/config` | Configuration loader and its types | Load shared and project configurations | | `rstack/app` | Public APIs from `@rsbuild/core` | Build applications and extend Rsbuild | | `rstack/lib` | Public APIs from `@rslib/core` | Build libraries and extend Rslib | | `rstack/test` | Public APIs from `@rstest/core` | Write tests and configure test projects | @@ -26,68 +25,6 @@ Rstack CLI provides a unified configuration API and re-exports the public APIs o Import `define` from `rstack` to register tool configurations in `rstack.config.ts`; see [Configuration APIs](./configuration#configuration-apis) for details. -### `define.extends()` \{#define-extends} - -Registers shared configurations to apply before the project's own tool configurations: - -```ts -import { define } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -define.extends([sharedConfig]); -``` - -- **Type:** `(configs: readonly RstackConfig[]) => void` -- Accepts configuration objects. For a parameterized preset, call the preset function and pass its return value. -- Can be called at most once per configuration load, including calls with an empty array. -- Applies shared configurations from left to right, then project definitions, regardless of call order. - -See [Shared configurations](./configuration#shared-configurations) for nested inheritance, merge rules, and examples. - -### `RstackConfig` - -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. - -```ts -import type { RstackConfig } from 'rstack'; - -export const sharedConfig: RstackConfig = { - fmt: { - singleQuote: true, - }, -}; -``` - -## Loading configurations - -### `loadRstackConfig()` \{#loadrstackconfig} - -Import `loadRstackConfig` from `rstack/config` to load configurations programmatically: - -```ts -import { loadRstackConfig } from 'rstack/config'; - -const { configs, filePath, dependencies } = await loadRstackConfig({ - cwd: process.cwd(), - configFilePath: './rstack.config.ts', -}); -``` - -Both options are optional: - -- `cwd`: the directory used to search for a configuration file and resolve relative configuration file paths. Defaults to the current working directory. -- `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`. - -The returned object contains: - -- `configs`: effective tool definitions, including shared configurations and project definitions. A tool's field is absent if it was never defined. -- `filePath`: the loaded configuration file's path, or `null` if no file was found. -- `dependencies`: configuration dependencies reported by the loader. - -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. - -`rstack/config` also exports the `Configs`, `LoadedRstackConfig`, and `LoadRstackConfigOptions` types. - ## Re-exports 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. diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index 60797985..4703beec 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -76,7 +76,7 @@ define.fmt({ ## Shared configurations -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. +Use `define.extends()` to share build, test, lint, and formatting settings across projects. Define a shared configuration as a plain object with the `RstackConfig` type. Its fields accept the same values as the corresponding `define.*()` APIs: ```ts title="shared.ts" import type { RstackConfig } from 'rstack'; @@ -93,7 +93,7 @@ export const sharedConfig: RstackConfig = { }; ``` -Import the object from a local module or an npm package, then customize the settings your project needs: +Import the shared configuration from a local module or npm package, then add any project-specific settings: ```ts title="rstack.config.ts" import { define } from 'rstack'; @@ -106,16 +106,16 @@ define.fmt({ }); ``` -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. +The project uses the shared test and lint settings, keeps `singleQuote: true`, and changes `printWidth` to `100`. Inherited settings take effect without additional `define.test()` or `define.lint()` calls. -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. +Like the other `define.*()` APIs, `define.extends()` must be called while loading `rstack.config.*`. Call it once with all shared configurations in a single array. Calling it again throws an error, even if the first array was empty. ### Merge order Configurations are applied in this order: 1. Shared configurations in the `define.extends()` array, from left to right. -2. Project configurations registered with `define.*()`, regardless of where those calls appear relative to `define.extends()`. +2. The project's own `define.*()` configurations, even if they appear before `define.extends()` in the file. Later configurations take precedence according to each tool's [merge rules](#merge-rules). @@ -135,7 +135,7 @@ export const teamConfig: RstackConfig = { Inherited configurations are applied before the object that extends them. Here, the order is `sharedConfig → teamConfig → project configuration`. -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. +Shared configurations are applied each time they appear. If both `a` and `b` extend `base`, for example, `define.extends([a, b])` applies `base → a → base → b → project configuration`. Array entries may therefore be added more than once; each tool determines how to handle duplicate plugins. Circular inheritance throws an error with the location of the circular reference. ### Merge rules @@ -151,15 +151,15 @@ Rstack merges configurations separately for each tool: | `doc` | Uses Rspress's `mergeDocConfig`: recursively merges objects, appends arrays, and replaces ordinary functions with later functions. | | `staged` | Shallow merge by glob pattern: later tasks replace earlier tasks for the same pattern, including command arrays and task functions. | -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. +For `doc`, Rspress also handles a mix of array and non-array values: if both are defined, it wraps the non-array value in an array before concatenating them. `builderConfig` follows these same Rspress rules. For example, a later `builderConfig.tools.rspack` callback replaces an earlier one, while callback arrays are concatenated. -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. +For `staged`, a top-level task-generator function replaces all earlier settings. A later configuration also replaces that function in full. Any subsequent glob mappings then merge as usual. For example, with `glob mappings → function → new glob mappings`, only the new mappings remain. Task functions run when lint-staged passes them the list of staged files. ### App and lib presets -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. +A developer infrastructure team can provide separate presets for applications and libraries. Each preset combines its build settings with shared test, lint, and formatting settings. -A preset can be a regular function when consumers need options. Call it to produce an object before passing it to `define.extends()`: +The application preset below extends `sharedConfig` and accepts a page title. To make a preset configurable, export a regular function that returns a `RstackConfig` object: ```ts title="app-preset.ts" import type { RstackConfig } from 'rstack'; @@ -184,6 +184,8 @@ export function appPreset(options: { title: string }): RstackConfig { } ``` +The library preset can use a plain object: + ```ts title="lib-preset.ts" import type { RstackConfig } from 'rstack'; import { sharedConfig } from './shared.ts'; @@ -218,13 +220,15 @@ import { define } from 'rstack'; define.extends([libPreset]); ``` -Make the presets' dependencies available: for this example, the App preset needs `@rsbuild/plugin-react`, and application projects need `happy-dom` for their tests. +Declare `@rsbuild/plugin-react` as a dependency of the application preset, and install `happy-dom` in applications that use it for testing. ### Loading dependencies and resolving paths -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. +Shared configurations follow the same [dependency loading guidance](#loading-dependencies-on-demand) as project configurations. A command evaluates only the tool configuration functions it needs. In the application preset above, `rs lint` and `rs fmt` skip the React plugin import. `rs test` loads it when inheriting the application configuration. + +The outer `appPreset()` function runs as soon as the configuration file loads. Keep tool-specific imports inside the relevant configuration function, such as `app: async () => { ... }`. -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: +Each tool keeps its existing rules for relative paths, so moving a setting into a preset does not change its path base. To reference a file shipped with the preset, resolve an absolute path from `import.meta.url`: ```ts title="shared.ts" import { fileURLToPath } from 'node:url'; @@ -237,7 +241,9 @@ export const sharedConfig: RstackConfig = { }; ``` -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. +Publish `setup.ts` at that location in the package, or update the path to point to its compiled output. + +Local shared modules are also tracked as configuration dependencies. Editing an imported shared module reloads the App, Lib, or Doc configuration in development or watch mode. ## Configuration APIs @@ -323,7 +329,7 @@ When `extends` is omitted, Rstack CLI automatically connects the test configurat 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. -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). +These rules also apply to shared configurations: Rstack first merges the test settings, then uses the merged App or Lib configuration for automatic inheritance. > For more guidance on testing, see [Testing](./testing). diff --git a/website/docs/en/guide/testing.mdx b/website/docs/en/guide/testing.mdx index e4e5b91b..db90404c 100644 --- a/website/docs/en/guide/testing.mdx +++ b/website/docs/en/guide/testing.mdx @@ -107,44 +107,6 @@ define.test({ 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. -### Shared test configurations - -[`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. - -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. - -```ts title="shared.ts" -import type { RstackConfig } from 'rstack'; - -export const sharedConfig: RstackConfig = { - app: { - resolve: { - alias: { - '@': './src', - }, - }, - }, - test: { - retry: 2, - }, -}; -``` - -```ts title="rstack.config.ts" -import { define } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -define.extends([sharedConfig]); - -define.test({ - testEnvironment: 'happy-dom', -}); -``` - -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. - -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. - ## Multiple projects 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. diff --git a/website/docs/zh/guide/api-reference.mdx b/website/docs/zh/guide/api-reference.mdx index 43bd8015..16c373fc 100644 --- a/website/docs/zh/guide/api-reference.mdx +++ b/website/docs/zh/guide/api-reference.mdx @@ -11,7 +11,6 @@ Rstack CLI 提供统一的配置 API,并通过专用子路径重导出 Rsbuild | 导入路径 | 内容 | 使用场景 | | ------------------------ | ----------------------------------------- | ------------------------ | | `rstack` | Rstack CLI 配置 API | 注册各项工具配置 | -| `rstack/config` | 配置加载器及其类型 | 加载公共配置和项目配置 | | `rstack/app` | `@rsbuild/core` 的公开 API | 构建应用及扩展 Rsbuild | | `rstack/lib` | `@rslib/core` 的公开 API | 构建库及扩展 Rslib | | `rstack/test` | `@rstest/core` 的公开 API | 编写测试及配置测试项目 | @@ -26,68 +25,6 @@ Rstack CLI 提供统一的配置 API,并通过专用子路径重导出 Rsbuild 从 `rstack` 导入 `define`,用于在 `rstack.config.ts` 中注册各项工具配置;详细用法请参阅[配置 API](./configuration#configuration-apis)。 -### `define.extends()` \{#define-extends} - -注册公共配置,在项目自身的工具配置之前应用: - -```ts -import { define } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -define.extends([sharedConfig]); -``` - -- **类型:** `(configs: readonly RstackConfig[]) => void` -- 接受配置对象。对于带参数的预设,先调用预设函数,再传入返回值。 -- 每次加载配置时最多调用一次,传入空数组的调用也计入此限制。 -- 先从左到右应用公共配置,再应用项目配置,与调用位置无关。 - -嵌套继承、合并规则和示例请参阅[公共配置](./configuration#shared-configurations)。 - -### `RstackConfig` - -用于编写公共配置的类型。可选字段 `app`、`lib`、`doc`、`test`、`lint`、`fmt` 和 `staged` 接受与对应 `define.*()` API 相同的输入。可选字段 `extends` 接受 `readonly RstackConfig[]`,用于嵌套继承。 - -```ts -import type { RstackConfig } from 'rstack'; - -export const sharedConfig: RstackConfig = { - fmt: { - singleQuote: true, - }, -}; -``` - -## 加载配置 \{#loading-configurations} - -### `loadRstackConfig()` \{#loadrstackconfig} - -从 `rstack/config` 导入 `loadRstackConfig`,以编程方式加载配置: - -```ts -import { loadRstackConfig } from 'rstack/config'; - -const { configs, filePath, dependencies } = await loadRstackConfig({ - cwd: process.cwd(), - configFilePath: './rstack.config.ts', -}); -``` - -两个选项均可省略: - -- `cwd`:用于查找配置文件和解析相对配置文件路径的目录,默认为当前工作目录。 -- `configFilePath`:配置文件的相对或绝对路径。省略时,优先使用 CLI 的 `--config` 路径;如果未设置,则在 `cwd` 中查找[默认配置文件名](./configuration#configuration-file)。 - -返回对象包含: - -- `configs`:包含公共配置和项目配置的有效工具配置定义。未定义过的工具不会出现在该对象中。 -- `filePath`:加载的配置文件路径,未找到文件时为 `null`。 -- `dependencies`:加载器报告的配置依赖。 - -配置函数保持未执行状态。即使输入全部是对象,返回的字段仍可能是异步函数,因为合并会推迟到需要该工具时进行。调用方需要传入相应工具的参数来解析配置定义,并遵循工具自身的配置和执行流程;此 API 不会返回完全解析后的运行时配置。尤其是,仅加载配置不会应用 Rstack 的测试自动继承或格式化配置标准化。Staged 函数仍是接收文件名的任务生成函数。 - -`rstack/config` 还导出 `Configs`、`LoadedRstackConfig` 和 `LoadRstackConfigOptions` 类型。 - ## 重导出 \{#re-exports} 以下工具子路径均会重导出对应 core 包的公开 API。通过这些入口导入,可以统一依赖入口,并确保 API 与 Rstack CLI 集成的工具版本匹配。 diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index be090b75..33f9a06b 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -76,7 +76,7 @@ define.fmt({ ## 公共配置 \{#shared-configurations} -使用 `define.extends()` 在多个项目之间复用配置。公共配置是一个使用 `RstackConfig` 类型的普通对象,可以包含 `app`、`lib`、`doc`、`test`、`lint`、`fmt` 和 `staged`,每个字段接受与对应 `define.*()` API 相同的输入。 +使用 `define.extends()` 可以在多个项目之间共享构建、测试、lint 和格式化配置。公共配置是一个标注了 `RstackConfig` 类型的普通对象,各字段的用法与对应的 `define.*()` API 一致: ```ts title="shared.ts" import type { RstackConfig } from 'rstack'; @@ -93,7 +93,7 @@ export const sharedConfig: RstackConfig = { }; ``` -从本地模块或 npm 包导入公共配置对象,再按项目需求调整配置: +从本地模块或 npm 包导入公共配置,再添加项目自己的配置: ```ts title="rstack.config.ts" import { define } from 'rstack'; @@ -106,16 +106,16 @@ define.fmt({ }); ``` -这个项目会继承测试和 lint 配置,并使用 `singleQuote: true` 和 `printWidth: 100` 进行格式化。无需额外调用 `define.test()` 或 `define.lint()`,继承的配置就会生效。 +这个项目会使用公共配置中的测试和 lint 设置,保留 `singleQuote: true`,并将 `printWidth` 改为 `100`。无需额外调用 `define.test()` 或 `define.lint()`,继承的配置就会生效。 -每次加载配置时,`define.extends()` 最多调用一次,所有公共配置应放在同一个数组中。即使第一次传入空数组,第二次调用也会抛出错误。与其他 `define.*()` API 一样,请在加载 `rstack.config.*` 的过程中调用它。 +与其他 `define.*()` API 一样,`define.extends()` 需要在加载 `rstack.config.*` 时调用。请将所有公共配置放在同一个数组中,一次传入。重复调用会抛出错误,即使第一次传入的是空数组。 ### 合并顺序 \{#merge-order} 配置按以下顺序应用: 1. `define.extends()` 数组中的公共配置,从左到右。 -2. 通过 `define.*()` 注册的项目配置,与这些调用在 `define.extends()` 前后的位置无关。 +2. 项目自己的 `define.*()` 配置,即使这些调用写在 `define.extends()` 之前。 后面的配置按照各工具的[合并规则](#merge-rules)覆盖前面的配置。 @@ -135,7 +135,7 @@ export const teamConfig: RstackConfig = { 先应用被继承的配置,再应用当前对象。上例的顺序为 `sharedConfig → teamConfig → 项目配置`。 -公共配置每出现一次就应用一次。例如,`a` 和 `b` 都继承 `base` 时,`define.extends([a, b])` 的顺序为 `base → a → base → b → 项目配置`。这可能导致数组被重复追加,插件是否去重由底层工具决定。循环继承会抛出错误,并指出引用位置。 +公共配置每出现一次就应用一次。例如,`a` 和 `b` 都继承 `base` 时,`define.extends([a, b])` 的顺序为 `base → a → base → b → 项目配置`。因此,数组中的内容可能被多次添加,重复的插件则由各工具自行处理。循环继承会抛出错误,并指出循环引用的位置。 ### 合并规则 \{#merge-rules} @@ -151,15 +151,15 @@ Rstack 分别合并各个工具的配置: | `doc` | 使用 Rspress 的 `mergeDocConfig`:递归合并对象、追加数组,普通函数由后面的函数覆盖。 | | `staged` | 按 glob 模式浅合并,同名模式的任务整体替换,包括命令数组和任务函数。 | -对于 `doc`,前后值均已定义且任一值为数组时,Rspress 会先将另一个值视为单项数组,再拼接。`builderConfig` 同样遵循这些规则,Rstack 不会额外调用 `mergeRsbuildConfig`。例如,后面的 `builderConfig.tools.rspack` 回调会覆盖前面的回调,而回调数组则按顺序拼接。 +对于 `doc`,如果同一字段的两个值都已定义,且只有一个是数组,Rspress 会先将另一个值包装为数组,再拼接。`builderConfig` 也遵循这套 Rspress 合并规则。例如,后面的 `builderConfig.tools.rspack` 回调会覆盖前面的回调,回调数组则按顺序拼接。 -对于 `staged`,如果当前合并结果或下一份配置是顶层任务生成函数,下一份配置就会整体替换当前结果。后续 glob 映射从替换后的结果继续合并,不会恢复更早的任务。任务函数由 lint-staged 在获得暂存文件列表后调用,不会在配置合并期间执行。 +对于 `staged`,顶层任务生成函数会整体替换此前的配置,而它之后的配置也会整体替换这个函数。再往后的 glob 映射则按通常的规则合并。例如,按「glob 映射 → 函数 → 新的 glob 映射」应用时,最终只保留新的映射。任务函数由 lint-staged 传入暂存文件列表后执行。 ### App 和 lib 预设 \{#app-and-lib-presets} -开发者基础设施团队可以维护两套预设:一套涵盖 App + Test + Lint + Format,另一套涵盖 Lib + Test + Lint + Format。两者都可以继承上面的 `sharedConfig`。 +业务基建团队可以分别为应用和库提供一套预设,将各自的构建配置与公共的测试、lint 和格式化配置组合起来。 -如果使用者需要传入选项,可以将预设设计为普通函数。调用它得到配置对象后,再传给 `define.extends()`: +下面的应用预设继承了 `sharedConfig`,并允许使用者传入页面标题。需要支持参数时,导出一个返回 `RstackConfig` 对象的普通函数即可: ```ts title="app-preset.ts" import type { RstackConfig } from 'rstack'; @@ -184,6 +184,8 @@ export function appPreset(options: { title: string }): RstackConfig { } ``` +库预设可以直接使用普通对象: + ```ts title="lib-preset.ts" import type { RstackConfig } from 'rstack'; import { sharedConfig } from './shared.ts'; @@ -218,13 +220,15 @@ import { define } from 'rstack'; define.extends([libPreset]); ``` -请确保预设依赖的包可用:在本例中,App 预设需要 `@rsbuild/plugin-react`,应用项目需要安装 `happy-dom` 来运行测试。 +请将 `@rsbuild/plugin-react` 声明为应用预设的依赖,并在使用 happy-dom 运行测试的应用项目中安装 `happy-dom`。 ### 依赖加载与路径解析 \{#loading-dependencies-and-resolving-paths} -公共配置与项目配置遵循相同的[依赖加载建议](#loading-dependencies-on-demand),仅执行当前命令需要的工具配置函数。在上面的 App 预设中,`rs lint` 和 `rs fmt` 不会加载 React 插件;`rs test` 在继承 App 配置时可能会加载它。外层的 `appPreset()` 函数会在加载配置文件时执行,因此请将工具专用依赖的导入放在对应的配置函数内。 +公共配置与项目配置遵循相同的[依赖加载建议](#loading-dependencies-on-demand),每个命令只执行它需要的工具配置函数。在上面的应用预设中,`rs lint` 和 `rs fmt` 会跳过 React 插件的导入,`rs test` 则会在继承应用配置时加载它。 + +外层的 `appPreset()` 函数在加载配置文件时就会执行。因此,请将工具专用依赖的导入放在对应的配置函数内,例如 `app: async () => { ... }`。 -相对路径遵循各工具现有的解析规则,不会自动以公共配置模块所在目录为基准。对于随预设发布的文件,请使用相对于该模块生成的绝对路径: +各工具继续沿用原有的相对路径规则,将配置移入预设不会改变路径的解析基准。如果要引用随预设发布的文件,请通过 `import.meta.url` 获取绝对路径: ```ts title="shared.ts" import { fileURLToPath } from 'node:url'; @@ -237,7 +241,9 @@ export const sharedConfig: RstackConfig = { }; ``` -请将 `setup.ts` 发布到包中的对应位置,或将路径调整为构建后的文件。现有的本地配置依赖追踪也适用于公共模块:修改导入的本地公共配置时,App、Lib 和 Doc 会在开发或监听模式下重新加载配置。 +请将 `setup.ts` 发布到包中的对应位置,或将路径改为它的编译产物。 + +本地公共模块也会被追踪为配置依赖。在开发或监听模式下,修改导入的公共模块会重新加载对应的 App、Lib 或 Doc 配置。 ## 配置 API \{#configuration-apis} @@ -323,7 +329,7 @@ define.test({ 如果测试根配置未定义 `extends` 且包含 `projects`,Rstack CLI 会为每个未自行设置 `extends` 的内联项目应用自动继承。函数形式的应用或库配置只会解析一次,并由这些项目共享。字符串形式的项目会原样传给 Rstest;它们会独立加载外部配置,不继承当前应用或库的配置。 -使用公共配置时,以上规则在合并所有测试配置后应用,并使用合并后的 App 或 Lib 配置。详见[公共测试配置](./testing#shared-test-configurations)。 +这些规则同样适用于公共配置:Rstack 先合并测试配置,再使用合并后的 App 或 Lib 配置进行自动继承。 > 如需了解更多测试相关用法,请参阅[测试](./testing)。 diff --git a/website/docs/zh/guide/testing.mdx b/website/docs/zh/guide/testing.mdx index 9992d054..43474a71 100644 --- a/website/docs/zh/guide/testing.mdx +++ b/website/docs/zh/guide/testing.mdx @@ -107,44 +107,6 @@ define.test({ 使用多项目配置时,在 `define.test()` 根配置中设置 `extends` 会关闭所有项目的自动继承;在某个内联项目中设置 `extends` 则只会关闭该项目的自动继承。 -### 公共测试配置 \{#shared-test-configurations} - -[`define.extends()`](./configuration#shared-configurations) 用于跨工具共享配置,而测试配置内的 `extends` 字段是 Rstest 自身的继承选项,两者作用于不同层级。 - -Rstack 先使用 `mergeRstestConfig` 合并所有公共配置和项目中的测试配置,再根据结果中的 `extends` 和 `projects` 判断是否需要自动继承。如果需要,则使用合并后的 App 配置;未定义任何 App 配置时,使用合并后的 Lib 配置。 - -```ts title="shared.ts" -import type { RstackConfig } from 'rstack'; - -export const sharedConfig: RstackConfig = { - app: { - resolve: { - alias: { - '@': './src', - }, - }, - }, - test: { - retry: 2, - }, -}; -``` - -```ts title="rstack.config.ts" -import { define } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -define.extends([sharedConfig]); - -define.test({ - testEnvironment: 'happy-dom', -}); -``` - -测试会使用 `retry: 2`,在 happy-dom 中运行,并继承公共 App 配置中的 `@` 别名,无需在项目中调用 `define.app()`。 - -公共配置同样遵循关闭自动继承的规则:合并后的测试配置中存在显式 `extends` 时(包括 `extends: undefined`),会关闭自动继承。内联项目在合并后判断,字符串形式的外部项目仍独立加载。如果没有项目需要自动继承,Rstack 不会为此执行 App 或 Lib 配置函数。 - ## 多项目 \{#multiple-projects} 通过 Rstest 的 [`projects`](https://rstest.rs/zh/config/test/projects) 选项可以同时运行多套测试配置。每一项既可以是内联项目,也可以是由 Rstest 解析为外部项目的字符串。 From 17e99aea960c9d7294774576efe6b6d84f428515 Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 9 Oct 2026 14:11:43 +0800 Subject: [PATCH 3/6] docs: move shared configurations after configuration APIs --- website/docs/en/guide/configuration.mdx | 262 ++++++++++++------------ website/docs/zh/guide/configuration.mdx | 262 ++++++++++++------------ 2 files changed, 262 insertions(+), 262 deletions(-) diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index 4703beec..60b99b64 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -74,6 +74,137 @@ define.fmt({ }); ``` +## Configuration APIs + +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. + +| API | Tool | Commands | +| ----------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| [`define.app()`](#define-app) | [Rsbuild](https://rsbuild.rs/config/) | [`rs dev`](./cli/dev), [`rs build`](./cli/build), [`rs preview`](./cli/preview) | +| [`define.lib()`](#define-lib) | [Rslib](https://rslib.rs/config/) | [`rs lib`](./cli/lib) | +| [`define.doc()`](#define-doc) | [Rspress](https://rspress.rs/api/config/config-basic) | [`rs doc`](./cli/doc) | +| [`define.test()`](#define-test) | [Rstest](https://rstest.rs/config/) | [`rs test`](./cli/test) | +| [`define.lint()`](#define-lint) | [Rslint](https://rslint.rs/config/) | [`rs lint`](./cli/lint) | +| [`define.fmt()`](#define-fmt) | [Prettier](https://prettier.io/docs/options) | [`rs fmt`](./cli/fmt) | +| [`define.staged()`](#define-staged) | [lint-staged](https://github.com/lint-staged/lint-staged#configuration) | [`rs staged`](./cli/staged) | + +### `define.app()` \{#define-app} + +Defines the [Rsbuild configuration](https://rsbuild.rs/config/) for an application. It accepts a configuration object or a configuration function. The function receives the standard Rsbuild configuration parameters. + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.app({ + html: { + title: 'My App', + }, + output: { + distPath: { + root: 'dist', + }, + }, +}); +``` + +### `define.lib()` \{#define-lib} + +Defines the [Rslib configuration](https://rslib.rs/config/) for a library. It accepts a configuration object or a configuration function. The function receives the standard Rslib configuration parameters. + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.lib({ + dts: true, + format: 'esm', +}); +``` + +### `define.doc()` \{#define-doc} + +Defines the [Rspress configuration](https://rspress.rs/api/config/config-basic) for a documentation site. It accepts a configuration object or an async configuration function. + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.doc({ + root: 'docs', + title: 'My Site', +}); +``` + +`@rspress/core` is an optional dependency of Rstack CLI. Install it in every project that uses the `rs doc` command: + + + +### `define.test()` \{#define-test} + +Defines the [Rstest configuration](https://rstest.rs/config/). It accepts a configuration object or a configuration function. + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.app({ + // Shared application configuration +}); + +define.test({ + setupFiles: ['./tests/rstest.setup.ts'], + testEnvironment: 'happy-dom', +}); +``` + +When `extends` is omitted, Rstack CLI automatically connects the test configuration to `define.app()` through the Rsbuild adapter. If no application configuration is defined, it falls back to `define.lib()` through the Rslib adapter. The application configuration takes precedence when both are defined. Set `extends` explicitly to opt out of this automatic inheritance. + +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. + +These rules also apply to shared configurations: Rstack first merges the test settings, then uses the merged App or Lib configuration for automatic inheritance. + +> For more guidance on testing, see [Testing](./testing). + +### `define.lint()` \{#define-lint} + +Defines the [Rslint configuration](https://rslint.rs/config/). Pass the configuration directly, or use a synchronous or asynchronous function. The function receives all exports from `rstack/lint`, so presets and plugins do not need to be imported manually. + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); +``` + +### `define.fmt()` \{#define-fmt} + +Defines formatting settings for [`rs fmt`](./cli/fmt). Pass a configuration object directly, or use a synchronous or asynchronous function that returns one. + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.fmt({ + printWidth: 100, + singleQuote: true, +}); +``` + +For detailed usage, see [Formatting](./formatting). + +### `define.staged()` \{#define-staged} + +Defines the [lint-staged configuration](https://github.com/lint-staged/lint-staged#configuration) used to run tasks on staged Git files. It accepts either an object that maps glob patterns to tasks or a task-generator function. Tasks can be commands, command arrays, or functions supported by lint-staged. + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.staged({ + '*.{js,jsx,ts,tsx}': ['rs lint', 'rs fmt'], + '*.{json,jsonc,md,mdx,css,html,yml,yaml}': 'rs fmt', +}); +``` + +`rs staged` requires a staged configuration, provided by `define.staged()` or a shared configuration, and reports an error when it is missing. + ## Shared configurations Use `define.extends()` to share build, test, lint, and formatting settings across projects. Define a shared configuration as a plain object with the `RstackConfig` type. Its fields accept the same values as the corresponding `define.*()` APIs: @@ -244,134 +375,3 @@ export const sharedConfig: RstackConfig = { Publish `setup.ts` at that location in the package, or update the path to point to its compiled output. Local shared modules are also tracked as configuration dependencies. Editing an imported shared module reloads the App, Lib, or Doc configuration in development or watch mode. - -## Configuration APIs - -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. - -| API | Tool | Commands | -| ----------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| [`define.app()`](#define-app) | [Rsbuild](https://rsbuild.rs/config/) | [`rs dev`](./cli/dev), [`rs build`](./cli/build), [`rs preview`](./cli/preview) | -| [`define.lib()`](#define-lib) | [Rslib](https://rslib.rs/config/) | [`rs lib`](./cli/lib) | -| [`define.doc()`](#define-doc) | [Rspress](https://rspress.rs/api/config/config-basic) | [`rs doc`](./cli/doc) | -| [`define.test()`](#define-test) | [Rstest](https://rstest.rs/config/) | [`rs test`](./cli/test) | -| [`define.lint()`](#define-lint) | [Rslint](https://rslint.rs/config/) | [`rs lint`](./cli/lint) | -| [`define.fmt()`](#define-fmt) | [Prettier](https://prettier.io/docs/options) | [`rs fmt`](./cli/fmt) | -| [`define.staged()`](#define-staged) | [lint-staged](https://github.com/lint-staged/lint-staged#configuration) | [`rs staged`](./cli/staged) | - -### `define.app()` \{#define-app} - -Defines the [Rsbuild configuration](https://rsbuild.rs/config/) for an application. It accepts a configuration object or a configuration function. The function receives the standard Rsbuild configuration parameters. - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.app({ - html: { - title: 'My App', - }, - output: { - distPath: { - root: 'dist', - }, - }, -}); -``` - -### `define.lib()` \{#define-lib} - -Defines the [Rslib configuration](https://rslib.rs/config/) for a library. It accepts a configuration object or a configuration function. The function receives the standard Rslib configuration parameters. - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.lib({ - dts: true, - format: 'esm', -}); -``` - -### `define.doc()` \{#define-doc} - -Defines the [Rspress configuration](https://rspress.rs/api/config/config-basic) for a documentation site. It accepts a configuration object or an async configuration function. - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.doc({ - root: 'docs', - title: 'My Site', -}); -``` - -`@rspress/core` is an optional dependency of Rstack CLI. Install it in every project that uses the `rs doc` command: - - - -### `define.test()` \{#define-test} - -Defines the [Rstest configuration](https://rstest.rs/config/). It accepts a configuration object or a configuration function. - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.app({ - // Shared application configuration -}); - -define.test({ - setupFiles: ['./tests/rstest.setup.ts'], - testEnvironment: 'happy-dom', -}); -``` - -When `extends` is omitted, Rstack CLI automatically connects the test configuration to `define.app()` through the Rsbuild adapter. If no application configuration is defined, it falls back to `define.lib()` through the Rslib adapter. The application configuration takes precedence when both are defined. Set `extends` explicitly to opt out of this automatic inheritance. - -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. - -These rules also apply to shared configurations: Rstack first merges the test settings, then uses the merged App or Lib configuration for automatic inheritance. - -> For more guidance on testing, see [Testing](./testing). - -### `define.lint()` \{#define-lint} - -Defines the [Rslint configuration](https://rslint.rs/config/). Pass the configuration directly, or use a synchronous or asynchronous function. The function receives all exports from `rstack/lint`, so presets and plugins do not need to be imported manually. - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.lint(({ js, ts }) => [ - js.configs.recommended, - ts.configs.recommendedTypeChecked, -]); -``` - -### `define.fmt()` \{#define-fmt} - -Defines formatting settings for [`rs fmt`](./cli/fmt). Pass a configuration object directly, or use a synchronous or asynchronous function that returns one. - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.fmt({ - printWidth: 100, - singleQuote: true, -}); -``` - -For detailed usage, see [Formatting](./formatting). - -### `define.staged()` \{#define-staged} - -Defines the [lint-staged configuration](https://github.com/lint-staged/lint-staged#configuration) used to run tasks on staged Git files. It accepts either an object that maps glob patterns to tasks or a task-generator function. Tasks can be commands, command arrays, or functions supported by lint-staged. - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.staged({ - '*.{js,jsx,ts,tsx}': ['rs lint', 'rs fmt'], - '*.{json,jsonc,md,mdx,css,html,yml,yaml}': 'rs fmt', -}); -``` - -`rs staged` requires a staged configuration, provided by `define.staged()` or a shared configuration, and reports an error when it is missing. diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index 33f9a06b..92e269dc 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -74,6 +74,137 @@ define.fmt({ }); ``` +## 配置 API \{#configuration-apis} + +各 API 沿用底层工具的配置格式。使用 Rstack CLI 已重导出的 API 和辅助函数时,推荐从 `rstack/app`、`rstack/lib`、`rstack/test` 和 `rstack/lint` 入口导入。 + +| API | 底层工具 | 对应命令 | +| ----------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| [`define.app()`](#define-app) | [Rsbuild](https://rsbuild.rs/zh/config/) | [`rs dev`](./cli/dev)、[`rs build`](./cli/build)、[`rs preview`](./cli/preview) | +| [`define.lib()`](#define-lib) | [Rslib](https://rslib.rs/zh/config/) | [`rs lib`](./cli/lib) | +| [`define.doc()`](#define-doc) | [Rspress](https://rspress.rs/zh/api/config/config-basic) | [`rs doc`](./cli/doc) | +| [`define.test()`](#define-test) | [Rstest](https://rstest.rs/zh/config/) | [`rs test`](./cli/test) | +| [`define.lint()`](#define-lint) | [Rslint](https://rslint.rs/config/) | [`rs lint`](./cli/lint) | +| [`define.fmt()`](#define-fmt) | [Prettier](https://prettier.io/docs/options) | [`rs fmt`](./cli/fmt) | +| [`define.staged()`](#define-staged) | [lint-staged](https://github.com/lint-staged/lint-staged#configuration) | [`rs staged`](./cli/staged) | + +### `define.app()` \{#define-app} + +定义应用的 [Rsbuild 配置](https://rsbuild.rs/zh/config/),支持传入配置对象或配置函数。配置函数接收 Rsbuild 的标准配置参数。 + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.app({ + html: { + title: 'My App', + }, + output: { + distPath: { + root: 'dist', + }, + }, +}); +``` + +### `define.lib()` \{#define-lib} + +定义库的 [Rslib 配置](https://rslib.rs/zh/config/),支持传入配置对象或配置函数。配置函数接收 Rslib 的标准配置参数。 + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.lib({ + dts: true, + format: 'esm', +}); +``` + +### `define.doc()` \{#define-doc} + +定义文档站点的 [Rspress 配置](https://rspress.rs/zh/api/config/config-basic),支持传入配置对象或异步配置函数。 + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.doc({ + root: 'docs', + title: 'My Site', +}); +``` + +`@rspress/core` 是 Rstack CLI 的可选依赖。每个使用 `rs doc` 命令的项目都需要安装该依赖: + + + +### `define.test()` \{#define-test} + +定义 [Rstest 配置](https://rstest.rs/zh/config/),支持传入配置对象或配置函数。 + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.app({ + // 共享的应用配置 +}); + +define.test({ + setupFiles: ['./tests/rstest.setup.ts'], + testEnvironment: 'happy-dom', +}); +``` + +未设置 `extends` 时,Rstack CLI 会通过 Rsbuild 适配器让测试配置自动继承 `define.app()`;如果未定义应用配置,则通过 Rslib 适配器回退到 `define.lib()`。二者同时存在时,应用配置的优先级更高。显式设置 `extends` 可关闭自动继承。 + +如果测试根配置未定义 `extends` 且包含 `projects`,Rstack CLI 会为每个未自行设置 `extends` 的内联项目应用自动继承。函数形式的应用或库配置只会解析一次,并由这些项目共享。字符串形式的项目会原样传给 Rstest;它们会独立加载外部配置,不继承当前应用或库的配置。 + +这些规则同样适用于公共配置:Rstack 先合并测试配置,再使用合并后的 App 或 Lib 配置进行自动继承。 + +> 如需了解更多测试相关用法,请参阅[测试](./testing)。 + +### `define.lint()` \{#define-lint} + +定义 [Rslint 配置](https://rslint.rs/config/)。可以直接传入配置,也可以传入同步或异步函数。函数会接收 `rstack/lint` 的全部导出,因此无需手动导入预设和插件。 + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.lint(({ js, ts }) => [ + js.configs.recommended, + ts.configs.recommendedTypeChecked, +]); +``` + +### `define.fmt()` \{#define-fmt} + +定义 [`rs fmt`](./cli/fmt) 的格式化配置。可以直接传入配置对象,也可以传入返回配置对象的同步或异步函数。 + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.fmt({ + printWidth: 100, + singleQuote: true, +}); +``` + +详细用法请参考[格式化](./formatting)指南。 + +### `define.staged()` \{#define-staged} + +定义用于处理 Git 暂存文件的 [lint-staged 配置](https://github.com/lint-staged/lint-staged#configuration)。支持传入从 glob 匹配模式映射到任务的配置对象,也支持传入任务生成函数。任务可以是 lint-staged 支持的命令、命令数组或函数。 + +```ts title="rstack.config.ts" +import { define } from 'rstack'; + +define.staged({ + '*.{js,jsx,ts,tsx}': ['rs lint', 'rs fmt'], + '*.{json,jsonc,md,mdx,css,html,yml,yaml}': 'rs fmt', +}); +``` + +`rs staged` 必须通过 `define.staged()` 或公共配置提供 staged 配置,缺少配置时会报错。 + ## 公共配置 \{#shared-configurations} 使用 `define.extends()` 可以在多个项目之间共享构建、测试、lint 和格式化配置。公共配置是一个标注了 `RstackConfig` 类型的普通对象,各字段的用法与对应的 `define.*()` API 一致: @@ -244,134 +375,3 @@ export const sharedConfig: RstackConfig = { 请将 `setup.ts` 发布到包中的对应位置,或将路径改为它的编译产物。 本地公共模块也会被追踪为配置依赖。在开发或监听模式下,修改导入的公共模块会重新加载对应的 App、Lib 或 Doc 配置。 - -## 配置 API \{#configuration-apis} - -各 API 沿用底层工具的配置格式。使用 Rstack CLI 已重导出的 API 和辅助函数时,推荐从 `rstack/app`、`rstack/lib`、`rstack/test` 和 `rstack/lint` 入口导入。 - -| API | 底层工具 | 对应命令 | -| ----------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| [`define.app()`](#define-app) | [Rsbuild](https://rsbuild.rs/zh/config/) | [`rs dev`](./cli/dev)、[`rs build`](./cli/build)、[`rs preview`](./cli/preview) | -| [`define.lib()`](#define-lib) | [Rslib](https://rslib.rs/zh/config/) | [`rs lib`](./cli/lib) | -| [`define.doc()`](#define-doc) | [Rspress](https://rspress.rs/zh/api/config/config-basic) | [`rs doc`](./cli/doc) | -| [`define.test()`](#define-test) | [Rstest](https://rstest.rs/zh/config/) | [`rs test`](./cli/test) | -| [`define.lint()`](#define-lint) | [Rslint](https://rslint.rs/config/) | [`rs lint`](./cli/lint) | -| [`define.fmt()`](#define-fmt) | [Prettier](https://prettier.io/docs/options) | [`rs fmt`](./cli/fmt) | -| [`define.staged()`](#define-staged) | [lint-staged](https://github.com/lint-staged/lint-staged#configuration) | [`rs staged`](./cli/staged) | - -### `define.app()` \{#define-app} - -定义应用的 [Rsbuild 配置](https://rsbuild.rs/zh/config/),支持传入配置对象或配置函数。配置函数接收 Rsbuild 的标准配置参数。 - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.app({ - html: { - title: 'My App', - }, - output: { - distPath: { - root: 'dist', - }, - }, -}); -``` - -### `define.lib()` \{#define-lib} - -定义库的 [Rslib 配置](https://rslib.rs/zh/config/),支持传入配置对象或配置函数。配置函数接收 Rslib 的标准配置参数。 - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.lib({ - dts: true, - format: 'esm', -}); -``` - -### `define.doc()` \{#define-doc} - -定义文档站点的 [Rspress 配置](https://rspress.rs/zh/api/config/config-basic),支持传入配置对象或异步配置函数。 - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.doc({ - root: 'docs', - title: 'My Site', -}); -``` - -`@rspress/core` 是 Rstack CLI 的可选依赖。每个使用 `rs doc` 命令的项目都需要安装该依赖: - - - -### `define.test()` \{#define-test} - -定义 [Rstest 配置](https://rstest.rs/zh/config/),支持传入配置对象或配置函数。 - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.app({ - // 共享的应用配置 -}); - -define.test({ - setupFiles: ['./tests/rstest.setup.ts'], - testEnvironment: 'happy-dom', -}); -``` - -未设置 `extends` 时,Rstack CLI 会通过 Rsbuild 适配器让测试配置自动继承 `define.app()`;如果未定义应用配置,则通过 Rslib 适配器回退到 `define.lib()`。二者同时存在时,应用配置的优先级更高。显式设置 `extends` 可关闭自动继承。 - -如果测试根配置未定义 `extends` 且包含 `projects`,Rstack CLI 会为每个未自行设置 `extends` 的内联项目应用自动继承。函数形式的应用或库配置只会解析一次,并由这些项目共享。字符串形式的项目会原样传给 Rstest;它们会独立加载外部配置,不继承当前应用或库的配置。 - -这些规则同样适用于公共配置:Rstack 先合并测试配置,再使用合并后的 App 或 Lib 配置进行自动继承。 - -> 如需了解更多测试相关用法,请参阅[测试](./testing)。 - -### `define.lint()` \{#define-lint} - -定义 [Rslint 配置](https://rslint.rs/config/)。可以直接传入配置,也可以传入同步或异步函数。函数会接收 `rstack/lint` 的全部导出,因此无需手动导入预设和插件。 - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.lint(({ js, ts }) => [ - js.configs.recommended, - ts.configs.recommendedTypeChecked, -]); -``` - -### `define.fmt()` \{#define-fmt} - -定义 [`rs fmt`](./cli/fmt) 的格式化配置。可以直接传入配置对象,也可以传入返回配置对象的同步或异步函数。 - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.fmt({ - printWidth: 100, - singleQuote: true, -}); -``` - -详细用法请参考[格式化](./formatting)指南。 - -### `define.staged()` \{#define-staged} - -定义用于处理 Git 暂存文件的 [lint-staged 配置](https://github.com/lint-staged/lint-staged#configuration)。支持传入从 glob 匹配模式映射到任务的配置对象,也支持传入任务生成函数。任务可以是 lint-staged 支持的命令、命令数组或函数。 - -```ts title="rstack.config.ts" -import { define } from 'rstack'; - -define.staged({ - '*.{js,jsx,ts,tsx}': ['rs lint', 'rs fmt'], - '*.{json,jsonc,md,mdx,css,html,yml,yaml}': 'rs fmt', -}); -``` - -`rs staged` 必须通过 `define.staged()` 或公共配置提供 staged 配置,缺少配置时会报错。 From 6a16e6bb36b0d1ed7658367670905fb6a8b99e0e Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 9 Oct 2026 14:21:22 +0800 Subject: [PATCH 4/6] docs: simplify shared configuration guide --- website/docs/en/guide/configuration.mdx | 85 ++----------------------- website/docs/zh/guide/configuration.mdx | 85 ++----------------------- 2 files changed, 14 insertions(+), 156 deletions(-) diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index 60b99b64..a4836ac9 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -158,7 +158,7 @@ When `extends` is omitted, Rstack CLI automatically connects the test configurat 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. -These rules also apply to shared configurations: Rstack first merges the test settings, then uses the merged App or Lib configuration for automatic inheritance. +These rules also apply to [shared configurations](#shared-configurations): Rstack first merges the test settings, then uses the merged App or Lib configuration for automatic inheritance. > For more guidance on testing, see [Testing](./testing). @@ -237,9 +237,9 @@ define.fmt({ }); ``` -The project uses the shared test and lint settings, keeps `singleQuote: true`, and changes `printWidth` to `100`. Inherited settings take effect without additional `define.test()` or `define.lint()` calls. +The project inherits the shared settings and changes only `printWidth` to `100`. -Like the other `define.*()` APIs, `define.extends()` must be called while loading `rstack.config.*`. Call it once with all shared configurations in a single array. Calling it again throws an error, even if the first array was empty. +Call `define.extends()` at most once per configuration load, with all shared configurations in a single array. ### Merge order @@ -286,80 +286,11 @@ For `doc`, Rspress also handles a mix of array and non-array values: if both are For `staged`, a top-level task-generator function replaces all earlier settings. A later configuration also replaces that function in full. Any subsequent glob mappings then merge as usual. For example, with `glob mappings → function → new glob mappings`, only the new mappings remain. Task functions run when lint-staged passes them the list of staged files. -### App and lib presets +### Dependencies and paths -A developer infrastructure team can provide separate presets for applications and libraries. Each preset combines its build settings with shared test, lint, and formatting settings. +Shared configurations can also [import dependencies dynamically](#loading-dependencies-on-demand) inside tool configuration functions, so each command loads only what it needs. -The application preset below extends `sharedConfig` and accepts a page title. To make a preset configurable, export a regular function that returns a `RstackConfig` object: - -```ts title="app-preset.ts" -import type { RstackConfig } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -export function appPreset(options: { title: string }): RstackConfig { - return { - extends: [sharedConfig], - app: async () => { - const { pluginReact } = await import('@rsbuild/plugin-react'); - return { - plugins: [pluginReact()], - html: { - title: options.title, - }, - }; - }, - test: { - testEnvironment: 'happy-dom', - }, - }; -} -``` - -The library preset can use a plain object: - -```ts title="lib-preset.ts" -import type { RstackConfig } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -export const libPreset: RstackConfig = { - extends: [sharedConfig], - lib: { - format: 'esm', - dts: true, - }, - test: { - testEnvironment: 'node', - }, -}; -``` - -After publishing these presets as `@biz/app-preset` and `@biz/lib-preset`, an application can use: - -```ts title="rstack.config.ts (application)" -import { appPreset } from '@biz/app-preset'; -import { define } from 'rstack'; - -define.extends([appPreset({ title: 'My App' })]); -``` - -A library can use: - -```ts title="rstack.config.ts (library)" -import { libPreset } from '@biz/lib-preset'; -import { define } from 'rstack'; - -define.extends([libPreset]); -``` - -Declare `@rsbuild/plugin-react` as a dependency of the application preset, and install `happy-dom` in applications that use it for testing. - -### Loading dependencies and resolving paths - -Shared configurations follow the same [dependency loading guidance](#loading-dependencies-on-demand) as project configurations. A command evaluates only the tool configuration functions it needs. In the application preset above, `rs lint` and `rs fmt` skip the React plugin import. `rs test` loads it when inheriting the application configuration. - -The outer `appPreset()` function runs as soon as the configuration file loads. Keep tool-specific imports inside the relevant configuration function, such as `app: async () => { ... }`. - -Each tool keeps its existing rules for relative paths, so moving a setting into a preset does not change its path base. To reference a file shipped with the preset, resolve an absolute path from `import.meta.url`: +Relative paths follow each tool's existing rules. Use an absolute path to reference a file shipped with a shared configuration: ```ts title="shared.ts" import { fileURLToPath } from 'node:url'; @@ -372,6 +303,4 @@ export const sharedConfig: RstackConfig = { }; ``` -Publish `setup.ts` at that location in the package, or update the path to point to its compiled output. - -Local shared modules are also tracked as configuration dependencies. Editing an imported shared module reloads the App, Lib, or Doc configuration in development or watch mode. +Include the file when publishing the shared configuration package, or point the path to its compiled output. diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index 92e269dc..78066dba 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -158,7 +158,7 @@ define.test({ 如果测试根配置未定义 `extends` 且包含 `projects`,Rstack CLI 会为每个未自行设置 `extends` 的内联项目应用自动继承。函数形式的应用或库配置只会解析一次,并由这些项目共享。字符串形式的项目会原样传给 Rstest;它们会独立加载外部配置,不继承当前应用或库的配置。 -这些规则同样适用于公共配置:Rstack 先合并测试配置,再使用合并后的 App 或 Lib 配置进行自动继承。 +这些规则同样适用于[公共配置](#shared-configurations):Rstack 先合并测试配置,再使用合并后的 App 或 Lib 配置进行自动继承。 > 如需了解更多测试相关用法,请参阅[测试](./testing)。 @@ -237,9 +237,9 @@ define.fmt({ }); ``` -这个项目会使用公共配置中的测试和 lint 设置,保留 `singleQuote: true`,并将 `printWidth` 改为 `100`。无需额外调用 `define.test()` 或 `define.lint()`,继承的配置就会生效。 +项目会继承公共配置,仅将 `printWidth` 改为 `100`。 -与其他 `define.*()` API 一样,`define.extends()` 需要在加载 `rstack.config.*` 时调用。请将所有公共配置放在同一个数组中,一次传入。重复调用会抛出错误,即使第一次传入的是空数组。 +每次加载配置时,`define.extends()` 最多调用一次,多个公共配置请放在同一个数组中。 ### 合并顺序 \{#merge-order} @@ -286,80 +286,11 @@ Rstack 分别合并各个工具的配置: 对于 `staged`,顶层任务生成函数会整体替换此前的配置,而它之后的配置也会整体替换这个函数。再往后的 glob 映射则按通常的规则合并。例如,按「glob 映射 → 函数 → 新的 glob 映射」应用时,最终只保留新的映射。任务函数由 lint-staged 传入暂存文件列表后执行。 -### App 和 lib 预设 \{#app-and-lib-presets} +### 依赖与路径 \{#dependencies-and-paths} -业务基建团队可以分别为应用和库提供一套预设,将各自的构建配置与公共的测试、lint 和格式化配置组合起来。 +公共配置也可以在工具配置函数中[动态导入依赖](#loading-dependencies-on-demand),让各命令只加载需要的依赖。 -下面的应用预设继承了 `sharedConfig`,并允许使用者传入页面标题。需要支持参数时,导出一个返回 `RstackConfig` 对象的普通函数即可: - -```ts title="app-preset.ts" -import type { RstackConfig } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -export function appPreset(options: { title: string }): RstackConfig { - return { - extends: [sharedConfig], - app: async () => { - const { pluginReact } = await import('@rsbuild/plugin-react'); - return { - plugins: [pluginReact()], - html: { - title: options.title, - }, - }; - }, - test: { - testEnvironment: 'happy-dom', - }, - }; -} -``` - -库预设可以直接使用普通对象: - -```ts title="lib-preset.ts" -import type { RstackConfig } from 'rstack'; -import { sharedConfig } from './shared.ts'; - -export const libPreset: RstackConfig = { - extends: [sharedConfig], - lib: { - format: 'esm', - dts: true, - }, - test: { - testEnvironment: 'node', - }, -}; -``` - -将两套预设发布为 `@biz/app-preset` 和 `@biz/lib-preset` 后,应用项目可以这样使用: - -```ts title="rstack.config.ts(应用项目)" -import { appPreset } from '@biz/app-preset'; -import { define } from 'rstack'; - -define.extends([appPreset({ title: 'My App' })]); -``` - -库项目可以这样使用: - -```ts title="rstack.config.ts(库项目)" -import { libPreset } from '@biz/lib-preset'; -import { define } from 'rstack'; - -define.extends([libPreset]); -``` - -请将 `@rsbuild/plugin-react` 声明为应用预设的依赖,并在使用 happy-dom 运行测试的应用项目中安装 `happy-dom`。 - -### 依赖加载与路径解析 \{#loading-dependencies-and-resolving-paths} - -公共配置与项目配置遵循相同的[依赖加载建议](#loading-dependencies-on-demand),每个命令只执行它需要的工具配置函数。在上面的应用预设中,`rs lint` 和 `rs fmt` 会跳过 React 插件的导入,`rs test` 则会在继承应用配置时加载它。 - -外层的 `appPreset()` 函数在加载配置文件时就会执行。因此,请将工具专用依赖的导入放在对应的配置函数内,例如 `app: async () => { ... }`。 - -各工具继续沿用原有的相对路径规则,将配置移入预设不会改变路径的解析基准。如果要引用随预设发布的文件,请通过 `import.meta.url` 获取绝对路径: +相对路径沿用各工具的解析规则。引用公共配置自带的文件时,请使用绝对路径: ```ts title="shared.ts" import { fileURLToPath } from 'node:url'; @@ -372,6 +303,4 @@ export const sharedConfig: RstackConfig = { }; ``` -请将 `setup.ts` 发布到包中的对应位置,或将路径改为它的编译产物。 - -本地公共模块也会被追踪为配置依赖。在开发或监听模式下,修改导入的公共模块会重新加载对应的 App、Lib 或 Doc 配置。 +发布公共配置包时,请包含该文件,或将路径指向它的编译产物。 From 0b3b0c83a216c1126332af9f93e05a605c49d71c Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 9 Oct 2026 14:31:50 +0800 Subject: [PATCH 5/6] docs: clarify shared configuration semantics --- website/docs/en/guide/configuration.mdx | 38 ++++++++++++------------- website/docs/zh/guide/configuration.mdx | 38 ++++++++++++------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index a4836ac9..2e4df17c 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -158,7 +158,7 @@ When `extends` is omitted, Rstack CLI automatically connects the test configurat 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. -These rules also apply to [shared configurations](#shared-configurations): Rstack first merges the test settings, then uses the merged App or Lib configuration for automatic inheritance. +These rules also apply to [shared configurations](#shared-configurations): Rstack first merges the test settings, then determines whether to inherit the merged App or Lib configuration. > For more guidance on testing, see [Testing](./testing). @@ -203,11 +203,11 @@ define.staged({ }); ``` -`rs staged` requires a staged configuration, provided by `define.staged()` or a shared configuration, and reports an error when it is missing. +Provide a staged configuration through `define.staged()` or a [shared configuration](#shared-configurations). `rs staged` reports an error if neither provides one. ## Shared configurations -Use `define.extends()` to share build, test, lint, and formatting settings across projects. Define a shared configuration as a plain object with the `RstackConfig` type. Its fields accept the same values as the corresponding `define.*()` APIs: +Use `define.extends()` to share build, test, lint, and formatting settings across projects. A shared configuration is a plain object whose fields accept the same values as the corresponding `define.*()` APIs. In TypeScript, use `RstackConfig` to check its types: ```ts title="shared.ts" import type { RstackConfig } from 'rstack'; @@ -248,7 +248,7 @@ Configurations are applied in this order: 1. Shared configurations in the `define.extends()` array, from left to right. 2. The project's own `define.*()` configurations, even if they appear before `define.extends()` in the file. -Later configurations take precedence according to each tool's [merge rules](#merge-rules). +Each tool's [merge rules](#merge-rules) determine which values are combined and which are replaced. Shared configurations can also extend other shared configurations: @@ -264,33 +264,33 @@ export const teamConfig: RstackConfig = { }; ``` -Inherited configurations are applied before the object that extends them. Here, the order is `sharedConfig → teamConfig → project configuration`. +Inherited configurations are applied first. With `define.extends([teamConfig])`, the order is `sharedConfig → teamConfig → project configuration`. -Shared configurations are applied each time they appear. If both `a` and `b` extend `base`, for example, `define.extends([a, b])` applies `base → a → base → b → project configuration`. Array entries may therefore be added more than once; each tool determines how to handle duplicate plugins. Circular inheritance throws an error with the location of the circular reference. +Repeated references are not deduplicated. If both `a` and `b` extend `base`, `define.extends([a, b])` applies `base → a → base → b → project configuration`. Each tool handles duplicate plugins according to its own rules. Circular inheritance throws an error that identifies the circular reference. ### Merge rules Rstack merges configurations separately for each tool: -| Field | Merge behavior | -| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `app` | Uses Rsbuild's `mergeRsbuildConfig`. | -| `lib` | Uses Rslib's `mergeRslibConfig`. | -| `test` | Uses Rstest's `mergeRstestConfig`, then determines whether to inherit the merged App or Lib configuration. See [Test configuration inheritance](./testing#configuration-inheritance). | -| `lint` | Concatenates configuration arrays in order; Rslint applies their rules. | -| `fmt` | Shallow merge: later values replace earlier values for the same option. Arrays and objects, including `overrides`, are replaced as a whole. | -| `doc` | Uses Rspress's `mergeDocConfig`: recursively merges objects, appends arrays, and replaces ordinary functions with later functions. | -| `staged` | Shallow merge by glob pattern: later tasks replace earlier tasks for the same pattern, including command arrays and task functions. | +| Field | Merge behavior | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `app` | Uses Rsbuild's `mergeRsbuildConfig`. | +| `lib` | Uses Rslib's `mergeRslibConfig`. | +| `test` | Uses Rstest's `mergeRstestConfig`. | +| `lint` | Concatenates configuration arrays in order; Rslint applies their rules. | +| `fmt` | Shallow merge: later values replace earlier values for the same option. Arrays and objects, including `overrides`, are replaced as a whole. | +| `doc` | Uses Rspress's `mergeDocConfig`: recursively merges objects, appends arrays, and replaces ordinary functions with later functions. | +| `staged` | Shallow merge by glob pattern: later tasks replace earlier tasks for the same pattern, including command arrays and task functions. | -For `doc`, Rspress also handles a mix of array and non-array values: if both are defined, it wraps the non-array value in an array before concatenating them. `builderConfig` follows these same Rspress rules. For example, a later `builderConfig.tools.rspack` callback replaces an earlier one, while callback arrays are concatenated. +The `doc` rules also apply to `builderConfig`: a later `builderConfig.tools.rspack` callback replaces an earlier one, while callback arrays are concatenated. When an array is merged with a defined non-array value, Rspress treats that value as a single array item. -For `staged`, a top-level task-generator function replaces all earlier settings. A later configuration also replaces that function in full. Any subsequent glob mappings then merge as usual. For example, with `glob mappings → function → new glob mappings`, only the new mappings remain. Task functions run when lint-staged passes them the list of staged files. +For `staged`, if either configuration is a top-level task-generator function, the later configuration replaces the earlier one entirely. For example, `glob mappings → function → new glob mappings` leaves only the new mappings. Task functions run when lint-staged supplies the staged file list. ### Dependencies and paths Shared configurations can also [import dependencies dynamically](#loading-dependencies-on-demand) inside tool configuration functions, so each command loads only what it needs. -Relative paths follow each tool's existing rules. Use an absolute path to reference a file shipped with a shared configuration: +Relative paths follow each tool's existing rules; they are not automatically resolved from the shared module's directory. Use an absolute path for files shipped with the shared configuration: ```ts title="shared.ts" import { fileURLToPath } from 'node:url'; @@ -303,4 +303,4 @@ export const sharedConfig: RstackConfig = { }; ``` -Include the file when publishing the shared configuration package, or point the path to its compiled output. +Make sure the published package includes the referenced file. If you compile it, update the path to match its output location. diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index 78066dba..53bf9218 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -158,7 +158,7 @@ define.test({ 如果测试根配置未定义 `extends` 且包含 `projects`,Rstack CLI 会为每个未自行设置 `extends` 的内联项目应用自动继承。函数形式的应用或库配置只会解析一次,并由这些项目共享。字符串形式的项目会原样传给 Rstest;它们会独立加载外部配置,不继承当前应用或库的配置。 -这些规则同样适用于[公共配置](#shared-configurations):Rstack 先合并测试配置,再使用合并后的 App 或 Lib 配置进行自动继承。 +这些规则同样适用于[公共配置](#shared-configurations):Rstack 先合并测试配置,再判断是否需要继承合并后的 App 或 Lib 配置。 > 如需了解更多测试相关用法,请参阅[测试](./testing)。 @@ -203,11 +203,11 @@ define.staged({ }); ``` -`rs staged` 必须通过 `define.staged()` 或公共配置提供 staged 配置,缺少配置时会报错。 +请通过 `define.staged()` 或[公共配置](#shared-configurations)提供 staged 配置,否则 `rs staged` 会报错。 ## 公共配置 \{#shared-configurations} -使用 `define.extends()` 可以在多个项目之间共享构建、测试、lint 和格式化配置。公共配置是一个标注了 `RstackConfig` 类型的普通对象,各字段的用法与对应的 `define.*()` API 一致: +使用 `define.extends()` 可以在多个项目之间共享构建、测试、lint 和格式化配置。公共配置是一个普通对象,各字段的用法与对应的 `define.*()` API 一致。在 TypeScript 中,可以用 `RstackConfig` 检查配置类型: ```ts title="shared.ts" import type { RstackConfig } from 'rstack'; @@ -248,7 +248,7 @@ define.fmt({ 1. `define.extends()` 数组中的公共配置,从左到右。 2. 项目自己的 `define.*()` 配置,即使这些调用写在 `define.extends()` 之前。 -后面的配置按照各工具的[合并规则](#merge-rules)覆盖前面的配置。 +各工具的[合并规则](#merge-rules)决定同一字段的值是合并还是替换。 公共配置也可以继承其他公共配置: @@ -264,33 +264,33 @@ export const teamConfig: RstackConfig = { }; ``` -先应用被继承的配置,再应用当前对象。上例的顺序为 `sharedConfig → teamConfig → 项目配置`。 +被继承的配置先应用。使用 `define.extends([teamConfig])` 时,顺序为 `sharedConfig → teamConfig → 项目配置`。 -公共配置每出现一次就应用一次。例如,`a` 和 `b` 都继承 `base` 时,`define.extends([a, b])` 的顺序为 `base → a → base → b → 项目配置`。因此,数组中的内容可能被多次添加,重复的插件则由各工具自行处理。循环继承会抛出错误,并指出循环引用的位置。 +重复引用不会去重。例如,`a` 和 `b` 都继承 `base` 时,`define.extends([a, b])` 的顺序为 `base → a → base → b → 项目配置`。重复的插件由各工具按自身规则处理。循环继承会抛出错误,并指出循环引用的位置。 ### 合并规则 \{#merge-rules} Rstack 分别合并各个工具的配置: -| 字段 | 合并方式 | -| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `app` | 使用 Rsbuild 的 `mergeRsbuildConfig`。 | -| `lib` | 使用 Rslib 的 `mergeRslibConfig`。 | -| `test` | 使用 Rstest 的 `mergeRstestConfig`,再判断是否继承合并后的 App 或 Lib 配置,详见[测试配置继承](./testing#configuration-inheritance)。 | -| `lint` | 按顺序拼接配置数组,由 Rslint 应用其中的规则。 | -| `fmt` | 浅合并,同名选项由后面的值覆盖。数组和对象(包括 `overrides`)整体替换。 | -| `doc` | 使用 Rspress 的 `mergeDocConfig`:递归合并对象、追加数组,普通函数由后面的函数覆盖。 | -| `staged` | 按 glob 模式浅合并,同名模式的任务整体替换,包括命令数组和任务函数。 | +| 字段 | 合并方式 | +| -------- | ------------------------------------------------------------------------------------ | +| `app` | 使用 Rsbuild 的 `mergeRsbuildConfig`。 | +| `lib` | 使用 Rslib 的 `mergeRslibConfig`。 | +| `test` | 使用 Rstest 的 `mergeRstestConfig`。 | +| `lint` | 按顺序拼接配置数组,由 Rslint 应用其中的规则。 | +| `fmt` | 浅合并,同名选项由后面的值覆盖。数组和对象(包括 `overrides`)整体替换。 | +| `doc` | 使用 Rspress 的 `mergeDocConfig`:递归合并对象、追加数组,普通函数由后面的函数覆盖。 | +| `staged` | 按 glob 模式浅合并,同名模式的任务整体替换,包括命令数组和任务函数。 | -对于 `doc`,如果同一字段的两个值都已定义,且只有一个是数组,Rspress 会先将另一个值包装为数组,再拼接。`builderConfig` 也遵循这套 Rspress 合并规则。例如,后面的 `builderConfig.tools.rspack` 回调会覆盖前面的回调,回调数组则按顺序拼接。 +`doc` 的合并规则同样适用于 `builderConfig`:后面的 `builderConfig.tools.rspack` 回调会覆盖前面的回调,回调数组则按顺序拼接。数组与已定义的非数组值合并时,Rspress 会将后者作为一个数组元素参与拼接。 -对于 `staged`,顶层任务生成函数会整体替换此前的配置,而它之后的配置也会整体替换这个函数。再往后的 glob 映射则按通常的规则合并。例如,按「glob 映射 → 函数 → 新的 glob 映射」应用时,最终只保留新的映射。任务函数由 lint-staged 传入暂存文件列表后执行。 +合并 `staged` 时,只要任一配置是顶层任务生成函数,就由后一个配置整体替换前一个。例如,「glob 映射 → 函数 → 新的 glob 映射」最终只保留新的映射。任务函数由 lint-staged 传入暂存文件列表后执行。 ### 依赖与路径 \{#dependencies-and-paths} 公共配置也可以在工具配置函数中[动态导入依赖](#loading-dependencies-on-demand),让各命令只加载需要的依赖。 -相对路径沿用各工具的解析规则。引用公共配置自带的文件时,请使用绝对路径: +相对路径沿用各工具的解析规则,不会自动改为以公共模块所在目录为基准。引用公共配置自带的文件时,请使用绝对路径: ```ts title="shared.ts" import { fileURLToPath } from 'node:url'; @@ -303,4 +303,4 @@ export const sharedConfig: RstackConfig = { }; ``` -发布公共配置包时,请包含该文件,或将路径指向它的编译产物。 +发布包中需要包含引用的文件。如果该文件经过编译,请将路径改为编译产物的位置。 From 7d5e23a9dfde15636e49e3d8c7cac51164866c18 Mon Sep 17 00:00:00 2001 From: neverland Date: Fri, 9 Oct 2026 14:43:54 +0800 Subject: [PATCH 6/6] docs: illustrate lazy shared config dependencies --- website/docs/en/guide/configuration.mdx | 17 ++++++++++++++++- website/docs/zh/guide/configuration.mdx | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/website/docs/en/guide/configuration.mdx b/website/docs/en/guide/configuration.mdx index 2e4df17c..29183e66 100644 --- a/website/docs/en/guide/configuration.mdx +++ b/website/docs/en/guide/configuration.mdx @@ -286,10 +286,25 @@ The `doc` rules also apply to `builderConfig`: a later `builderConfig.tools.rspa For `staged`, if either configuration is a top-level task-generator function, the later configuration replaces the earlier one entirely. For example, `glob mappings → function → new glob mappings` leaves only the new mappings. Task functions run when lint-staged supplies the staged file list. -### Dependencies and paths +### Loading dependencies Shared configurations can also [import dependencies dynamically](#loading-dependencies-on-demand) inside tool configuration functions, so each command loads only what it needs. +```ts title="shared.ts" +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + app: async () => { + const { pluginReact } = await import('@rsbuild/plugin-react'); + return { + plugins: [pluginReact()], + }; + }, +}; +``` + +### Resolving paths + Relative paths follow each tool's existing rules; they are not automatically resolved from the shared module's directory. Use an absolute path for files shipped with the shared configuration: ```ts title="shared.ts" diff --git a/website/docs/zh/guide/configuration.mdx b/website/docs/zh/guide/configuration.mdx index 53bf9218..b0f3e9d0 100644 --- a/website/docs/zh/guide/configuration.mdx +++ b/website/docs/zh/guide/configuration.mdx @@ -286,10 +286,25 @@ Rstack 分别合并各个工具的配置: 合并 `staged` 时,只要任一配置是顶层任务生成函数,就由后一个配置整体替换前一个。例如,「glob 映射 → 函数 → 新的 glob 映射」最终只保留新的映射。任务函数由 lint-staged 传入暂存文件列表后执行。 -### 依赖与路径 \{#dependencies-and-paths} +### 依赖加载 \{#loading-dependencies} 公共配置也可以在工具配置函数中[动态导入依赖](#loading-dependencies-on-demand),让各命令只加载需要的依赖。 +```ts title="shared.ts" +import type { RstackConfig } from 'rstack'; + +export const sharedConfig: RstackConfig = { + app: async () => { + const { pluginReact } = await import('@rsbuild/plugin-react'); + return { + plugins: [pluginReact()], + }; + }, +}; +``` + +### 路径解析 \{#resolving-paths} + 相对路径沿用各工具的解析规则,不会自动改为以公共模块所在目录为基准。引用公共配置自带的文件时,请使用绝对路径: ```ts title="shared.ts"