mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nuxt): app.config
with hmr and reactivity support (#6333)
Co-authored-by: Daniel Roe <daniel@roe.dev>
This commit is contained in:
parent
405629dc35
commit
94f76ea930
55
docs/content/2.guide/2.features/10.app-config.md
Normal file
55
docs/content/2.guide/2.features/10.app-config.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# App Config
|
||||||
|
|
||||||
|
::StabilityEdge
|
||||||
|
::
|
||||||
|
|
||||||
|
Nuxt 3 provides an `app.config` config file to expose reactive configuration within your application with the ability to update it at runtime within lifecycle or using a nuxt plugin and editing it with HMR (hot-module-replacement).
|
||||||
|
|
||||||
|
::alert{type=warning}
|
||||||
|
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
|
||||||
|
::
|
||||||
|
|
||||||
|
## Defining App Config
|
||||||
|
|
||||||
|
To expose config and environment variables to the rest of your app, you will need to define configuration in `app.config` file.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```ts [app.config.ts]
|
||||||
|
export default defineAppConfig({
|
||||||
|
theme: {
|
||||||
|
primaryColor: '#ababab'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When adding `theme` to the `app.config`, Nuxt uses Vite or Webpack to bundle the code. We can universally access `theme` in both server and browser using [useAppConfig](/api/composables/use-app-config) composable.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
|
||||||
|
console.log(appConfig.theme)
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- TODO: Document module author for extension -->
|
||||||
|
|
||||||
|
### Manually Typing App Config
|
||||||
|
|
||||||
|
Nuxt tries to automatically generate a typescript interface from provided app config.
|
||||||
|
|
||||||
|
It is also possible to type app config manually:
|
||||||
|
|
||||||
|
```ts [index.d.ts]
|
||||||
|
declare module '@nuxt/schema' {
|
||||||
|
interface AppConfig {
|
||||||
|
/** Theme configuration */
|
||||||
|
theme: {
|
||||||
|
/** Primary app color */
|
||||||
|
primaryColor: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is always important to ensure you import/export something when augmenting a type
|
||||||
|
export {}
|
||||||
|
```
|
24
docs/content/2.guide/3.directory-structure/16.app.config.md
Normal file
24
docs/content/2.guide/3.directory-structure/16.app.config.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
icon: IconFile
|
||||||
|
title: app.config.ts
|
||||||
|
head.title: Nuxt App Config
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nuxt App Config
|
||||||
|
|
||||||
|
::StabilityEdge
|
||||||
|
::
|
||||||
|
|
||||||
|
You can easily provide runtime app configuration using `app.config.ts` file. It can have either of `.ts`, `.js`, or `.mjs` extensions.
|
||||||
|
|
||||||
|
```ts [app.config.ts]
|
||||||
|
export default defineAppConfig({
|
||||||
|
foo: 'bar'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
::alert{type=warning}
|
||||||
|
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
|
||||||
|
::
|
||||||
|
|
||||||
|
::ReadMore{link="/guide/features/app-config"}
|
16
docs/content/3.api/1.composables/use-app-config.md
Normal file
16
docs/content/3.api/1.composables/use-app-config.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# `useAppConfig`
|
||||||
|
|
||||||
|
::StabilityEdge
|
||||||
|
::
|
||||||
|
|
||||||
|
Access [app config](/guide/features/app-config):
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
|
||||||
|
console.log(appConfig)
|
||||||
|
```
|
||||||
|
|
||||||
|
::ReadMore{link="/guide/features/app-config"}
|
5
examples/advanced/config-extends/app.config.ts
Normal file
5
examples/advanced/config-extends/app.config.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default defineAppConfig({
|
||||||
|
foo: 'user',
|
||||||
|
bar: 'user',
|
||||||
|
baz: 'base'
|
||||||
|
})
|
4
examples/advanced/config-extends/base/app.config.ts
Normal file
4
examples/advanced/config-extends/base/app.config.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default defineAppConfig({
|
||||||
|
bar: 'base',
|
||||||
|
baz: 'base'
|
||||||
|
})
|
@ -1,12 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const themeConfig = useRuntimeConfig().theme
|
const themeConfig = useRuntimeConfig().theme
|
||||||
|
const appConfig = useAppConfig()
|
||||||
const foo = useFoo()
|
const foo = useFoo()
|
||||||
const bar = getBar()
|
const bar = getBar()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NuxtExampleLayout example="advanced/config-extends">
|
<NuxtExampleLayout example="advanced/config-extends">
|
||||||
theme runtimeConfig
|
appConfig:
|
||||||
|
<pre>{{ JSON.stringify(appConfig, null, 2) }}</pre>
|
||||||
|
runtimeConfig:
|
||||||
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
|
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
|
||||||
<BaseButton>Base Button</BaseButton>
|
<BaseButton>Base Button</BaseButton>
|
||||||
<FancyButton>Fancy Button</FancyButton>
|
<FancyButton>Fancy Button</FancyButton>
|
||||||
|
@ -132,7 +132,7 @@ export default defineNuxtCommand({
|
|||||||
dLoad(true, `Directory \`${relativePath}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
|
dLoad(true, `Directory \`${relativePath}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
|
||||||
}
|
}
|
||||||
} else if (isFileChange) {
|
} else if (isFileChange) {
|
||||||
if (file.match(/(app|error)\.(js|ts|mjs|jsx|tsx|vue)$/)) {
|
if (file.match(/(app|error|app\.config)\.(js|ts|mjs|jsx|tsx|vue)$/)) {
|
||||||
dLoad(true, `\`${relativePath}\` ${event === 'add' ? 'created' : 'removed'}`)
|
dLoad(true, `\`${relativePath}\` ${event === 'add' ? 'created' : 'removed'}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
packages/nuxt/src/app/config.ts
Normal file
48
packages/nuxt/src/app/config.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { useNuxtApp } from './nuxt'
|
||||||
|
// @ts-ignore
|
||||||
|
import __appConfig from '#build/app.config.mjs'
|
||||||
|
|
||||||
|
// Workaround for vite HMR with virtual modules
|
||||||
|
export const _getAppConfig = () => __appConfig as AppConfig
|
||||||
|
|
||||||
|
export function useAppConfig (): AppConfig {
|
||||||
|
const nuxtApp = useNuxtApp()
|
||||||
|
if (!nuxtApp._appConfig) {
|
||||||
|
nuxtApp._appConfig = reactive(__appConfig) as AppConfig
|
||||||
|
}
|
||||||
|
return nuxtApp._appConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMR Support
|
||||||
|
if (process.dev) {
|
||||||
|
function applyHMR (newConfig: AppConfig) {
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
if (newConfig && appConfig) {
|
||||||
|
for (const key in newConfig) {
|
||||||
|
(appConfig as any)[key] = (newConfig as any)[key]
|
||||||
|
}
|
||||||
|
for (const key in appConfig) {
|
||||||
|
if (!(key in newConfig)) {
|
||||||
|
delete (appConfig as any)[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vite
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept((newModule) => {
|
||||||
|
const newConfig = newModule._getAppConfig()
|
||||||
|
applyHMR(newConfig)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webpack
|
||||||
|
if (import.meta.webpackHot) {
|
||||||
|
import.meta.webpackHot.accept('#build/app.config.mjs', () => {
|
||||||
|
applyHMR(__appConfig)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
export * from './nuxt'
|
export * from './nuxt'
|
||||||
export * from './composables'
|
export * from './composables'
|
||||||
export * from './components'
|
export * from './components'
|
||||||
|
export * from './config'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-restricted-paths
|
// eslint-disable-next-line import/no-restricted-paths
|
||||||
export type { PageMeta } from '../pages/runtime'
|
export type { PageMeta } from '../pages/runtime'
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import { getCurrentInstance, reactive } from 'vue'
|
import { getCurrentInstance, reactive } from 'vue'
|
||||||
import type { App, onErrorCaptured, VNode } from 'vue'
|
import type { App, onErrorCaptured, VNode } from 'vue'
|
||||||
import { createHooks, Hookable } from 'hookable'
|
import { createHooks, Hookable } from 'hookable'
|
||||||
import type { RuntimeConfig } from '@nuxt/schema'
|
import type { RuntimeConfig, AppConfigInput } from '@nuxt/schema'
|
||||||
import { getContext } from 'unctx'
|
import { getContext } from 'unctx'
|
||||||
import type { SSRContext } from 'vue-bundle-renderer/runtime'
|
import type { SSRContext } from 'vue-bundle-renderer/runtime'
|
||||||
import type { CompatibilityEvent } from 'h3'
|
import type { CompatibilityEvent } from 'h3'
|
||||||
@ -281,3 +281,7 @@ export function useRuntimeConfig (): RuntimeConfig {
|
|||||||
function defineGetter<K extends string | number | symbol, V> (obj: Record<K, V>, key: K, val: V) {
|
function defineGetter<K extends string | number | symbol, V> (obj: Record<K, V>, key: K, val: V) {
|
||||||
Object.defineProperty(obj, key, { get: () => val })
|
Object.defineProperty(obj, key, { get: () => val })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function defineAppConfig<C extends AppConfigInput> (config: C): C {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { defineUnimportPreset, Preset } from 'unimport'
|
import { defineUnimportPreset, Preset } from 'unimport'
|
||||||
|
|
||||||
export const commonPresets: Preset[] = [
|
const commonPresets: Preset[] = [
|
||||||
// #head
|
// #head
|
||||||
defineUnimportPreset({
|
defineUnimportPreset({
|
||||||
from: '#head',
|
from: '#head',
|
||||||
@ -19,7 +19,7 @@ export const commonPresets: Preset[] = [
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
export const appPreset = defineUnimportPreset({
|
const appPreset = defineUnimportPreset({
|
||||||
from: '#app',
|
from: '#app',
|
||||||
imports: [
|
imports: [
|
||||||
'useAsyncData',
|
'useAsyncData',
|
||||||
@ -49,12 +49,14 @@ export const appPreset = defineUnimportPreset({
|
|||||||
'isNuxtError',
|
'isNuxtError',
|
||||||
'useError',
|
'useError',
|
||||||
'createError',
|
'createError',
|
||||||
'defineNuxtLink'
|
'defineNuxtLink',
|
||||||
|
'useAppConfig',
|
||||||
|
'defineAppConfig'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// vue
|
// vue
|
||||||
export const vuePreset = defineUnimportPreset({
|
const vuePreset = defineUnimportPreset({
|
||||||
from: 'vue',
|
from: 'vue',
|
||||||
imports: [
|
imports: [
|
||||||
// <script setup>
|
// <script setup>
|
||||||
|
@ -115,6 +115,15 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
|||||||
app.middleware = uniqueBy(await resolvePaths(app.middleware, 'path'), 'name')
|
app.middleware = uniqueBy(await resolvePaths(app.middleware, 'path'), 'name')
|
||||||
app.plugins = uniqueBy(await resolvePaths(app.plugins, 'src'), 'src')
|
app.plugins = uniqueBy(await resolvePaths(app.plugins, 'src'), 'src')
|
||||||
|
|
||||||
|
// Resolve app.config
|
||||||
|
app.configs = []
|
||||||
|
for (const config of nuxt.options._layers.map(layer => layer.config)) {
|
||||||
|
const appConfigPath = await findPath(resolve(config.srcDir, 'app.config'))
|
||||||
|
if (appConfigPath) {
|
||||||
|
app.configs.push(appConfigPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extend app
|
// Extend app
|
||||||
await nuxt.callHook('app:resolve', app)
|
await nuxt.callHook('app:resolve', app)
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ async function initNuxt (nuxt: Nuxt) {
|
|||||||
}
|
}
|
||||||
// Add module augmentations directly to NuxtConfig
|
// Add module augmentations directly to NuxtConfig
|
||||||
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') })
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/schema.d.ts') })
|
||||||
|
opts.references.push({ path: resolve(nuxt.options.buildDir, 'types/app.config.d.ts') })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add import protection
|
// Add import protection
|
||||||
|
@ -124,7 +124,7 @@ export const schemaTemplate: NuxtTemplate<TemplateContext> = {
|
|||||||
"declare module '@nuxt/schema' {",
|
"declare module '@nuxt/schema' {",
|
||||||
' interface NuxtConfig {',
|
' interface NuxtConfig {',
|
||||||
...moduleInfo.filter(Boolean).map(meta =>
|
...moduleInfo.filter(Boolean).map(meta =>
|
||||||
` [${genString(meta.configKey)}]?: typeof ${genDynamicImport(meta.importName, { wrapper: false })}.default extends NuxtModule<infer O> ? Partial<O> : Record<string, any>`
|
` [${genString(meta.configKey)}]?: typeof ${genDynamicImport(meta.importName, { wrapper: false })}.default extends NuxtModule<infer O> ? Partial<O> : Record<string, any>`
|
||||||
),
|
),
|
||||||
' }',
|
' }',
|
||||||
generateTypes(resolveSchema(Object.fromEntries(Object.entries(nuxt.options.runtimeConfig).filter(([key]) => key !== 'public'))),
|
generateTypes(resolveSchema(Object.fromEntries(Object.entries(nuxt.options.runtimeConfig).filter(([key]) => key !== 'public'))),
|
||||||
@ -157,7 +157,7 @@ export const layoutTemplate: NuxtTemplate<TemplateContext> = {
|
|||||||
}))
|
}))
|
||||||
return [
|
return [
|
||||||
'import { defineAsyncComponent } from \'vue\'',
|
'import { defineAsyncComponent } from \'vue\'',
|
||||||
`export default ${layoutsObject}`
|
`export default ${layoutsObject}`
|
||||||
].join('\n')
|
].join('\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,7 +184,39 @@ export const useRuntimeConfig = () => window?.__NUXT__?.config || {}
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publicPathTemplate: NuxtTemplate<TemplateContext> = {
|
export const appConfigDeclarationTemplate: NuxtTemplate = {
|
||||||
|
filename: 'types/app.config.d.ts',
|
||||||
|
getContents: ({ app, nuxt }) => {
|
||||||
|
return `
|
||||||
|
import type { Defu } from 'defu'
|
||||||
|
${app.configs.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id.replace(/(?<=\w)\.\w+$/g, ''))}`).join('\n')}
|
||||||
|
|
||||||
|
declare const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
|
||||||
|
type ResolvedAppConfig = Defu<typeof inlineConfig, [${app.configs.map((_id: string, index: number) => `typeof cfg${index}`).join(', ')}]>
|
||||||
|
|
||||||
|
declare module '@nuxt/schema' {
|
||||||
|
interface AppConfig extends ResolvedAppConfig { }
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appConfigTemplate: NuxtTemplate = {
|
||||||
|
filename: 'app.config.mjs',
|
||||||
|
write: true,
|
||||||
|
getContents: ({ app, nuxt }) => {
|
||||||
|
return `
|
||||||
|
import defu 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 const publicPathTemplate: NuxtTemplate = {
|
||||||
filename: 'paths.mjs',
|
filename: 'paths.mjs',
|
||||||
getContents ({ nuxt }) {
|
getContents ({ nuxt }) {
|
||||||
return [
|
return [
|
||||||
|
@ -747,5 +747,16 @@ export default {
|
|||||||
* @version 3
|
* @version 3
|
||||||
* @deprecated Use `runtimeConfig` option with `public` key (`runtimeConfig.public.*`).
|
* @deprecated Use `runtimeConfig` option with `public` key (`runtimeConfig.public.*`).
|
||||||
*/
|
*/
|
||||||
publicRuntimeConfig: {}
|
publicRuntimeConfig: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional app configuration
|
||||||
|
*
|
||||||
|
* For programmatic usage and type support, you can directly provide app config with this option.
|
||||||
|
* It will be merged with `app.config` file as default value.
|
||||||
|
*
|
||||||
|
* @type {typeof import('../src/types/config').AppConfig}
|
||||||
|
* @version 3
|
||||||
|
*/
|
||||||
|
appConfig: {},
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,28 @@ export interface NuxtOptions extends ConfigSchema {
|
|||||||
_layers: NuxtConfigLayer[]
|
_layers: NuxtConfigLayer[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ViteConfig extends ViteUserConfig {
|
||||||
|
/**
|
||||||
|
* Options passed to @vitejs/plugin-vue
|
||||||
|
* @see https://github.com/vitejs/vite/tree/main/packages/plugin-vue
|
||||||
|
*/
|
||||||
|
vue?: VuePluginOptions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundler for dev time server-side rendering.
|
||||||
|
* @default 'vite-node'
|
||||||
|
*/
|
||||||
|
devBundler?: 'vite-node' | 'legacy',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warmup vite entrypoint caches on dev startup.
|
||||||
|
*/
|
||||||
|
warmupEntry?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- Runtime Config --
|
||||||
|
|
||||||
type RuntimeConfigNamespace = Record<string, any>
|
type RuntimeConfigNamespace = Record<string, any>
|
||||||
|
|
||||||
export interface PublicRuntimeConfig extends RuntimeConfigNamespace { }
|
export interface PublicRuntimeConfig extends RuntimeConfigNamespace { }
|
||||||
@ -39,19 +61,14 @@ export interface RuntimeConfig extends PrivateRuntimeConfig, RuntimeConfigNamesp
|
|||||||
public: PublicRuntimeConfig
|
public: PublicRuntimeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViteConfig extends ViteUserConfig {
|
// -- App Config --
|
||||||
/**
|
export interface AppConfigInput extends Record<string, any> {
|
||||||
* Options passed to @vitejs/plugin-vue
|
/** @deprecated reserved */
|
||||||
* @see https://github.com/vitejs/vite/tree/main/packages/plugin-vue
|
private?: never
|
||||||
*/
|
/** @deprecated reserved */
|
||||||
vue?: VuePluginOptions
|
nuxt?: never
|
||||||
/**
|
/** @deprecated reserved */
|
||||||
* Bundler for dev time server-side rendering.
|
nitro?: never
|
||||||
* @default 'vite-node'
|
|
||||||
*/
|
|
||||||
devBundler?: 'vite-node' | 'legacy',
|
|
||||||
/**
|
|
||||||
* Warmup vite entrypoint caches on dev startup.
|
|
||||||
*/
|
|
||||||
warmupEntry?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AppConfig { }
|
||||||
|
@ -60,6 +60,7 @@ export interface NuxtApp {
|
|||||||
layouts: Record<string, NuxtLayout>
|
layouts: Record<string, NuxtLayout>
|
||||||
middleware: NuxtMiddleware[]
|
middleware: NuxtMiddleware[]
|
||||||
templates: NuxtTemplate[]
|
templates: NuxtTemplate[]
|
||||||
|
configs: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type _TemplatePlugin = Omit<NuxtPlugin, 'src'> & NuxtTemplate
|
type _TemplatePlugin = Omit<NuxtPlugin, 'src'> & NuxtTemplate
|
||||||
|
@ -449,4 +449,28 @@ describe('dynamic paths', () => {
|
|||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('restore server', async () => {
|
||||||
|
process.env.NUXT_APP_BASE_URL = undefined
|
||||||
|
process.env.NUXT_APP_CDN_URL = undefined
|
||||||
|
process.env.NUXT_APP_BUILD_ASSETS_DIR = undefined
|
||||||
|
await startServer()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('app config', () => {
|
||||||
|
it('should work', async () => {
|
||||||
|
const html = await $fetch('/app-config')
|
||||||
|
|
||||||
|
const expectedAppConfig = {
|
||||||
|
fromNuxtConfig: true,
|
||||||
|
nested: {
|
||||||
|
val: 2
|
||||||
|
},
|
||||||
|
fromLayer: true,
|
||||||
|
userConfig: 123
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(html).toContain(JSON.stringify(expectedAppConfig))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
6
test/fixtures/basic/app.config.ts
vendored
Normal file
6
test/fixtures/basic/app.config.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default defineAppConfig({
|
||||||
|
userConfig: 123,
|
||||||
|
nested: {
|
||||||
|
val: 2
|
||||||
|
}
|
||||||
|
})
|
3
test/fixtures/basic/extends/bar/app.config.ts
vendored
Normal file
3
test/fixtures/basic/extends/bar/app.config.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
fromLayer: true
|
||||||
|
}
|
6
test/fixtures/basic/nuxt.config.ts
vendored
6
test/fixtures/basic/nuxt.config.ts
vendored
@ -37,5 +37,11 @@ export default defineNuxtConfig({
|
|||||||
experimental: {
|
experimental: {
|
||||||
reactivityTransform: true,
|
reactivityTransform: true,
|
||||||
treeshakeClientOnly: true
|
treeshakeClientOnly: true
|
||||||
|
},
|
||||||
|
appConfig: {
|
||||||
|
fromNuxtConfig: true,
|
||||||
|
nested: {
|
||||||
|
val: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
11
test/fixtures/basic/pages/app-config.vue
vendored
Normal file
11
test/fixtures/basic/pages/app-config.vue
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
App Config:
|
||||||
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
|
<pre v-html="JSON.stringify(appConfig)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const appConfig = useAppConfig()
|
||||||
|
</script>
|
15
test/fixtures/basic/types.ts
vendored
15
test/fixtures/basic/types.ts
vendored
@ -1,6 +1,7 @@
|
|||||||
import { expectTypeOf } from 'expect-type'
|
import { expectTypeOf } from 'expect-type'
|
||||||
import { describe, it } from 'vitest'
|
import { describe, it } from 'vitest'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
|
import type { AppConfig } from '@nuxt/schema'
|
||||||
|
|
||||||
import { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, useRouter as vueUseRouter } from 'vue-router'
|
import { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, useRouter as vueUseRouter } from 'vue-router'
|
||||||
import { defineNuxtConfig } from '~~/../../../packages/nuxt/src'
|
import { defineNuxtConfig } from '~~/../../../packages/nuxt/src'
|
||||||
@ -165,3 +166,17 @@ describe('composables', () => {
|
|||||||
.toEqualTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo }))
|
.toEqualTypeOf(useLazyAsyncData(() => Promise.resolve({ foo: Math.random() }), { transform: data => data.foo }))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('app config', () => {
|
||||||
|
it('merges app config as expected', () => {
|
||||||
|
interface ExpectedMergedAppConfig {
|
||||||
|
fromLayer: boolean,
|
||||||
|
fromNuxtConfig: boolean,
|
||||||
|
nested: {
|
||||||
|
val: number
|
||||||
|
},
|
||||||
|
userConfig: number
|
||||||
|
}
|
||||||
|
expectTypeOf<AppConfig>().toMatchTypeOf<ExpectedMergedAppConfig>()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user