diff --git a/docs/content/3.api/1.composables/update-app-config.md b/docs/content/3.api/1.composables/update-app-config.md new file mode 100644 index 0000000000..9d7eecb93e --- /dev/null +++ b/docs/content/3.api/1.composables/update-app-config.md @@ -0,0 +1,20 @@ +# `updateAppConfig` + +::StabilityEdge +:: + +Updates [app config](/guide/features/app-config) using deep assignment. Existing (nested) properties will be preserved. + +**Usage:** + +```js +const appConfig = useAppConfig() // { foo: 'bar' } + +const newAppConfig = { foo: 'baz' } + +updateAppConfig(newAppConfig) + +console.log(appConfig) // { foo: 'baz' } +``` + +::ReadMore{link="/guide/features/app-config"} diff --git a/examples/advanced/config-extends/app.config.ts b/examples/advanced/config-extends/app.config.ts index 909ae0dc97..6ffbebb636 100644 --- a/examples/advanced/config-extends/app.config.ts +++ b/examples/advanced/config-extends/app.config.ts @@ -1,5 +1,10 @@ export default defineAppConfig({ foo: 'user', bar: 'user', - baz: 'base' + baz: 'base', + array: [ + 'user', + 'user', + 'user' + ] }) diff --git a/examples/advanced/config-extends/base/app.config.ts b/examples/advanced/config-extends/base/app.config.ts index b94ce0b0b0..e55c3765c3 100644 --- a/examples/advanced/config-extends/base/app.config.ts +++ b/examples/advanced/config-extends/base/app.config.ts @@ -1,4 +1,18 @@ export default defineAppConfig({ bar: 'base', - baz: 'base' + baz: 'base', + array: () => [ + 'base', + 'base', + 'base' + ], + arrayNested: { + nested: { + array: [ + 'base', + 'base', + 'base' + ] + } + } }) diff --git a/packages/nuxt/src/app/config.ts b/packages/nuxt/src/app/config.ts index b8d504a072..84f93a1627 100644 --- a/packages/nuxt/src/app/config.ts +++ b/packages/nuxt/src/app/config.ts @@ -4,9 +4,35 @@ import { useNuxtApp } from './nuxt' // @ts-ignore import __appConfig from '#build/app.config.mjs' +type DeepPartial = T extends Function ? T : T extends Record ? { [P in keyof T]?: DeepPartial } : T + // Workaround for vite HMR with virtual modules export const _getAppConfig = () => __appConfig as AppConfig +function deepDelete (obj: any, newObj: any) { + for (const key in obj) { + const val = newObj[key] + if (!(key in newObj)) { + delete (obj as any)[key] + } + + if (val !== null && typeof val === 'object') { + deepDelete(obj[key], newObj[key]) + } + } +} + +function deepAssign (obj: any, newObj: any) { + for (const key in newObj) { + const val = newObj[key] + if (val !== null && typeof val === 'object') { + deepAssign(obj[key], val) + } else { + obj[key] = val + } + } +} + export function useAppConfig (): AppConfig { const nuxtApp = useNuxtApp() if (!nuxtApp._appConfig) { @@ -15,28 +41,23 @@ export function useAppConfig (): AppConfig { return nuxtApp._appConfig } +/** + * Deep assign the current appConfig with the new one. + * + * Will preserve existing properties. + */ +export function updateAppConfig (appConfig: DeepPartial) { + const _appConfig = useAppConfig() + deepAssign(_appConfig, appConfig) +} + // HMR Support if (process.dev) { function applyHMR (newConfig: AppConfig) { const appConfig = useAppConfig() if (newConfig && appConfig) { deepAssign(appConfig, newConfig) - for (const key in appConfig) { - if (!(key in newConfig)) { - delete (appConfig as any)[key] - } - } - } - } - - function deepAssign (obj: any, newObj: any) { - for (const key in newObj) { - const val = newObj[key] - if (val !== null && typeof val === 'object') { - deepAssign(obj[key], val) - } else { - obj[key] = val - } + deepDelete(appConfig, newConfig) } } diff --git a/packages/nuxt/src/core/templates.ts b/packages/nuxt/src/core/templates.ts index cc5dc9055d..8d0e8334fd 100644 --- a/packages/nuxt/src/core/templates.ts +++ b/packages/nuxt/src/core/templates.ts @@ -207,12 +207,13 @@ export const appConfigTemplate: NuxtTemplate = { write: true, getContents: ({ app, nuxt }) => { return ` -import defu from 'defu' +import { defuFn } from 'defu' const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)} ${app.configs.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id)}`).join('\n')} -export default defu(${app.configs.map((_id: string, index: number) => `cfg${index}`).concat(['inlineConfig']).join(', ')}) + +export default defuFn(${app.configs.map((_id: string, index: number) => `cfg${index}`).concat(['inlineConfig']).join(', ')}) ` } } diff --git a/packages/nuxt/src/imports/presets.ts b/packages/nuxt/src/imports/presets.ts index 5655717733..6985af5b27 100644 --- a/packages/nuxt/src/imports/presets.ts +++ b/packages/nuxt/src/imports/presets.ts @@ -51,6 +51,7 @@ const appPreset = defineUnimportPreset({ 'createError', 'defineNuxtLink', 'useAppConfig', + 'updateAppConfig', 'defineAppConfig', 'preloadComponents', 'prefetchComponents'