@@ -2,7 +2,11 @@ import path from 'path'
2
2
import fs from 'fs-extra'
3
3
import chalk from 'chalk'
4
4
import globby from 'globby'
5
- import { AliasOptions , UserConfig as ViteConfig } from 'vite'
5
+ import {
6
+ AliasOptions ,
7
+ UserConfig as ViteConfig ,
8
+ mergeConfig as mergeViteConfig
9
+ } from 'vite'
6
10
import { Options as VuePluginOptions } from '@vitejs/plugin-vue'
7
11
import {
8
12
SiteData ,
@@ -47,8 +51,15 @@ export interface UserConfig<ThemeConfig = any> {
47
51
* @deprecated use `vue` instead
48
52
*/
49
53
vueOptions ?: VuePluginOptions
54
+
55
+ extends ?: RawConfigExports
50
56
}
51
57
58
+ type RawConfigExports =
59
+ | UserConfig
60
+ | Promise < UserConfig >
61
+ | ( ( ) => UserConfig | Promise < UserConfig > )
62
+
52
63
export interface SiteConfig < ThemeConfig = any > {
53
64
root : string
54
65
srcDir : string
@@ -122,16 +133,53 @@ export async function resolveUserConfig(root: string): Promise<UserConfig> {
122
133
const hasUserConfig = await fs . pathExists ( configPath )
123
134
// always delete cache first before loading config
124
135
delete require . cache [ configPath ]
125
- const userConfig : UserConfig | ( ( ) => UserConfig ) = hasUserConfig
126
- ? require ( configPath )
127
- : { }
136
+ const userConfig : RawConfigExports = hasUserConfig ? require ( configPath ) : { }
128
137
if ( hasUserConfig ) {
129
138
debug ( `loaded config at ${ chalk . yellow ( configPath ) } ` )
130
139
} else {
131
140
debug ( `no config file found.` )
132
141
}
142
+ return resolveConfigExtends ( userConfig )
143
+ }
144
+
145
+ async function resolveConfigExtends (
146
+ config : RawConfigExports
147
+ ) : Promise < UserConfig > {
148
+ const resolved = await ( typeof config === 'function' ? config ( ) : config )
149
+ if ( resolved . extends ) {
150
+ const base = await resolveConfigExtends ( resolved . extends )
151
+ return mergeConfig ( base , resolved )
152
+ }
153
+ return resolved
154
+ }
155
+
156
+ function mergeConfig ( a : UserConfig , b : UserConfig , isRoot = true ) {
157
+ const merged : Record < string , any > = { ...a }
158
+ for ( const key in b ) {
159
+ const value = b [ key as keyof UserConfig ]
160
+ if ( value == null ) {
161
+ continue
162
+ }
163
+ const existing = merged [ key ]
164
+ if ( Array . isArray ( existing ) && Array . isArray ( value ) ) {
165
+ merged [ key ] = [ ...existing , ...value ]
166
+ continue
167
+ }
168
+ if ( isObject ( existing ) && isObject ( value ) ) {
169
+ if ( isRoot && key === 'vite' ) {
170
+ merged [ key ] = mergeViteConfig ( existing , value )
171
+ } else {
172
+ merged [ key ] = mergeConfig ( existing , value , false )
173
+ }
174
+ continue
175
+ }
176
+ merged [ key ] = value
177
+ }
178
+ return merged
179
+ }
133
180
134
- return typeof userConfig === 'function' ? userConfig ( ) : userConfig
181
+ function isObject ( value : unknown ) : value is Record < string , any > {
182
+ return Object . prototype . toString . call ( value ) === '[object Object]'
135
183
}
136
184
137
185
export async function resolveSiteData (
0 commit comments