@@ -13,37 +13,35 @@ import type { UserConfig, UserConfigAsyncFn } from '@rspress/core';
1313import type { RstestConfigExport } from '@rstest/core' ;
1414import type { FmtConfigDefinition } from './fmt/types.ts' ;
1515import type { StagedConfig } from './staged.ts' ;
16- import { composeConfigLayers } from './configLayers.ts' ;
16+ import { composeConfigLayers , flattenConfigLayers } from './configLayers.ts' ;
1717
18- export type RslintConfigDefinition =
19- RslintConfig | ( ( ) => Promise < RslintConfig > ) ;
2018export type RspressConfigDefinition = UserConfig | UserConfigAsyncFn ;
2119
2220type RslintConfigFactory = (
2321 lint : typeof import ( '@rslint/core' ) ,
2422) => RslintConfig | Promise < RslintConfig > ;
2523
26- type RslintConfigInput = RslintConfig | RslintConfigFactory ;
24+ export type RslintConfigDefinition = RslintConfig | RslintConfigFactory ;
2725
2826export type Configs = {
2927 app ?: RsbuildConfigDefinition ;
3028 lib ?: RslibConfigDefinition ;
3129 doc ?: RspressConfigDefinition ;
3230 test ?: RstestConfigExport ;
33- lint ?: RslintConfigDefinition ;
31+ lint ?: RslintConfig | ( ( ) => Promise < RslintConfig > ) ;
3432 fmt ?: FmtConfigDefinition ;
3533 staged ?: StagedConfig ;
3634} ;
3735
3836/** Shared configuration input; lint factories receive the tool exports. */
3937export type RstackConfig = Omit < Configs , 'lint' > & {
4038 extends ?: readonly RstackConfig [ ] ;
41- lint ?: RslintConfigInput ;
39+ lint ?: RslintConfigDefinition ;
4240} ;
4341
4442const normalizeLintConfig = (
45- config : RslintConfigInput ,
46- ) : RslintConfigDefinition =>
43+ config : RslintConfigDefinition ,
44+ ) : NonNullable < Configs [ 'lint' ] > =>
4745 typeof config === 'function'
4846 ? async ( ) => config ( await import ( '@rslint/core' ) )
4947 : config ;
@@ -82,6 +80,7 @@ export type LoadRstackConfigOptions = {
8280
8381type ConfigSession = {
8482 configs : Configs ;
83+ extends ?: readonly RstackConfig [ ] ;
8584 active : boolean ;
8685} ;
8786
@@ -125,6 +124,13 @@ export const getConfigState = (): ConfigState => {
125124} ;
126125
127126type Define = {
127+ /**
128+ * Inherits shared configs from left to right, followed by the project's own
129+ * definitions regardless of call order. May only be called once per config load.
130+ *
131+ * @see {@link https://rstack.rs/config | Configuration guide }
132+ */
133+ extends : ( configs : readonly RstackConfig [ ] ) => void ;
128134 /**
129135 * Defines the Rsbuild config for the app.
130136 *
@@ -169,7 +175,7 @@ type Define = {
169175 *
170176 * @see {@link https://rstack.rs/config | Configuration guide }
171177 */
172- lint : ( config : RslintConfigInput ) => void ;
178+ lint : ( config : RslintConfigDefinition ) => void ;
173179 /**
174180 * Defines the Prettier config for formatting.
175181 *
@@ -207,6 +213,18 @@ const setConfig = <T extends keyof Configs>(
207213} ;
208214
209215export const define : Define = {
216+ extends : ( configs ) => {
217+ const session = getConfigSessionStorage ( ) . getStore ( ) ;
218+ if ( ! session ?. active ) {
219+ throw new Error (
220+ 'The "extends" config must be defined while loading an Rstack config.' ,
221+ ) ;
222+ }
223+ if ( session . extends !== undefined ) {
224+ throw new Error ( 'The "extends" config has already been defined.' ) ;
225+ }
226+ session . extends = configs ;
227+ } ,
210228 app : ( ( config ) => {
211229 setConfig ( 'app' , config ) ;
212230 return config ;
@@ -253,13 +271,19 @@ export const loadRstackConfig = async ({
253271 } ) ;
254272
255273 return {
256- configs : await composeConfigLayers ( [ session . configs ] ) ,
274+ configs : session . extends ?. length
275+ ? await composeConfigLayers ( [
276+ ...flattenConfigLayers ( session . extends ) ,
277+ session . configs ,
278+ ] )
279+ : session . configs ,
257280 filePath,
258281 dependencies,
259282 } ;
260283 } finally {
261284 session . active = false ;
262285 session . configs = { } ;
286+ session . extends = undefined ;
263287 }
264288 } ) ;
265289} ;
0 commit comments