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

feat(nuxt): app.config with hmr and reactivity support #6333

Merged
merged 38 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5b8a04e
feat(nuxt): `app.config` with hmr support
pi0 Aug 3, 2022
eda4b99
Merge branch 'main' into feat/app-config
pi0 Aug 4, 2022
79ff3b1
webpack hmr support
pi0 Aug 4, 2022
c949684
update AppConfig schema
pi0 Aug 4, 2022
8316b5a
handle key removals
pi0 Aug 4, 2022
171d869
support inline config using `appConfig` in nuxt.config
pi0 Aug 4, 2022
22518d8
fix template when no appConfigs added
pi0 Aug 4, 2022
75e3be9
handle app.config add/removal
pi0 Aug 4, 2022
231e0ca
Merge branch 'main' into feat/app-config
pi0 Aug 4, 2022
efd22f0
auto generate types
pi0 Aug 4, 2022
3e0534c
add tests
pi0 Aug 4, 2022
8c2a012
Merge branch 'main' into feat/app-config
pi0 Aug 4, 2022
9cb0c74
fix test side effect
pi0 Aug 4, 2022
7d6c04a
Merge branch 'main' into feat/app-config
pi0 Aug 4, 2022
d3b9163
simplify reserved namespaces
pi0 Aug 4, 2022
9178cd8
fix: reserved are optional
pi0 Aug 4, 2022
bc269f3
Merge branch 'main' into feat/app-config
pi0 Aug 12, 2022
808bf9d
feat(nuxt): include type of resolved configs in AppConfig
danielroe Aug 16, 2022
d5011cf
refactor: write a single type declaration file
danielroe Aug 16, 2022
5d9c593
chore: upgrade defu
danielroe Aug 16, 2022
43b58f3
test: add type test
danielroe Aug 16, 2022
d152678
fix: update to use `Defu` type helper
danielroe Aug 16, 2022
59ed668
fix: use `ResolvedAppConfig` to for type inference and extract `defin…
danielroe Aug 17, 2022
c63fd89
Merge branch 'main' into feat/app-config
pi0 Aug 17, 2022
69c5b50
try removing subpath from package.json
pi0 Aug 17, 2022
15ed77c
refactor: move `defineAppConfig` to `nuxt.ts`
pi0 Aug 17, 2022
f36e0d0
Update packages/nuxt/src/app/config.ts
pi0 Aug 17, 2022
df2380d
chore: fix ts issue
pi0 Aug 17, 2022
b2c7f57
remove unused import
pi0 Aug 17, 2022
a08d987
add usage to examples
pi0 Aug 17, 2022
6a571f5
add docs
pi0 Aug 17, 2022
e54db5d
fix vite hmr
pi0 Aug 17, 2022
2cace40
update docs
pi0 Aug 17, 2022
840f245
update api guide
pi0 Aug 17, 2022
ed0b584
revert useRuntimeConfig back to nuxt.ts
pi0 Aug 17, 2022
12ec374
Merge branch 'main' into feat/app-config
pi0 Aug 17, 2022
5e7d9c6
i touched it!
pi0 Aug 17, 2022
15f5e3f
strict is not funny
pi0 Aug 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"@nuxt/schema": "3.0.0-rc.8",
"c12": "^0.2.9",
"consola": "^2.15.3",
"defu": "^6.0.0",
"defu": "^6.1.0",
"globby": "^13.1.2",
"hash-sum": "^2.0.0",
"ignore": "^5.2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxi/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export default defineNuxtCommand({
dLoad(true, `Directory \`${relativePath}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
}
} 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'}`)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@vueuse/head": "^0.7.9",
"chokidar": "^3.5.3",
"cookie-es": "^0.5.0",
"defu": "^6.0.0",
"defu": "^6.1.0",
"destr": "^1.1.1",
"escape-string-regexp": "^5.0.0",
"fs-extra": "^10.1.0",
Expand Down
57 changes: 57 additions & 0 deletions packages/nuxt/src/app/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { AppConfig, RuntimeConfig } 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 _appConfig = __appConfig as AppConfig

export function useRuntimeConfig (): RuntimeConfig {
pi0 marked this conversation as resolved.
Show resolved Hide resolved
return useNuxtApp().$config
}

export function defineAppConfig (config: AppConfig): AppConfig {
return config
}

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) {
const appConfig = useAppConfig()
if (newConfig && appConfig) {
for (const key in newConfig) {
appConfig[key] = newConfig[key]
}
for (const key in appConfig) {
if (!(key in newConfig)) {
delete appConfig[key]
}
}
}
}

// Vite
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
const newConfig = newModule?._appConfig
applyHMR(newConfig)
})
}

// Webpack
if (import.meta.webpackHot) {
console.log('Register webpackHot')
import.meta.webpackHot.accept('#build/app.config.mjs', () => {
applyHMR(__appConfig)
})
}
}
1 change: 1 addition & 0 deletions packages/nuxt/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export * from './nuxt'
export * from './composables'
export * from './components'
export * from './config'

// eslint-disable-next-line import/no-restricted-paths
export type { PageMeta } from '../pages/runtime'
Expand Down
4 changes: 0 additions & 4 deletions packages/nuxt/src/app/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,6 @@ export function useNuxtApp () {
return nuxtAppInstance
}

export function useRuntimeConfig (): RuntimeConfig {
return useNuxtApp().$config
}

function defineGetter<K extends string | number | symbol, V> (obj: Record<K, V>, key: K, val: V) {
Object.defineProperty(obj, key, { get: () => val })
}
4 changes: 3 additions & 1 deletion packages/nuxt/src/auto-imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export const appPreset = defineUnimportPreset({
'isNuxtError',
'useError',
'createError',
'defineNuxtLink'
'defineNuxtLink',
'defineAppConfig',
'useAppConfig'
]
})

Expand Down
9 changes: 9 additions & 0 deletions packages/nuxt/src/core/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
app.middleware = uniqueBy(await resolvePaths(app.middleware, 'path'), 'name')
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
await nuxt.callHook('app:resolve', app)

Expand Down
1 change: 1 addition & 0 deletions packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ async function initNuxt (nuxt: Nuxt) {
}
// 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/app.config.d.ts') })
})

// Add import protection
Expand Down
36 changes: 34 additions & 2 deletions packages/nuxt/src/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export const schemaTemplate = {
"declare module '@nuxt/schema' {",
' interface NuxtConfig {',
...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'))),
Expand Down Expand Up @@ -157,7 +157,7 @@ export const layoutTemplate: NuxtTemplate = {
}))
return [
'import { defineAsyncComponent } from \'vue\'',
`export default ${layoutsObject}`
`export default ${layoutsObject}`
].join('\n')
}
}
Expand All @@ -184,6 +184,38 @@ export const useRuntimeConfig = () => window?.__NUXT__?.config || {}
`
}

export const appConfigDeclarationTemplate: NuxtTemplate = {
filename: 'types/app.config.d.ts',
getContents: ({ app, nuxt }) => {
return `
import type { Defu } from 'defu'
${app.configs.map((id, index) => `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, index) => `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, index) => `import ${`cfg${index}`} from ${JSON.stringify(id)}`).join('\n')}
pi0 marked this conversation as resolved.
Show resolved Hide resolved
export default defu(${app.configs.map((_id, index) => `cfg${index}`).concat(['inlineConfig']).join(', ')})
`
}
}

export const publicPathTemplate: NuxtTemplate = {
filename: 'paths.mjs',
getContents ({ nuxt }) {
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dependencies": {
"c12": "^0.2.9",
"create-require": "^1.1.1",
"defu": "^6.0.0",
"defu": "^6.1.0",
"jiti": "^1.14.0",
"pathe": "^0.3.4",
"postcss-import-resolver": "^2.0.0",
Expand Down
13 changes: 12 additions & 1 deletion packages/schema/src/config/_common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,5 +747,16 @@ export default {
* @version 3
* @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: {},
}
26 changes: 20 additions & 6 deletions packages/schema/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ export interface NuxtOptions extends ConfigSchema {
_layers: ResolvedConfig<NuxtConfig>[]
}

export interface ViteConfig extends ViteUserConfig {
/**
* Options passed to @vitejs/plugin-vue
* @see https://github.com/vitejs/vite/tree/main/packages/plugin-vue
*/
vue?: VuePluginOptions
}


// -- Runtime Config --

type RuntimeConfigNamespace = Record<string, any>

export interface PublicRuntimeConfig extends RuntimeConfigNamespace { }
Expand All @@ -29,10 +40,13 @@ export interface RuntimeConfig extends PrivateRuntimeConfig, RuntimeConfigNamesp
public: PublicRuntimeConfig
}

export interface ViteConfig extends ViteUserConfig {
/**
* Options passed to @vitejs/plugin-vue
* @see https://github.com/vitejs/vite/tree/main/packages/plugin-vue
*/
vue?: VuePluginOptions
// -- App Config --
export interface AppConfig extends Record<string, any> {
/** @deprecated reserved */
private?: never
/** @deprecated reserved */
nuxt?: never
/** @deprecated reserved */
nitro?: never
}

1 change: 1 addition & 0 deletions packages/schema/src/types/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface NuxtApp {
layouts: Record<string, NuxtLayout>
middleware: NuxtMiddleware[]
templates: NuxtTemplate[]
configs: string[]
}

type _TemplatePlugin = Omit<NuxtPlugin, 'src'> & NuxtTemplate
Expand Down
2 changes: 1 addition & 1 deletion packages/test-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dependencies": {
"@nuxt/kit": "3.0.0-rc.8",
"@nuxt/schema": "3.0.0-rc.8",
"defu": "^6.0.0",
"defu": "^6.1.0",
"execa": "^6.1.0",
"get-port-please": "^2.6.1",
"jiti": "^1.14.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"autoprefixer": "^10.4.8",
"chokidar": "^3.5.3",
"cssnano": "^5.1.12",
"defu": "^6.0.0",
"defu": "^6.1.0",
"esbuild": "^0.15.1",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.1",
Expand Down
2 changes: 1 addition & 1 deletion playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<template>
<!-- Edit this file to play around with Nuxt but never commit changes! -->
<div>
Nuxt 3 Playground
Hello World
</div>
</template>

Expand Down
24 changes: 24 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,4 +450,28 @@ describe('dynamic paths', () => {
).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))
})
})
12 changes: 12 additions & 0 deletions test/fixtures/basic/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// TODO
// import { defineAppConfig } from '#imports'

// For now we can't use AppConfig to type this
danielroe marked this conversation as resolved.
Show resolved Hide resolved
// as it will circularly reference itself

export default {
userConfig: 123,
nested: {
val: 2
}
}
3 changes: 3 additions & 0 deletions test/fixtures/basic/extends/bar/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
fromLayer: true
}
6 changes: 6 additions & 0 deletions test/fixtures/basic/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,11 @@ export default defineNuxtConfig({
experimental: {
reactivityTransform: true,
treeshakeClientOnly: true
},
appConfig: {
fromNuxtConfig: true,
nested: {
val: 1
}
}
})
11 changes: 11 additions & 0 deletions test/fixtures/basic/pages/app-config.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions test/fixtures/basic/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expectTypeOf } from 'expect-type'
import { describe, it } from 'vitest'
import type { Ref } from 'vue'
import type { AppConfig } from '@nuxt/schema'

import { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, useRouter as vueUseRouter } from 'vue-router'
import { defineNuxtConfig } from '~~/../../../packages/nuxt/src'
Expand Down Expand Up @@ -164,3 +165,17 @@ describe('composables', () => {
.toMatchTypeOf(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