diff --git a/packages/rstack/src/config.ts b/packages/rstack/src/config.ts index f806237f..e6c2ceb0 100644 --- a/packages/rstack/src/config.ts +++ b/packages/rstack/src/config.ts @@ -1,5 +1,5 @@ import { AsyncLocalStorage } from 'node:async_hooks'; -import { loadConfig } from '@rstackjs/load-config'; +import { loadConfig, setJitiLoader } from '@rstackjs/load-config'; import type { defineConfig as defineAppConfig, RsbuildConfigDefinition, @@ -15,6 +15,23 @@ import type { FmtConfigDefinition } from './fmt/types.ts'; import type { StagedConfig } from './staged.ts'; import { composeConfigLayers, flattenConfigLayers } from './configLayers.ts'; +setJitiLoader(async () => { + const { createJiti } = await import('@rsbuild/core/jiti'); + + return { + createJiti: (...args: Parameters) => { + const session = getConfigSessionStorage().getStore(); + if (session?.active) { + // A native attempt may have registered configs before falling back. + // Reset per attempt, since the jiti importer itself is cached. + session.configs = {}; + session.extends = undefined; + } + return createJiti(...args); + }, + }; +}); + export type RspressConfigDefinition = UserConfig | UserConfigAsyncFn; type RslintConfigFactory = ( @@ -254,7 +271,7 @@ export const loadRstackConfig = async ({ return getConfigSessionStorage().run(session, async () => { try { const { filePath, dependencies } = await loadConfig({ - loader: 'native', + loader: 'auto', exportName: false, fresh: true, cwd, diff --git a/packages/rstack/tests/config/load-config/fallback.config.ts b/packages/rstack/tests/config/load-config/fallback.config.ts new file mode 100644 index 00000000..d2477808 --- /dev/null +++ b/packages/rstack/tests/config/load-config/fallback.config.ts @@ -0,0 +1,6 @@ +import { define } from 'rstack'; + +define.extends([{ fmt: { singleQuote: true } }]); +define.lib({}); + +await import('./jiti.config.ts'); diff --git a/packages/rstack/tests/config/load-config/index.test.ts b/packages/rstack/tests/config/load-config/index.test.ts index b6ec8f01..2f7c5da7 100644 --- a/packages/rstack/tests/config/load-config/index.test.ts +++ b/packages/rstack/tests/config/load-config/index.test.ts @@ -58,6 +58,24 @@ test('should prefer an explicit config path over the state config path', async ( expect(filePath).toBe(explicitConfigPath); }); +test('should fall back to jiti for TypeScript syntax unsupported by the native loader', async () => { + const { configs } = await loadConfigFile('jiti.config.ts'); + + expect(configs.app).toEqual({ html: { title: 'jiti config' } }); +}); + +test('should avoid duplicate definitions when falling back to jiti across repeated loads', async () => { + for (let attempt = 0; attempt < 2; attempt++) { + const { configs } = await loadConfigFile('fallback.config.ts'); + + expect(configs).toEqual({ + app: { html: { title: 'jiti config' } }, + lib: {}, + fmt: { singleQuote: true }, + }); + } +}); + test('should resolve a relative explicit config path from cwd', async () => { const { configs, filePath } = await loadRstackConfig({ configFilePath: 'explicit.config.ts', diff --git a/packages/rstack/tests/config/load-config/jiti.config.ts b/packages/rstack/tests/config/load-config/jiti.config.ts new file mode 100644 index 00000000..5762e944 --- /dev/null +++ b/packages/rstack/tests/config/load-config/jiti.config.ts @@ -0,0 +1,7 @@ +import { define } from 'rstack'; + +enum Title { + App = 'jiti config', +} + +define.app({ html: { title: Title.App } });