feat(kit): add useRuntimeConfig and updateRuntimeConfig utils (#27117)

This commit is contained in:
Daniel Roe 2024-05-09 18:49:35 +01:00 committed by GitHub
parent 76925a4477
commit cebc89186e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 165 additions and 20 deletions

View File

@ -0,0 +1,27 @@
---
title: Runtime Config
description: Nuxt Kit provides a set of utilities to help you access and modify Nuxt runtime configuration.
links:
- label: Source
icon: i-simple-icons-github
to: https://github.com/nuxt/nuxt/blob/main/packages/kit/src/runtime-config.ts
size: xs
---
## `useRuntimeConfig`
At build-time, it is possible to access the resolved Nuxt [runtime config](/docs/guide/going-further/runtime-config).
### Type
```ts
function useRuntimeConfig (): Record<string, unknown>
```
## `updateRuntimeConfig`
It is also possible to update runtime configuration. This will be merged with the existing runtime configuration, and if Nitro has already been initialized it will trigger an HMR event to reload the Nitro runtime config.
```ts
function updateRuntimeConfig (config: Record<string, unknown>): void | Promise<void>
```

View File

@ -30,10 +30,12 @@
"c12": "^1.10.0", "c12": "^1.10.0",
"consola": "^3.2.3", "consola": "^3.2.3",
"defu": "^6.1.4", "defu": "^6.1.4",
"destr": "^2.0.3",
"globby": "^14.0.1", "globby": "^14.0.1",
"hash-sum": "^2.0.0", "hash-sum": "^2.0.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"jiti": "^1.21.0", "jiti": "^1.21.0",
"klona": "^2.0.6",
"knitwork": "^1.1.0", "knitwork": "^1.1.0",
"mlly": "^1.7.0", "mlly": "^1.7.0",
"pathe": "^1.1.2", "pathe": "^1.1.2",

View File

@ -10,6 +10,7 @@ export * from './loader/nuxt'
// Utils // Utils
export * from './imports' export * from './imports'
export { updateRuntimeConfig, useRuntimeConfig } from './runtime-config'
export * from './build' export * from './build'
export * from './compatibility' export * from './compatibility'
export * from './components' export * from './components'

View File

@ -0,0 +1,103 @@
import process from 'node:process'
import destr from 'destr'
import { snakeCase } from 'scule'
import { klona } from 'klona'
import defu from 'defu'
import { useNuxt } from './context'
import { useNitro } from './nitro'
/**
* Access 'resolved' Nuxt runtime configuration, with values updated from environment.
*
* This mirrors the runtime behavior of Nitro.
*/
export function useRuntimeConfig () {
const nuxt = useNuxt()
return applyEnv(klona(nuxt.options.nitro.runtimeConfig!), {
prefix: 'NITRO_',
altPrefix: 'NUXT_',
envExpansion: nuxt.options.nitro.experimental?.envExpansion ?? !!process.env.NITRO_ENV_EXPANSION,
})
}
/**
* Update Nuxt runtime configuration.
*/
export function updateRuntimeConfig (runtimeConfig: Record<string, unknown>) {
const nuxt = useNuxt()
Object.assign(nuxt.options.nitro.runtimeConfig as Record<string, unknown>, defu(runtimeConfig, nuxt.options.nitro.runtimeConfig))
try {
return useNitro().updateConfig({ runtimeConfig })
} catch {
// Nitro is not yet initialised - we can safely ignore this error
}
}
/**
* @internal
*
* https://github.com/unjs/nitro/blob/main/src/runtime/utils.env.ts.
*
* These utils will be replaced by util exposed from nitropack. See https://github.com/unjs/nitro/pull/2404
* for more context and future plans.)
*/
type EnvOptions = {
prefix?: string
altPrefix?: string
envExpansion?: boolean
}
function getEnv (key: string, opts: EnvOptions, env = process.env) {
const envKey = snakeCase(key).toUpperCase()
return destr(
env[opts.prefix + envKey] ?? env[opts.altPrefix + envKey],
)
}
function _isObject (input: unknown) {
return typeof input === 'object' && !Array.isArray(input)
}
function applyEnv (
obj: Record<string, any>,
opts: EnvOptions,
parentKey = '',
) {
for (const key in obj) {
const subKey = parentKey ? `${parentKey}_${key}` : key
const envValue = getEnv(subKey, opts)
if (_isObject(obj[key])) {
// Same as before
if (_isObject(envValue)) {
obj[key] = { ...(obj[key] as any), ...(envValue as any) }
applyEnv(obj[key], opts, subKey)
} else if (envValue === undefined) {
// If envValue is undefined
// Then proceed to nested properties
applyEnv(obj[key], opts, subKey)
} else {
// If envValue is a primitive other than undefined
// Then set objValue and ignore the nested properties
obj[key] = envValue ?? obj[key]
}
} else {
obj[key] = envValue ?? obj[key]
}
// Experimental env expansion
if (opts.envExpansion && typeof obj[key] === 'string') {
obj[key] = _expandFromEnv(obj[key])
}
}
return obj
}
const envExpandRx = /{{(.*?)}}/g
function _expandFromEnv (value: string, env: Record<string, any> = process.env) {
return value.replace(envExpandRx, (match, key) => {
return env[key] || match
})
}

View File

@ -12,7 +12,7 @@ import { defu } from 'defu'
import fsExtra from 'fs-extra' import fsExtra from 'fs-extra'
import { dynamicEventHandler } from 'h3' import { dynamicEventHandler } from 'h3'
import { isWindows } from 'std-env' import { isWindows } from 'std-env'
import type { Nuxt, NuxtOptions, RuntimeConfig } from 'nuxt/schema' import type { Nuxt, NuxtOptions } from 'nuxt/schema'
import { version as nuxtVersion } from '../../package.json' import { version as nuxtVersion } from '../../package.json'
import { distDir } from '../dirs' import { distDir } from '../dirs'
import { toArray } from '../utils' import { toArray } from '../utils'
@ -27,8 +27,6 @@ const logLevelMapReverse = {
export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) { export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
// Resolve config // Resolve config
const _nitroConfig = ((nuxt.options as any).nitro || {}) as NitroConfig
const excludePaths = nuxt.options._layers const excludePaths = nuxt.options._layers
.flatMap(l => [ .flatMap(l => [
l.cwd.match(/(?<=\/)node_modules\/(.+)$/)?.[1], l.cwd.match(/(?<=\/)node_modules\/(.+)$/)?.[1],
@ -48,7 +46,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
.map(m => m.entryPath), .map(m => m.entryPath),
) )
const nitroConfig: NitroConfig = defu(_nitroConfig, { const nitroConfig: NitroConfig = defu(nuxt.options.nitro, {
debug: nuxt.options.debug, debug: nuxt.options.debug,
rootDir: nuxt.options.rootDir, rootDir: nuxt.options.rootDir,
workspaceDir: nuxt.options.workspaceDir, workspaceDir: nuxt.options.workspaceDir,
@ -57,7 +55,7 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
buildDir: nuxt.options.buildDir, buildDir: nuxt.options.buildDir,
experimental: { experimental: {
asyncContext: nuxt.options.experimental.asyncContext, asyncContext: nuxt.options.experimental.asyncContext,
typescriptBundlerResolution: nuxt.options.future.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || _nitroConfig.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler', typescriptBundlerResolution: nuxt.options.future.typescriptBundlerResolution || nuxt.options.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler' || nuxt.options.nitro.typescript?.tsConfig?.compilerOptions?.moduleResolution?.toLowerCase() === 'bundler',
}, },
framework: { framework: {
name: 'nuxt', name: 'nuxt',
@ -110,20 +108,6 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
routeRules: { routeRules: {
'/__nuxt_error': { cache: false }, '/__nuxt_error': { cache: false },
}, },
runtimeConfig: {
...nuxt.options.runtimeConfig,
app: {
...nuxt.options.runtimeConfig.app,
baseURL: nuxt.options.runtimeConfig.app.baseURL.startsWith('./')
? nuxt.options.runtimeConfig.app.baseURL.slice(1)
: nuxt.options.runtimeConfig.app.baseURL,
},
nitro: {
envPrefix: 'NUXT_',
// TODO: address upstream issue with defu types...?
...nuxt.options.runtimeConfig.nitro satisfies RuntimeConfig['nitro'] as any,
},
},
appConfig: nuxt.options.appConfig, appConfig: nuxt.options.appConfig,
appConfigFiles: nuxt.options._layers.map( appConfigFiles: nuxt.options._layers.map(
layer => resolve(layer.config.srcDir, 'app.config'), layer => resolve(layer.config.srcDir, 'app.config'),

View File

@ -4,7 +4,7 @@ import ignore from 'ignore'
import type { LoadNuxtOptions } from '@nuxt/kit' import type { LoadNuxtOptions } from '@nuxt/kit'
import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit' import { addBuildPlugin, addComponent, addPlugin, addRouteMiddleware, addServerPlugin, addVitePlugin, addWebpackPlugin, installModule, loadNuxtConfig, logger, nuxtCtx, resolveAlias, resolveFiles, resolveIgnorePatterns, resolvePath, tryResolveModule, useNitro } from '@nuxt/kit'
import { resolvePath as _resolvePath } from 'mlly' import { resolvePath as _resolvePath } from 'mlly'
import type { Nuxt, NuxtHooks, NuxtOptions } from 'nuxt/schema' import type { Nuxt, NuxtHooks, NuxtOptions, RuntimeConfig } from 'nuxt/schema'
import type { PackageJson } from 'pkg-types' import type { PackageJson } from 'pkg-types'
import { readPackageJSON, resolvePackageJSON } from 'pkg-types' import { readPackageJSON, resolvePackageJSON } from 'pkg-types'
@ -600,6 +600,9 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
options._modules.push('@nuxt/telemetry') options._modules.push('@nuxt/telemetry')
} }
// Ensure we share runtime config between Nuxt and Nitro
options.runtimeConfig = options.nitro.runtimeConfig as RuntimeConfig
const nuxt = createNuxt(options) const nuxt = createNuxt(options)
// We register hooks layer-by-layer so any overrides need to be registered separately // We register hooks layer-by-layer so any overrides need to be registered separately

View File

@ -1,4 +1,5 @@
import { defineUntypedSchema } from 'untyped' import { defineUntypedSchema } from 'untyped'
import type { RuntimeConfig } from '../types/config'
export default defineUntypedSchema({ export default defineUntypedSchema({
/** /**
@ -7,6 +8,24 @@ export default defineUntypedSchema({
* @type {typeof import('nitropack')['NitroConfig']} * @type {typeof import('nitropack')['NitroConfig']}
*/ */
nitro: { nitro: {
runtimeConfig: {
$resolve: async (val: Record<string, any> | undefined, get) => {
const runtimeConfig = await get('runtimeConfig') as RuntimeConfig
return {
...runtimeConfig,
app: {
...runtimeConfig.app,
baseURL: runtimeConfig.app.baseURL.startsWith('./')
? runtimeConfig.app.baseURL.slice(1)
: runtimeConfig.app.baseURL,
},
nitro: {
envPrefix: 'NUXT_',
...runtimeConfig.nitro,
},
}
},
},
routeRules: { routeRules: {
$resolve: async (val: Record<string, any> | undefined, get) => ({ $resolve: async (val: Record<string, any> | undefined, get) => ({
...await get('routeRules') as Record<string, any>, ...await get('routeRules') as Record<string, any>,

View File

@ -167,6 +167,9 @@ importers:
defu: defu:
specifier: ^6.1.4 specifier: ^6.1.4
version: 6.1.4 version: 6.1.4
destr:
specifier: ^2.0.3
version: 2.0.3
globby: globby:
specifier: ^14.0.1 specifier: ^14.0.1
version: 14.0.1 version: 14.0.1
@ -179,6 +182,9 @@ importers:
jiti: jiti:
specifier: ^1.21.0 specifier: ^1.21.0
version: 1.21.0 version: 1.21.0
klona:
specifier: ^2.0.6
version: 2.0.6
knitwork: knitwork:
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0 version: 1.1.0