From f749b272d4603a3b8eaf251b0feebe2d33da3983 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 8 Sep 2021 14:18:09 -0400 Subject: [PATCH] feat: support config.extends --- src/node/config.ts | 58 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/src/node/config.ts b/src/node/config.ts index a7b052b579b1..09337fbbce26 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -2,7 +2,11 @@ import path from 'path' import fs from 'fs-extra' import chalk from 'chalk' import globby from 'globby' -import { AliasOptions, UserConfig as ViteConfig } from 'vite' +import { + AliasOptions, + UserConfig as ViteConfig, + mergeConfig as mergeViteConfig +} from 'vite' import { Options as VuePluginOptions } from '@vitejs/plugin-vue' import { SiteData, @@ -47,8 +51,15 @@ export interface UserConfig { * @deprecated use `vue` instead */ vueOptions?: VuePluginOptions + + extends?: RawConfigExports } +type RawConfigExports = + | UserConfig + | Promise + | (() => UserConfig | Promise) + export interface SiteConfig { root: string srcDir: string @@ -122,16 +133,53 @@ export async function resolveUserConfig(root: string): Promise { const hasUserConfig = await fs.pathExists(configPath) // always delete cache first before loading config delete require.cache[configPath] - const userConfig: UserConfig | (() => UserConfig) = hasUserConfig - ? require(configPath) - : {} + const userConfig: RawConfigExports = hasUserConfig ? require(configPath) : {} if (hasUserConfig) { debug(`loaded config at ${chalk.yellow(configPath)}`) } else { debug(`no config file found.`) } + return resolveConfigExtends(userConfig) +} + +async function resolveConfigExtends( + config: RawConfigExports +): Promise { + const resolved = await (typeof config === 'function' ? config() : config) + if (resolved.extends) { + const base = await resolveConfigExtends(resolved.extends) + return mergeConfig(base, resolved) + } + return resolved +} + +function mergeConfig(a: UserConfig, b: UserConfig, isRoot = true) { + const merged: Record = { ...a } + for (const key in b) { + const value = b[key as keyof UserConfig] + if (value == null) { + continue + } + const existing = merged[key] + if (Array.isArray(existing) && Array.isArray(value)) { + merged[key] = [...existing, ...value] + continue + } + if (isObject(existing) && isObject(value)) { + if (isRoot && key === 'vite') { + merged[key] = mergeViteConfig(existing, value) + } else { + merged[key] = mergeConfig(existing, value, false) + } + continue + } + merged[key] = value + } + return merged +} - return typeof userConfig === 'function' ? userConfig() : userConfig +function isObject(value: unknown): value is Record { + return Object.prototype.toString.call(value) === '[object Object]' } export async function resolveSiteData(