From a772ac5c543ff2f13360d930cf3b9278b8f4ee28 Mon Sep 17 00:00:00 2001 From: neverland Date: Sat, 11 Jul 2026 17:38:05 +0800 Subject: [PATCH 1/3] feat: restart dev server on config changes --- packages/rstack/src/config.ts | 21 ++++++-- packages/rstack/src/rsbuildConfig.ts | 21 ++++++-- .../test/config/reload-app-config/.gitignore | 2 + .../config/reload-app-config/index.test.ts | 50 +++++++++++++++++++ .../config/reload-app-config/src/index.js | 1 + 5 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 packages/rstack/test/config/reload-app-config/.gitignore create mode 100644 packages/rstack/test/config/reload-app-config/index.test.ts create mode 100644 packages/rstack/test/config/reload-app-config/src/index.js diff --git a/packages/rstack/src/config.ts b/packages/rstack/src/config.ts index ca479bc..3f1f70e 100644 --- a/packages/rstack/src/config.ts +++ b/packages/rstack/src/config.ts @@ -1,4 +1,4 @@ -import { loadConfig } from '@rstackjs/load-config'; +import { loadConfig, type LoadConfigResult } from '@rstackjs/load-config'; import type { RsbuildConfigDefinition } from '@rsbuild/core'; import type { RslibConfigDefinition } from '@rslib/core'; import type { RslintConfig } from '@rslint/core'; @@ -18,6 +18,10 @@ export type Configs = { staged?: StagedConfig; }; +type LoadedRstackConfig = Pick & { + configs: Configs; +}; + type ConfigState = { configs: Configs; configPath?: string; @@ -101,12 +105,12 @@ export const define: Define = { staged: (config) => setConfig('staged', config), }; -export const loadRstackConfig = async (): Promise => { +export const loadRstackConfigWithMeta = async (): Promise => { const state = getConfigState(); state.configs = {}; try { - await loadConfig({ + const { filePath, dependencies } = await loadConfig({ loader: 'native', exportName: false, fresh: true, @@ -122,8 +126,17 @@ export const loadRstackConfig = async (): Promise => { }), }); - return state.configs; + return { + configs: state.configs, + filePath, + dependencies, + }; } finally { state.configs = {}; } }; + +export const loadRstackConfig = async (): Promise => { + const { configs } = await loadRstackConfigWithMeta(); + return configs; +}; diff --git a/packages/rstack/src/rsbuildConfig.ts b/packages/rstack/src/rsbuildConfig.ts index e6d32c8..87486f1 100644 --- a/packages/rstack/src/rsbuildConfig.ts +++ b/packages/rstack/src/rsbuildConfig.ts @@ -1,5 +1,5 @@ -import type { RsbuildConfigDefinition, ConfigParams } from '@rsbuild/core'; -import { loadRstackConfig, type Configs } from './config.js'; +import { mergeRsbuildConfig, type RsbuildConfigDefinition, type ConfigParams } from '@rsbuild/core'; +import { loadRstackConfigWithMeta, type Configs } from './config.js'; const resolveRsbuildConfig = async (configs: Configs, params: ConfigParams) => { const appConfig = configs.app; @@ -13,8 +13,21 @@ const resolveRsbuildConfig = async (configs: Configs, params: ConfigParams) => { }; const loadRsbuildConfig: RsbuildConfigDefinition = async (params) => { - const configs = await loadRstackConfig(); - return resolveRsbuildConfig(configs, params); + const { configs, filePath, dependencies } = await loadRstackConfigWithMeta(); + const config = await resolveRsbuildConfig(configs, params); + + if (!filePath) { + return config; + } + + return mergeRsbuildConfig(config, { + dev: { + watchFiles: { + paths: [filePath, ...dependencies], + type: 'reload-server', + }, + }, + }); }; export default loadRsbuildConfig; diff --git a/packages/rstack/test/config/reload-app-config/.gitignore b/packages/rstack/test/config/reload-app-config/.gitignore new file mode 100644 index 0000000..b944a20 --- /dev/null +++ b/packages/rstack/test/config/reload-app-config/.gitignore @@ -0,0 +1,2 @@ +rstack.config.ts +dist-2/ diff --git a/packages/rstack/test/config/reload-app-config/index.test.ts b/packages/rstack/test/config/reload-app-config/index.test.ts new file mode 100644 index 0000000..603631a --- /dev/null +++ b/packages/rstack/test/config/reload-app-config/index.test.ts @@ -0,0 +1,50 @@ +import { rm, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { expectFile, getRandomPort, test } from '#test-helpers'; + +test('should restart dev server and reload config when Rstack config changes', async ({ + execCliAsync, +}) => { + const dist1 = path.join(import.meta.dirname, 'dist'); + const dist2 = path.join(import.meta.dirname, 'dist-2'); + const configFile = path.join(import.meta.dirname, 'rstack.config.ts'); + + await rm(dist1, { recursive: true, force: true }); + await rm(dist2, { recursive: true, force: true }); + await rm(configFile, { force: true }); + + await writeFile( + configFile, + `import { define } from 'rstack'; + +define.app({ + dev: { + writeToDisk: true, + }, + server: { port: ${await getRandomPort()} }, +}); +`, + ); + + execCliAsync('dev'); + + await expectFile(dist1); + + await writeFile( + configFile, + `import { define } from 'rstack'; + +define.app({ + dev: { + writeToDisk: true, + }, + output: { + distPath: 'dist-2', + }, + server: { port: ${await getRandomPort()} }, +}); +`, + ); + + await expectFile(dist2); +}, 30_000); diff --git a/packages/rstack/test/config/reload-app-config/src/index.js b/packages/rstack/test/config/reload-app-config/src/index.js new file mode 100644 index 0000000..ddc67c9 --- /dev/null +++ b/packages/rstack/test/config/reload-app-config/src/index.js @@ -0,0 +1 @@ +console.log('hello!'); From e9f395dfb5ec1fa2c6964efa202b5ec1d52ff458 Mon Sep 17 00:00:00 2001 From: neverland Date: Sat, 11 Jul 2026 18:18:36 +0800 Subject: [PATCH 2/3] refactor: simplify Rstack config loader --- packages/rstack/src/config.ts | 7 +------ packages/rstack/src/rsbuildConfig.ts | 4 ++-- packages/rstack/src/rslibConfig.ts | 2 +- packages/rstack/src/rslintConfig.ts | 2 +- packages/rstack/src/rspressConfig.ts | 2 +- packages/rstack/src/rstestConfig.ts | 2 +- packages/rstack/src/staged.ts | 2 +- 7 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/rstack/src/config.ts b/packages/rstack/src/config.ts index 3f1f70e..dafd575 100644 --- a/packages/rstack/src/config.ts +++ b/packages/rstack/src/config.ts @@ -105,7 +105,7 @@ export const define: Define = { staged: (config) => setConfig('staged', config), }; -export const loadRstackConfigWithMeta = async (): Promise => { +export const loadRstackConfig = async (): Promise => { const state = getConfigState(); state.configs = {}; @@ -135,8 +135,3 @@ export const loadRstackConfigWithMeta = async (): Promise => state.configs = {}; } }; - -export const loadRstackConfig = async (): Promise => { - const { configs } = await loadRstackConfigWithMeta(); - return configs; -}; diff --git a/packages/rstack/src/rsbuildConfig.ts b/packages/rstack/src/rsbuildConfig.ts index 87486f1..1dcc716 100644 --- a/packages/rstack/src/rsbuildConfig.ts +++ b/packages/rstack/src/rsbuildConfig.ts @@ -1,5 +1,5 @@ import { mergeRsbuildConfig, type RsbuildConfigDefinition, type ConfigParams } from '@rsbuild/core'; -import { loadRstackConfigWithMeta, type Configs } from './config.js'; +import { loadRstackConfig, type Configs } from './config.js'; const resolveRsbuildConfig = async (configs: Configs, params: ConfigParams) => { const appConfig = configs.app; @@ -13,7 +13,7 @@ const resolveRsbuildConfig = async (configs: Configs, params: ConfigParams) => { }; const loadRsbuildConfig: RsbuildConfigDefinition = async (params) => { - const { configs, filePath, dependencies } = await loadRstackConfigWithMeta(); + const { configs, filePath, dependencies } = await loadRstackConfig(); const config = await resolveRsbuildConfig(configs, params); if (!filePath) { diff --git a/packages/rstack/src/rslibConfig.ts b/packages/rstack/src/rslibConfig.ts index 4666398..4681d9d 100644 --- a/packages/rstack/src/rslibConfig.ts +++ b/packages/rstack/src/rslibConfig.ts @@ -14,7 +14,7 @@ const resolveRslibConfig = async (configs: Configs, params: ConfigParams): Promi }; const loadRslibConfig = (async (params: ConfigParams) => { - const configs = await loadRstackConfig(); + const { configs } = await loadRstackConfig(); return resolveRslibConfig(configs, params); }) as RslibConfigDefinition; diff --git a/packages/rstack/src/rslintConfig.ts b/packages/rstack/src/rslintConfig.ts index ff9851c..aea6340 100644 --- a/packages/rstack/src/rslintConfig.ts +++ b/packages/rstack/src/rslintConfig.ts @@ -1,7 +1,7 @@ import { loadRstackConfig } from './config.js'; import type { RslintConfig } from '@rslint/core'; -const configs = await loadRstackConfig(); +const { configs } = await loadRstackConfig(); const lintExports = configs.lint ?? []; let lintConfig: RslintConfig; diff --git a/packages/rstack/src/rspressConfig.ts b/packages/rstack/src/rspressConfig.ts index 65c78bc..6c97bf4 100644 --- a/packages/rstack/src/rspressConfig.ts +++ b/packages/rstack/src/rspressConfig.ts @@ -13,6 +13,6 @@ const resolveRspressConfig = async (configs: Configs): Promise => { }; export default async (): Promise => { - const configs = await loadRstackConfig(); + const { configs } = await loadRstackConfig(); return resolveRspressConfig(configs); }; diff --git a/packages/rstack/src/rstestConfig.ts b/packages/rstack/src/rstestConfig.ts index e375063..7c3ed41 100644 --- a/packages/rstack/src/rstestConfig.ts +++ b/packages/rstack/src/rstestConfig.ts @@ -56,7 +56,7 @@ const resolveRstestConfig = async (configs: Configs) => { }; const loadRstestConfig = (async (params: ConfigParams) => { - const configs = await loadRstackConfig(); + const { configs } = await loadRstackConfig(); const testConfig = await resolveRstestConfig(configs); return extendsConfig(configs, testConfig, params); }) as RstestConfigExport; diff --git a/packages/rstack/src/staged.ts b/packages/rstack/src/staged.ts index c45513a..c0af58c 100644 --- a/packages/rstack/src/staged.ts +++ b/packages/rstack/src/staged.ts @@ -65,7 +65,7 @@ export async function runStagedCLI(args: string[]): Promise { return; } - const configs = await loadRstackConfig(); + const { configs } = await loadRstackConfig(); const stagedConfig = configs.staged; if (!stagedConfig) { throw new Error( From a2813d6e41792067f88862dce8949f8a0d406630 Mon Sep 17 00:00:00 2001 From: neverland Date: Sat, 11 Jul 2026 22:22:16 +0800 Subject: [PATCH 3/3] test: use shared ignore rules --- .gitignore | 1 + packages/rstack/test/config/reload-app-config/.gitignore | 2 -- packages/rstack/test/config/reload-app-config/index.test.ts | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 packages/rstack/test/config/reload-app-config/.gitignore diff --git a/.gitignore b/.gitignore index bda5db4..c411c06 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Dist node_modules dist/ +dist-*/ test-results doc_build diff --git a/packages/rstack/test/config/reload-app-config/.gitignore b/packages/rstack/test/config/reload-app-config/.gitignore deleted file mode 100644 index b944a20..0000000 --- a/packages/rstack/test/config/reload-app-config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -rstack.config.ts -dist-2/ diff --git a/packages/rstack/test/config/reload-app-config/index.test.ts b/packages/rstack/test/config/reload-app-config/index.test.ts index 603631a..b3c8687 100644 --- a/packages/rstack/test/config/reload-app-config/index.test.ts +++ b/packages/rstack/test/config/reload-app-config/index.test.ts @@ -7,7 +7,7 @@ test('should restart dev server and reload config when Rstack config changes', a }) => { const dist1 = path.join(import.meta.dirname, 'dist'); const dist2 = path.join(import.meta.dirname, 'dist-2'); - const configFile = path.join(import.meta.dirname, 'rstack.config.ts'); + const configFile = path.join(import.meta.dirname, 'test-temp-rstack.config.ts'); await rm(dist1, { recursive: true, force: true }); await rm(dist2, { recursive: true, force: true }); @@ -26,7 +26,7 @@ define.app({ `, ); - execCliAsync('dev'); + execCliAsync('dev --config test-temp-rstack.config.ts'); await expectFile(dist1);