Skip to content
This repository was archived by the owner on Apr 6, 2023. It is now read-only.

Commit 94f76ea

Browse files
pi0danielroe
andauthored
feat(nuxt): app.config with hmr and reactivity support (#6333)
Co-authored-by: Daniel Roe <[email protected]>
1 parent 405629d commit 94f76ea

File tree

23 files changed

+321
-23
lines changed

23 files changed

+321
-23
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# App Config
2+
3+
::StabilityEdge
4+
::
5+
6+
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).
7+
8+
::alert{type=warning}
9+
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
10+
::
11+
12+
## Defining App Config
13+
14+
To expose config and environment variables to the rest of your app, you will need to define configuration in `app.config` file.
15+
16+
**Example:**
17+
18+
```ts [app.config.ts]
19+
export default defineAppConfig({
20+
theme: {
21+
primaryColor: '#ababab'
22+
}
23+
})
24+
```
25+
26+
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.
27+
28+
```js
29+
const appConfig = useAppConfig()
30+
31+
console.log(appConfig.theme)
32+
```
33+
34+
<!-- TODO: Document module author for extension -->
35+
36+
### Manually Typing App Config
37+
38+
Nuxt tries to automatically generate a typescript interface from provided app config.
39+
40+
It is also possible to type app config manually:
41+
42+
```ts [index.d.ts]
43+
declare module '@nuxt/schema' {
44+
interface AppConfig {
45+
/** Theme configuration */
46+
theme: {
47+
/** Primary app color */
48+
primaryColor: string
49+
}
50+
}
51+
}
52+
53+
// It is always important to ensure you import/export something when augmenting a type
54+
export {}
55+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
icon: IconFile
3+
title: app.config.ts
4+
head.title: Nuxt App Config
5+
---
6+
7+
# Nuxt App Config
8+
9+
::StabilityEdge
10+
::
11+
12+
You can easily provide runtime app configuration using `app.config.ts` file. It can have either of `.ts`, `.js`, or `.mjs` extensions.
13+
14+
```ts [app.config.ts]
15+
export default defineAppConfig({
16+
foo: 'bar'
17+
})
18+
```
19+
20+
::alert{type=warning}
21+
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
22+
::
23+
24+
::ReadMore{link="/guide/features/app-config"}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# `useAppConfig`
2+
3+
::StabilityEdge
4+
::
5+
6+
Access [app config](/guide/features/app-config):
7+
8+
**Usage:**
9+
10+
```js
11+
const appConfig = useAppConfig()
12+
13+
console.log(appConfig)
14+
```
15+
16+
::ReadMore{link="/guide/features/app-config"}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default defineAppConfig({
2+
foo: 'user',
3+
bar: 'user',
4+
baz: 'base'
5+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default defineAppConfig({
2+
bar: 'base',
3+
baz: 'base'
4+
})

examples/advanced/config-extends/pages/index.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
<script setup>
22
const themeConfig = useRuntimeConfig().theme
3+
const appConfig = useAppConfig()
34
const foo = useFoo()
45
const bar = getBar()
56
</script>
67

78
<template>
89
<NuxtExampleLayout example="advanced/config-extends">
9-
theme runtimeConfig
10+
appConfig:
11+
<pre>{{ JSON.stringify(appConfig, null, 2) }}</pre>
12+
runtimeConfig:
1013
<pre>{{ JSON.stringify(themeConfig, null, 2) }}</pre>
1114
<BaseButton>Base Button</BaseButton>
1215
<FancyButton>Fancy Button</FancyButton>

packages/nuxi/src/commands/dev.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export default defineNuxtCommand({
132132
dLoad(true, `Directory \`${relativePath}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
133133
}
134134
} else if (isFileChange) {
135-
if (file.match(/(app|error)\.(js|ts|mjs|jsx|tsx|vue)$/)) {
135+
if (file.match(/(app|error|app\.config)\.(js|ts|mjs|jsx|tsx|vue)$/)) {
136136
dLoad(true, `\`${relativePath}\` ${event === 'add' ? 'created' : 'removed'}`)
137137
}
138138
}

packages/nuxt/src/app/config.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { AppConfig } from '@nuxt/schema'
2+
import { reactive } from 'vue'
3+
import { useNuxtApp } from './nuxt'
4+
// @ts-ignore
5+
import __appConfig from '#build/app.config.mjs'
6+
7+
// Workaround for vite HMR with virtual modules
8+
export const _getAppConfig = () => __appConfig as AppConfig
9+
10+
export function useAppConfig (): AppConfig {
11+
const nuxtApp = useNuxtApp()
12+
if (!nuxtApp._appConfig) {
13+
nuxtApp._appConfig = reactive(__appConfig) as AppConfig
14+
}
15+
return nuxtApp._appConfig
16+
}
17+
18+
// HMR Support
19+
if (process.dev) {
20+
function applyHMR (newConfig: AppConfig) {
21+
const appConfig = useAppConfig()
22+
if (newConfig && appConfig) {
23+
for (const key in newConfig) {
24+
(appConfig as any)[key] = (newConfig as any)[key]
25+
}
26+
for (const key in appConfig) {
27+
if (!(key in newConfig)) {
28+
delete (appConfig as any)[key]
29+
}
30+
}
31+
}
32+
}
33+
34+
// Vite
35+
if (import.meta.hot) {
36+
import.meta.hot.accept((newModule) => {
37+
const newConfig = newModule._getAppConfig()
38+
applyHMR(newConfig)
39+
})
40+
}
41+
42+
// Webpack
43+
if (import.meta.webpackHot) {
44+
import.meta.webpackHot.accept('#build/app.config.mjs', () => {
45+
applyHMR(__appConfig)
46+
})
47+
}
48+
}

packages/nuxt/src/app/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
export * from './nuxt'
44
export * from './composables'
55
export * from './components'
6+
export * from './config'
67

78
// eslint-disable-next-line import/no-restricted-paths
89
export type { PageMeta } from '../pages/runtime'

packages/nuxt/src/app/nuxt.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { getCurrentInstance, reactive } from 'vue'
33
import type { App, onErrorCaptured, VNode } from 'vue'
44
import { createHooks, Hookable } from 'hookable'
5-
import type { RuntimeConfig } from '@nuxt/schema'
5+
import type { RuntimeConfig, AppConfigInput } from '@nuxt/schema'
66
import { getContext } from 'unctx'
77
import type { SSRContext } from 'vue-bundle-renderer/runtime'
88
import type { CompatibilityEvent } from 'h3'
@@ -281,3 +281,7 @@ export function useRuntimeConfig (): RuntimeConfig {
281281
function defineGetter<K extends string | number | symbol, V> (obj: Record<K, V>, key: K, val: V) {
282282
Object.defineProperty(obj, key, { get: () => val })
283283
}
284+
285+
export function defineAppConfig<C extends AppConfigInput> (config: C): C {
286+
return config
287+
}

0 commit comments

Comments
 (0)