@@ -94,22 +94,29 @@ export function resolveSiteDataByRoute(
94
94
relativePath : string
95
95
) : SiteData {
96
96
const localeIndex = getLocaleForPath ( siteData , relativePath )
97
-
98
- return Object . assign ( { } , siteData , {
99
- localeIndex,
100
- lang : siteData . locales [ localeIndex ] ?. lang ?? siteData . lang ,
101
- dir : siteData . locales [ localeIndex ] ?. dir ?? siteData . dir ,
102
- title : siteData . locales [ localeIndex ] ?. title ?? siteData . title ,
103
- titleTemplate :
104
- siteData . locales [ localeIndex ] ?. titleTemplate ?? siteData . titleTemplate ,
105
- description :
106
- siteData . locales [ localeIndex ] ?. description ?? siteData . description ,
107
- head : mergeHead ( siteData . head , siteData . locales [ localeIndex ] ?. head ?? [ ] ) ,
108
- themeConfig : {
109
- ...siteData . themeConfig ,
110
- ...siteData . locales [ localeIndex ] ?. themeConfig
111
- }
112
- } )
97
+ const { label, link, ...localeConfig } = siteData . locales [ localeIndex ] ?? { }
98
+ const additionalConfigs = resolveAdditionalConfig ( siteData , relativePath )
99
+ if ( inBrowser && ( import . meta as any ) . env ?. DEV ) {
100
+ ; ( localeConfig as any ) [ VP_SOURCE_KEY ] = `locale config (${ localeIndex } )`
101
+ reportConfigLayers ( relativePath , [
102
+ ...additionalConfigs ,
103
+ localeConfig as SiteData ,
104
+ siteData
105
+ ] )
106
+ }
107
+ const topLayer = {
108
+ head : mergeHead (
109
+ siteData . head ?? [ ] ,
110
+ localeConfig . head ?? [ ] ,
111
+ ...additionalConfigs . map ( ( data ) => data ?. head ?? [ ] ) . reverse ( )
112
+ )
113
+ } as SiteData
114
+ return stackView < SiteData > (
115
+ topLayer ,
116
+ ...additionalConfigs ,
117
+ localeConfig ,
118
+ siteData
119
+ )
113
120
}
114
121
115
122
/**
@@ -161,8 +168,18 @@ function hasTag(head: HeadConfig[], tag: HeadConfig) {
161
168
)
162
169
}
163
170
164
- export function mergeHead ( prev : HeadConfig [ ] , curr : HeadConfig [ ] ) {
165
- return [ ...prev . filter ( ( tagAttrs ) => ! hasTag ( curr , tagAttrs ) ) , ...curr ]
171
+ export function mergeHead ( current : HeadConfig [ ] , ...incoming : HeadConfig [ ] [ ] ) {
172
+ return incoming
173
+ . filter ( ( el ) => Array . isArray ( el ) && el . length > 0 )
174
+ . flat ( 1 )
175
+ . reverse ( )
176
+ . reduce (
177
+ ( merged , tag ) => {
178
+ if ( ! hasTag ( merged , tag ) ) merged . push ( tag )
179
+ return merged
180
+ } ,
181
+ [ ...current ]
182
+ )
166
183
}
167
184
168
185
// https://github.com/rollup/rollup/blob/fec513270c6ac350072425cc045db367656c623b/src/utils/sanitizeFileName.ts
@@ -230,3 +247,91 @@ export function escapeHtml(str: string): string {
230
247
. replace ( / " / g, '"' )
231
248
. replace ( / & (? ! [ \w # ] + ; ) / g, '&' )
232
249
}
250
+
251
+ export function resolveAdditionalConfig ( site : SiteData , path : string ) {
252
+ if ( ! path . startsWith ( '/' ) ) path = `/${ path } `
253
+ const additionalConfig = site . additionalConfig
254
+ if ( additionalConfig === undefined ) return [ ]
255
+ else if ( typeof additionalConfig === 'function' )
256
+ return additionalConfig ( path ) as SiteData [ ]
257
+ const configs : SiteData [ ] = [ ]
258
+ const segments = path . split ( '/' ) . slice ( 1 , - 1 )
259
+ while ( segments . length ) {
260
+ const key = `/${ segments . join ( '/' ) } /`
261
+ if ( key in additionalConfig ) configs . push ( additionalConfig [ key ] as SiteData )
262
+ segments . pop ( )
263
+ }
264
+ if ( '/' in additionalConfig ) configs . push ( additionalConfig [ '/' ] as SiteData )
265
+ return configs
266
+ }
267
+
268
+ export const VP_SOURCE_KEY = '[VP_SOURCE]'
269
+
270
+ function reportConfigLayers ( path : string , layers : SiteData [ ] ) {
271
+ // This helps users to understand which configuration files are active
272
+ const summaryTitle = `Config Layers for ${ path } :`
273
+ const summary = layers . map ( ( c , i , arr ) => {
274
+ const n = i + 1
275
+ if ( n === arr . length ) return `${ n } . .vitepress/config (root)`
276
+ return `${ n } . ${ ( c as any ) ?. [ VP_SOURCE_KEY ] ?? '(Unknown Source)' } `
277
+ } )
278
+ console . debug (
279
+ [ summaryTitle , '' . padEnd ( summaryTitle . length , '=' ) , ...summary ] . join ( '\n' )
280
+ )
281
+ }
282
+
283
+ /**
284
+ * Creates a deep, merged view of multiple objects without mutating originals.
285
+ * Returns a readonly proxy behaving like a merged object of the input objects.
286
+ * Layers are merged in descending precedence, i.e. earlier layer is on top.
287
+ */
288
+ export function stackView < T extends object > ( ...layers : Partial < T > [ ] ) : T {
289
+ layers = layers . filter ( ( layer ) => layer !== undefined )
290
+ if ( ! isStackable ( layers [ 0 ] ) ) return layers [ 0 ] as T
291
+ layers = layers . filter ( isStackable )
292
+ if ( layers . length <= 1 ) return layers [ 0 ] as T
293
+ return new Proxy (
294
+ { } ,
295
+ {
296
+ get ( _ , key ) {
297
+ return key === UnpackStackView
298
+ ? layers
299
+ : stackView ( ...layers . map ( ( layer ) => ( layer as any ) ?. [ key ] ) )
300
+ } ,
301
+ set ( _ , key , value ) {
302
+ throw new Error ( 'StackView is read-only and cannot be mutated.' )
303
+ } ,
304
+ has ( _ , key ) {
305
+ for ( const layer of layers ) {
306
+ if ( key in layer ) return true
307
+ }
308
+ return false
309
+ } ,
310
+ ownKeys ( _ ) {
311
+ const keys = new Set < string > ( )
312
+ for ( const layer of layers ) {
313
+ for ( const key of Object . keys ( layer ) ) {
314
+ keys . add ( key )
315
+ }
316
+ }
317
+ return Array . from ( keys )
318
+ } ,
319
+ getOwnPropertyDescriptor ( _ , key ) {
320
+ for ( const layer of layers ) {
321
+ if ( key in layer ) {
322
+ return Object . getOwnPropertyDescriptor ( layer , key )
323
+ }
324
+ }
325
+ }
326
+ }
327
+ ) as T
328
+ }
329
+
330
+ function isStackable ( obj : any ) {
331
+ return typeof obj === 'object' && obj !== null && ! Array . isArray ( obj )
332
+ }
333
+
334
+ const UnpackStackView = Symbol ( 'stack-view:unpack' )
335
+ stackView . unpack = function < T > ( obj : T ) : T [ ] | undefined {
336
+ return ( obj as any ) ?. [ UnpackStackView ]
337
+ }
0 commit comments