From dc5bfdbc85f2a92814fc8f95b29675e91e12bf24 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 20 Jul 2026 21:25:31 +0800 Subject: [PATCH 1/2] feat(core): watch loaded config files for restart --- .../restart-config-files/index.test.ts | 48 +++++++++++++++++++ .../restart-config-files/rsbuild.config.mjs | 3 ++ .../restart-config-files/sharedConfig.mjs | 1 + .../restart-config-files/src/index.js | 1 + packages/core/src/cli/init.ts | 14 +----- packages/core/src/restart.ts | 7 ++- website/docs/en/api/javascript-api/core.mdx | 4 +- website/docs/en/config/dev/watch-files.mdx | 2 +- .../docs/en/guide/configuration/rsbuild.mdx | 8 ++++ website/docs/zh/api/javascript-api/core.mdx | 4 +- website/docs/zh/config/dev/watch-files.mdx | 2 +- .../docs/zh/guide/configuration/rsbuild.mdx | 8 ++++ 12 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 e2e/cases/javascript-api/restart-config-files/index.test.ts create mode 100644 e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs create mode 100644 e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs create mode 100644 e2e/cases/javascript-api/restart-config-files/src/index.js diff --git a/e2e/cases/javascript-api/restart-config-files/index.test.ts b/e2e/cases/javascript-api/restart-config-files/index.test.ts new file mode 100644 index 0000000000..711d58dbd2 --- /dev/null +++ b/e2e/cases/javascript-api/restart-config-files/index.test.ts @@ -0,0 +1,48 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { expect, test } from '@e2e/helper'; +import { createRsbuild, loadConfig, type RestartContext } from '@rsbuild/core'; +import { getRandomPort } from '@rstackjs/test-utils'; + +const configDependency = path.join(import.meta.dirname, 'sharedConfig.mjs'); +const originalConfig = fs.readFileSync(configDependency, 'utf-8'); + +test.afterAll(() => { + fs.writeFileSync(configDependency, originalConfig); +}); + +test('should watch loaded config dependencies for restart', async () => { + const result = await loadConfig({ cwd: import.meta.dirname }); + result.content.server = { port: await getRandomPort() }; + + const rsbuild = await createRsbuild({ + cwd: import.meta.dirname, + config: result, + }); + let restartContext: RestartContext | undefined; + rsbuild.onRestart((context) => { + restartContext = context; + }); + + const server = await rsbuild.createDevServer({ runCompile: false }); + let version = 0; + + try { + await expect + .poll( + () => { + if (!restartContext) { + fs.writeFileSync(configDependency, `${originalConfig}\n// ${++version}\n`); + } + return restartContext; + }, + { timeout: 5_000 }, + ) + .toEqual({ + action: 'dev', + filePath: configDependency, + }); + } finally { + await server.close(); + } +}); diff --git a/e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs b/e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs new file mode 100644 index 0000000000..1c613462b3 --- /dev/null +++ b/e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs @@ -0,0 +1,3 @@ +import { sharedConfig } from './sharedConfig.mjs'; + +export default sharedConfig; diff --git a/e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs b/e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs new file mode 100644 index 0000000000..2c027a5035 --- /dev/null +++ b/e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs @@ -0,0 +1 @@ +export const sharedConfig = {}; diff --git a/e2e/cases/javascript-api/restart-config-files/src/index.js b/e2e/cases/javascript-api/restart-config-files/src/index.js new file mode 100644 index 0000000000..eafcfcda16 --- /dev/null +++ b/e2e/cases/javascript-api/restart-config-files/src/index.js @@ -0,0 +1 @@ +console.log('restart config files'); diff --git a/packages/core/src/cli/init.ts b/packages/core/src/cli/init.ts index f32a31c94c..80ba54afb3 100644 --- a/packages/core/src/cli/init.ts +++ b/packages/core/src/cli/init.ts @@ -1,6 +1,5 @@ import path from 'node:path'; import { createRsbuild } from '../createRsbuild'; -import { castArray } from '../helpers'; import { ensureAbsolutePath } from '../helpers/path'; import { loadConfig as baseLoadConfig } from '../loadConfig'; import { defaultLogger } from '../logger'; @@ -47,7 +46,7 @@ const loadConfig = async (root: string) => { loader: options.configLoader, command, }); - const { content: config, filePath, dependencies } = result; + const { content: config } = result; config.dev ||= {}; config.source ||= {}; @@ -111,17 +110,6 @@ const loadConfig = async (root: string) => { config.dev.cliShortcuts = true; } - // watch the config file - if (filePath) { - config.dev.watchFiles = [ - ...(config.dev.watchFiles ? castArray(config.dev.watchFiles) : []), - { - paths: [filePath, ...dependencies], - type: 'restart', - }, - ]; - } - return result; }; diff --git a/packages/core/src/restart.ts b/packages/core/src/restart.ts index 9d3a83bbaa..4ea2a7b0fe 100644 --- a/packages/core/src/restart.ts +++ b/packages/core/src/restart.ts @@ -50,12 +50,15 @@ export function watchFilesForRestart({ context: InternalContext; action: RestartContext['action']; }): WatchFilesResult | undefined { - if (!watchFiles.length) { + const defaultFiles = context.configFile + ? [context.configFile, ...context.configFileDependencies] + : []; + + if (!watchFiles.length && !defaultFiles.length) { return; } const watchGroups: { files: string[]; options?: ChokidarOptions }[] = []; - const defaultFiles: string[] = []; for (const { paths, options, type } of watchFiles) { if (type !== 'restart' && type !== 'reload-server') { diff --git a/website/docs/en/api/javascript-api/core.mdx b/website/docs/en/api/javascript-api/core.mdx index 746f011524..db0d5b0df9 100644 --- a/website/docs/en/api/javascript-api/core.mdx +++ b/website/docs/en/api/javascript-api/core.mdx @@ -70,7 +70,7 @@ const rsbuild = await createRsbuild({ }); ``` -When the complete `loadConfig` result is passed, the absolute paths of the config file and its imported dependencies are stored in [`rsbuild.context.configFile`](/api/javascript-api/instance#contextconfigfile) and [`rsbuild.context.configFileDependencies`](/api/javascript-api/instance#contextconfigfiledependencies), respectively, and used as build dependencies when persistent build cache is enabled. +When the complete `loadConfig` result is passed, the absolute paths of the config file and its imported dependencies are stored in [`rsbuild.context.configFile`](/api/javascript-api/instance#contextconfigfile) and [`rsbuild.context.configFileDependencies`](/api/javascript-api/instance#contextconfigfiledependencies), respectively. When persistent build cache is enabled, these files are also used as build dependencies. See [Configuration file watching](/guide/configuration/rsbuild#configuration-file-watching) for how Rsbuild watches them for restart requests. ### Load environment variables @@ -105,6 +105,8 @@ const rsbuild = await createRsbuild({ The `restart` option allows the caller to control how Rsbuild restarts the current dev server or watch build. This is useful when the restart flow needs to be managed outside Rsbuild. +See [Configuration file watching](/guide/configuration/rsbuild#configuration-file-watching) for when restart watchers are installed and how loaded config files are watched. + When a restart is requested, Rsbuild calls the [onRestart hook](/plugins/dev/hooks#onrestart), closes the current task resources, and then calls `restart`. The function should return `true` when the replacement task starts successfully. If it returns `false` or throws an error, the restart watcher remains active so later file changes can retry the restart. diff --git a/website/docs/en/config/dev/watch-files.mdx b/website/docs/en/config/dev/watch-files.mdx index 8411e361d0..3c8ac144d6 100644 --- a/website/docs/en/config/dev/watch-files.mdx +++ b/website/docs/en/config/dev/watch-files.mdx @@ -112,7 +112,7 @@ export default { }; ``` -When using the JavaScript API, `rsbuild.startDevServer()`, `rsbuild.createDevServer()`, and `rsbuild.build({ watch: true })` install restart watchers, while regular builds and preview servers do not. When a watched file changes, Rsbuild calls the [onRestart hook](/plugins/dev/hooks#onrestart). By default, Rsbuild does not close or restart the current task; you can pass the [`restart` option](/api/javascript-api/core#restart) to handle restart requests. +For details about automatic config file watching and restart behavior in the CLI and JavaScript API, see [Configuration file watching](/guide/configuration/rsbuild#configuration-file-watching). ### reload-server diff --git a/website/docs/en/guide/configuration/rsbuild.mdx b/website/docs/en/guide/configuration/rsbuild.mdx index 6e1541b2a4..dea2053921 100644 --- a/website/docs/en/guide/configuration/rsbuild.mdx +++ b/website/docs/en/guide/configuration/rsbuild.mdx @@ -164,6 +164,14 @@ When using Node.js's native loader, note the following limitations: > See [Node.js - Running TypeScript Natively](https://nodejs.org/en/learn/typescript/run-natively#running-typescript-natively) for more details. +## Configuration file watching + +When using the Rsbuild CLI, `rsbuild dev` and `rsbuild build --watch` automatically watch the loaded configuration file and its imported dependencies. When one of these files changes, Rsbuild reloads the configuration and restarts the current dev server or watch build. + +When using the JavaScript API, pass the complete [`loadConfig`](/api/javascript-api/core#loadconfig) result to `createRsbuild` so Rsbuild can track and automatically watch these files. + +`rsbuild.startDevServer()`, `rsbuild.createDevServer()`, and `rsbuild.build({ watch: true })` install restart watchers, while regular builds and preview servers do not. When a watched file changes, Rsbuild calls the [onRestart hook](/plugins/dev/hooks#onrestart). By default, Rsbuild does not close or restart the current task; you can pass the [`restart` option](/api/javascript-api/core#restart) to handle restart requests. + ## Using environment variables In the configuration file, you can use Node.js environment variables such as `process.env.NODE_ENV` to dynamically set different configurations: diff --git a/website/docs/zh/api/javascript-api/core.mdx b/website/docs/zh/api/javascript-api/core.mdx index 96eea05476..969be8e8c7 100644 --- a/website/docs/zh/api/javascript-api/core.mdx +++ b/website/docs/zh/api/javascript-api/core.mdx @@ -70,7 +70,7 @@ const rsbuild = await createRsbuild({ }); ``` -传入完整的 `loadConfig` 返回结果后,配置文件及其导入依赖的绝对路径会分别记录在 [`rsbuild.context.configFile`](/api/javascript-api/instance#contextconfigfile) 和 [`rsbuild.context.configFileDependencies`](/api/javascript-api/instance#contextconfigfiledependencies) 中,并在启用持久化构建缓存时作为构建依赖。 +传入完整的 `loadConfig` 返回结果后,配置文件及其导入依赖的绝对路径会分别记录在 [`rsbuild.context.configFile`](/api/javascript-api/instance#contextconfigfile) 和 [`rsbuild.context.configFileDependencies`](/api/javascript-api/instance#contextconfigfiledependencies) 中。启用持久化构建缓存时,这些文件也会作为构建依赖。关于 Rsbuild 如何监听这些文件,并在文件变化时请求重启,详见[配置文件监听](/guide/configuration/rsbuild#configuration-file-watching)。 ### 加载环境变量 @@ -105,6 +105,8 @@ const rsbuild = await createRsbuild({ `restart` 选项允许调用方控制 Rsbuild 如何重启当前 dev server 或监听构建,适用于需要在 Rsbuild 外部管理重启流程的场景。 +restart watcher 的安装条件及加载配置文件的监听方式,详见[配置文件监听](/guide/configuration/rsbuild#configuration-file-watching)。 + 当请求重启时,Rsbuild 会调用 [onRestart hook](/plugins/dev/hooks#onrestart),关闭当前任务的资源,然后调用 `restart`。 新任务成功启动时,该函数应返回 `true`。如果返回 `false` 或抛出错误,restart watcher 会保持运行,以便后续文件变化时再次尝试重启。 diff --git a/website/docs/zh/config/dev/watch-files.mdx b/website/docs/zh/config/dev/watch-files.mdx index 89156a4d41..457b8b7f82 100644 --- a/website/docs/zh/config/dev/watch-files.mdx +++ b/website/docs/zh/config/dev/watch-files.mdx @@ -112,7 +112,7 @@ export default { }; ``` -使用 JavaScript API 时,`rsbuild.startDevServer()`、`rsbuild.createDevServer()` 和 `rsbuild.build({ watch: true })` 会安装 restart watcher,普通构建和预览服务器则不会安装。被监听的文件发生变化时,Rsbuild 会调用 [onRestart hook](/plugins/dev/hooks#onrestart)。默认情况下,Rsbuild 不会关闭或重启当前任务;你可以传入 [`restart` 选项](/api/javascript-api/core#restart) 来处理重启请求。 +关于 CLI 和 JavaScript API 中的配置文件自动监听及重启行为,详见[配置文件监听](/guide/configuration/rsbuild#configuration-file-watching)。 ### reload-server diff --git a/website/docs/zh/guide/configuration/rsbuild.mdx b/website/docs/zh/guide/configuration/rsbuild.mdx index 92d926da71..41254c3724 100644 --- a/website/docs/zh/guide/configuration/rsbuild.mdx +++ b/website/docs/zh/guide/configuration/rsbuild.mdx @@ -164,6 +164,14 @@ Rsbuild 提供了三种配置文件加载方式: > 详见 [Node.js - Running TypeScript Natively](https://nodejs.org/en/learn/typescript/run-natively#running-typescript-natively)。 +## 配置文件监听 \{#configuration-file-watching} + +使用 Rsbuild CLI 时,`rsbuild dev` 和 `rsbuild build --watch` 会自动监听已加载的配置文件及其导入依赖。当其中任一文件发生变化时,Rsbuild 会重新加载配置,并重启当前 dev server 或监听构建。 + +使用 JavaScript API 时,请将完整的 [`loadConfig`](/api/javascript-api/core#loadconfig) 返回结果传入 `createRsbuild`,使 Rsbuild 可以追踪并自动监听这些文件。 + +`rsbuild.startDevServer()`、`rsbuild.createDevServer()` 和 `rsbuild.build({ watch: true })` 会安装 restart watcher,普通构建和预览服务器则不会安装。被监听的文件发生变化时,Rsbuild 会调用 [onRestart hook](/plugins/dev/hooks#onrestart)。默认情况下,Rsbuild 不会关闭或重启当前任务;你可以传入 [`restart` 选项](/api/javascript-api/core#restart) 来处理重启请求。 + ## 使用环境变量 在配置文件中,你可以使用 `process.env.NODE_ENV` 等 Node.js 环境变量,来动态写入不同的配置: From 16bd9391d39b34bb03e3c69841560337e53034df Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 20 Jul 2026 22:40:20 +0800 Subject: [PATCH 2/2] fix --- .../index.test.ts | 34 +++++++++++-------- .../dev-server-restart/rsbuild.config.mjs | 3 ++ .../src/index.js | 0 .../restart-config-files/rsbuild.config.mjs | 3 -- .../restart-config-files/sharedConfig.mjs | 1 - website/docs/en/api/javascript-api/core.mdx | 2 +- website/docs/en/config/dev/watch-files.mdx | 2 +- website/docs/zh/api/javascript-api/core.mdx | 4 +-- website/docs/zh/config/dev/watch-files.mdx | 2 +- 9 files changed, 28 insertions(+), 23 deletions(-) rename e2e/cases/javascript-api/{restart-config-files => dev-server-restart}/index.test.ts (60%) create mode 100644 e2e/cases/javascript-api/dev-server-restart/rsbuild.config.mjs rename e2e/cases/javascript-api/{restart-config-files => dev-server-restart}/src/index.js (100%) delete mode 100644 e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs delete mode 100644 e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs diff --git a/e2e/cases/javascript-api/restart-config-files/index.test.ts b/e2e/cases/javascript-api/dev-server-restart/index.test.ts similarity index 60% rename from e2e/cases/javascript-api/restart-config-files/index.test.ts rename to e2e/cases/javascript-api/dev-server-restart/index.test.ts index 711d58dbd2..c257abc634 100644 --- a/e2e/cases/javascript-api/restart-config-files/index.test.ts +++ b/e2e/cases/javascript-api/dev-server-restart/index.test.ts @@ -4,22 +4,31 @@ import { expect, test } from '@e2e/helper'; import { createRsbuild, loadConfig, type RestartContext } from '@rsbuild/core'; import { getRandomPort } from '@rstackjs/test-utils'; -const configDependency = path.join(import.meta.dirname, 'sharedConfig.mjs'); -const originalConfig = fs.readFileSync(configDependency, 'utf-8'); +const configDep = path.join(import.meta.dirname, 'test-temp-config-dep.mjs'); +const configDepContent = 'export const sharedConfig = {};\n'; + +test.beforeAll(() => { + fs.writeFileSync(configDep, configDepContent); +}); test.afterAll(() => { - fs.writeFileSync(configDependency, originalConfig); + fs.rmSync(configDep, { force: true }); }); test('should watch loaded config dependencies for restart', async () => { const result = await loadConfig({ cwd: import.meta.dirname }); - result.content.server = { port: await getRandomPort() }; + + result.content.server = { + port: await getRandomPort(), + }; const rsbuild = await createRsbuild({ cwd: import.meta.dirname, config: result, }); + let restartContext: RestartContext | undefined; + rsbuild.onRestart((context) => { restartContext = context; }); @@ -29,18 +38,15 @@ test('should watch loaded config dependencies for restart', async () => { try { await expect - .poll( - () => { - if (!restartContext) { - fs.writeFileSync(configDependency, `${originalConfig}\n// ${++version}\n`); - } - return restartContext; - }, - { timeout: 5_000 }, - ) + .poll(() => { + if (!restartContext) { + fs.writeFileSync(configDep, `${configDepContent}// ${++version}\n`); + } + return restartContext; + }) .toEqual({ action: 'dev', - filePath: configDependency, + filePath: configDep, }); } finally { await server.close(); diff --git a/e2e/cases/javascript-api/dev-server-restart/rsbuild.config.mjs b/e2e/cases/javascript-api/dev-server-restart/rsbuild.config.mjs new file mode 100644 index 0000000000..600fda2811 --- /dev/null +++ b/e2e/cases/javascript-api/dev-server-restart/rsbuild.config.mjs @@ -0,0 +1,3 @@ +import { sharedConfig } from './test-temp-config-dep.mjs'; + +export default sharedConfig; diff --git a/e2e/cases/javascript-api/restart-config-files/src/index.js b/e2e/cases/javascript-api/dev-server-restart/src/index.js similarity index 100% rename from e2e/cases/javascript-api/restart-config-files/src/index.js rename to e2e/cases/javascript-api/dev-server-restart/src/index.js diff --git a/e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs b/e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs deleted file mode 100644 index 1c613462b3..0000000000 --- a/e2e/cases/javascript-api/restart-config-files/rsbuild.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { sharedConfig } from './sharedConfig.mjs'; - -export default sharedConfig; diff --git a/e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs b/e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs deleted file mode 100644 index 2c027a5035..0000000000 --- a/e2e/cases/javascript-api/restart-config-files/sharedConfig.mjs +++ /dev/null @@ -1 +0,0 @@ -export const sharedConfig = {}; diff --git a/website/docs/en/api/javascript-api/core.mdx b/website/docs/en/api/javascript-api/core.mdx index db0d5b0df9..8f0498e21e 100644 --- a/website/docs/en/api/javascript-api/core.mdx +++ b/website/docs/en/api/javascript-api/core.mdx @@ -105,7 +105,7 @@ const rsbuild = await createRsbuild({ The `restart` option allows the caller to control how Rsbuild restarts the current dev server or watch build. This is useful when the restart flow needs to be managed outside Rsbuild. -See [Configuration file watching](/guide/configuration/rsbuild#configuration-file-watching) for when restart watchers are installed and how loaded config files are watched. +> See [Configuration file watching](/guide/configuration/rsbuild#configuration-file-watching) to learn what triggers a restart. When a restart is requested, Rsbuild calls the [onRestart hook](/plugins/dev/hooks#onrestart), closes the current task resources, and then calls `restart`. diff --git a/website/docs/en/config/dev/watch-files.mdx b/website/docs/en/config/dev/watch-files.mdx index 3c8ac144d6..02d0162376 100644 --- a/website/docs/en/config/dev/watch-files.mdx +++ b/website/docs/en/config/dev/watch-files.mdx @@ -112,7 +112,7 @@ export default { }; ``` -For details about automatic config file watching and restart behavior in the CLI and JavaScript API, see [Configuration file watching](/guide/configuration/rsbuild#configuration-file-watching). +> For details about automatic config file watching and restart behavior, see [Configuration file watching](/guide/configuration/rsbuild#configuration-file-watching). ### reload-server diff --git a/website/docs/zh/api/javascript-api/core.mdx b/website/docs/zh/api/javascript-api/core.mdx index 969be8e8c7..079cb2677f 100644 --- a/website/docs/zh/api/javascript-api/core.mdx +++ b/website/docs/zh/api/javascript-api/core.mdx @@ -103,9 +103,9 @@ const rsbuild = await createRsbuild({ - **版本:** v2.1.7 新增 -`restart` 选项允许调用方控制 Rsbuild 如何重启当前 dev server 或监听构建,适用于需要在 Rsbuild 外部管理重启流程的场景。 +`restart` 选项允许你控制 Rsbuild 如何重启当前 dev server 或监听构建,适用于需要在 Rsbuild 外部管理重启流程的场景。 -restart watcher 的安装条件及加载配置文件的监听方式,详见[配置文件监听](/guide/configuration/rsbuild#configuration-file-watching)。 +> 查看 [配置文件监听](/guide/configuration/rsbuild#configuration-file-watching) 了解重启的触发方式。 当请求重启时,Rsbuild 会调用 [onRestart hook](/plugins/dev/hooks#onrestart),关闭当前任务的资源,然后调用 `restart`。 diff --git a/website/docs/zh/config/dev/watch-files.mdx b/website/docs/zh/config/dev/watch-files.mdx index 457b8b7f82..95cbe72aec 100644 --- a/website/docs/zh/config/dev/watch-files.mdx +++ b/website/docs/zh/config/dev/watch-files.mdx @@ -112,7 +112,7 @@ export default { }; ``` -关于 CLI 和 JavaScript API 中的配置文件自动监听及重启行为,详见[配置文件监听](/guide/configuration/rsbuild#configuration-file-watching)。 +> 关于配置文件自动监听及重启行为,详见[配置文件监听](/guide/configuration/rsbuild#configuration-file-watching)。 ### reload-server