diff --git a/e2e/cases/javascript-api/dev-server-restart/index.test.ts b/e2e/cases/javascript-api/dev-server-restart/index.test.ts new file mode 100644 index 0000000000..c257abc634 --- /dev/null +++ b/e2e/cases/javascript-api/dev-server-restart/index.test.ts @@ -0,0 +1,54 @@ +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 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.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(), + }; + + 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(configDep, `${configDepContent}// ${++version}\n`); + } + return restartContext; + }) + .toEqual({ + action: 'dev', + 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/dev-server-restart/src/index.js b/e2e/cases/javascript-api/dev-server-restart/src/index.js new file mode 100644 index 0000000000..eafcfcda16 --- /dev/null +++ b/e2e/cases/javascript-api/dev-server-restart/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..8f0498e21e 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) 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`. 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..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 { }; ``` -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, 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..079cb2677f 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)。 ### 加载环境变量 @@ -103,7 +103,9 @@ const rsbuild = await createRsbuild({ - **版本:** v2.1.7 新增 -`restart` 选项允许调用方控制 Rsbuild 如何重启当前 dev server 或监听构建,适用于需要在 Rsbuild 外部管理重启流程的场景。 +`restart` 选项允许你控制 Rsbuild 如何重启当前 dev server 或监听构建,适用于需要在 Rsbuild 外部管理重启流程的场景。 + +> 查看 [配置文件监听](/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 89156a4d41..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 { }; ``` -使用 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) 来处理重启请求。 +> 关于配置文件自动监听及重启行为,详见[配置文件监听](/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 环境变量,来动态写入不同的配置: