diff --git a/docs/content/1.guide/2.auto-imports.md b/docs/content/1.guide/2.auto-imports.md index 9a841b405e..4cc07d1fe7 100644 --- a/docs/content/1.guide/2.auto-imports.md +++ b/docs/content/1.guide/2.auto-imports.md @@ -11,7 +11,8 @@ Nitro is using [unjs/unimport](https://github.com/unjs/unimport) to auto import - `defineCachedFunction(fn, options)`{lang=ts} / `cachedFunction(fn, options)`{lang=ts} - `defineCachedEventHandler(handler, options)`{lang=ts} / `cachedEventHandler(handler, options)`{lang=ts} - `defineRenderHandler(handler)`{lang=ts} -- `useRuntimeConfig()`{lang=ts} +- `useRuntimeConfig(event?)`{lang=ts} +- `useAppConfig(event?)`{lang=ts} - `useStorage(base?)`{lang=ts} - `useNitroApp()`{lang=ts} - `defineNitroPlugin(plugin)`{lang=ts} diff --git a/playground/routes/index.ts b/playground/routes/index.ts index 2b5a032a89..4f39705a87 100644 --- a/playground/routes/index.ts +++ b/playground/routes/index.ts @@ -1,3 +1,3 @@ -import { eventHandler } from "h3"; - -export default eventHandler(() => "

Hello Nitro!

"); +export default eventHandler(() => { + return "

Hello Nitro!

"; +}); diff --git a/src/runtime/app.ts b/src/runtime/app.ts index 1eebf7065c..391960ad9d 100644 --- a/src/runtime/app.ts +++ b/src/runtime/app.ts @@ -60,6 +60,8 @@ function createNitroApp(): NitroApp { // A generic event handler give nitro acess to the requests h3App.use( eventHandler((event) => { + // Init nitro context + event.context.nitro = event.context.nitro || {}; // Support platform context provided by local fetch const envContext = (event.node.req as any).__unenv__; if (envContext) { diff --git a/src/runtime/config.ts b/src/runtime/config.ts index d54a631be0..f9ada9bddb 100644 --- a/src/runtime/config.ts +++ b/src/runtime/config.ts @@ -1,57 +1,103 @@ import destr from "destr"; import { snakeCase } from "scule"; -import { appConfig as _appConfig } from "#internal/nitro/virtual/app-config"; +import { klona } from "klona"; +import { H3Event } from "h3"; +import { appConfig as _inlineAppConfig } from "#internal/nitro/virtual/app-config"; -// Runtime config -const _runtimeConfig = process.env.RUNTIME_CONFIG as any; +// Static runtime config inlined by nitro build +const _inlineRuntimeConfig = process.env.RUNTIME_CONFIG as any; const ENV_PREFIX = "NITRO_"; const ENV_PREFIX_ALT = - _runtimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_"; -overrideConfig(_runtimeConfig); + _inlineRuntimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_"; -const runtimeConfig = deepFreeze(_runtimeConfig); -export default runtimeConfig; // TODO: Remove in next major version -export const useRuntimeConfig = () => runtimeConfig; +// Runtime config +const _sharedRuntimeConfig = _deepFreeze( + _applyEnv(klona(_inlineRuntimeConfig)) +); +export function useRuntimeConfig(event?: H3Event) { + // Backwards compatibility with ambient context + if (!event) { + return _sharedRuntimeConfig; + } + // Reuse cached runtime config from event context + if (event.context.nitro.runtimeConfig) { + return event.context.nitro.runtimeConfig; + } + // Prepare runtime config for event context + const runtimeConfig = klona(_inlineRuntimeConfig); + _applyEnv(runtimeConfig); + event.context.nitro.runtimeConfig = runtimeConfig; + return runtimeConfig; +} // App config -const appConfig = deepFreeze(_appConfig); -export const useAppConfig = () => appConfig; +const _sharedAppConfig = _deepFreeze(klona(_inlineAppConfig)); +export function useAppConfig(event?: H3Event) { + // Backwards compatibility with ambient context + if (!event) { + return _sharedAppConfig; + } + // Reuse cached app config from event context + if (event.context.nitro.appConfig) { + return event.context.nitro.appConfig; + } + // Prepare app config for event context + const appConfig = klona(_inlineAppConfig); + event.context.nitro.appConfig = appConfig; + return appConfig; +} // --- Utils --- -function getEnv(key: string) { +function _getEnv(key: string) { const envKey = snakeCase(key).toUpperCase(); return destr( process.env[ENV_PREFIX + envKey] ?? process.env[ENV_PREFIX_ALT + envKey] ); } -function isObject(input: unknown) { +function _isObject(input: unknown) { return typeof input === "object" && !Array.isArray(input); } -function overrideConfig(obj: object, parentKey = "") { +function _applyEnv(obj: object, parentKey = "") { for (const key in obj) { const subKey = parentKey ? `${parentKey}_${key}` : key; - const envValue = getEnv(subKey); - if (isObject(obj[key])) { - if (isObject(envValue)) { + const envValue = _getEnv(subKey); + if (_isObject(obj[key])) { + if (_isObject(envValue)) { obj[key] = { ...obj[key], ...envValue }; } - overrideConfig(obj[key], subKey); + _applyEnv(obj[key], subKey); } else { obj[key] = envValue ?? obj[key]; } } + return obj; } -function deepFreeze(object: Record) { +function _deepFreeze(object: Record) { const propNames = Object.getOwnPropertyNames(object); for (const name of propNames) { const value = object[name]; if (value && typeof value === "object") { - deepFreeze(value); + _deepFreeze(value); } } return Object.freeze(object); } + +// --- Deprecated default export --- +// TODO: Remove in next major version +export default new Proxy(Object.create(null), { + get: (_, prop) => { + console.warn( + "Please use `useRuntimeConfig()` instead of accessing config directly." + ); + const runtimeConfig = useRuntimeConfig(); + if (prop in runtimeConfig) { + return runtimeConfig[prop]; + } + return undefined; + }, +}); diff --git a/test/fixture/middleware/config.ts b/test/fixture/middleware/config.ts new file mode 100644 index 0000000000..28158e4dad --- /dev/null +++ b/test/fixture/middleware/config.ts @@ -0,0 +1,6 @@ +process.env.NITRO_DYNAMIC = "from-env"; + +export default eventHandler((event) => { + const appConfig = useAppConfig(event); + appConfig.dynamic = "from-middleware"; +}); diff --git a/test/fixture/nitro.config.ts b/test/fixture/nitro.config.ts index 22e8c62a0b..b06f347082 100644 --- a/test/fixture/nitro.config.ts +++ b/test/fixture/nitro.config.ts @@ -22,6 +22,10 @@ export default defineNitroConfig({ ], appConfig: { "nitro-config": true, + dynamic: "initial", + }, + runtimeConfig: { + dynamic: "initial", }, appConfigFiles: ["~/server.config.ts"], publicAssets: [ diff --git a/test/fixture/routes/app-config.ts b/test/fixture/routes/app-config.ts deleted file mode 100644 index 39e7b94821..0000000000 --- a/test/fixture/routes/app-config.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default eventHandler(() => { - const appConfig = useAppConfig(); - - return { - appConfig, - }; -}); diff --git a/test/fixture/routes/config.ts b/test/fixture/routes/config.ts new file mode 100644 index 0000000000..9097e19f5c --- /dev/null +++ b/test/fixture/routes/config.ts @@ -0,0 +1,14 @@ +const sharedAppConfig = useAppConfig(); +const sharedRuntimeConfig = useRuntimeConfig(); + +export default eventHandler((event) => { + const appConfig = useAppConfig(event); + const runtimeConfig = useRuntimeConfig(event); + + return { + sharedAppConfig, + appConfig, + runtimeConfig, + sharedRuntimeConfig, + }; +}); diff --git a/test/tests.ts b/test/tests.ts index cce4c4de88..6122981e4a 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -308,19 +308,40 @@ export function testNitro( expect(data.url).toBe("/api/echo?foo=bar"); }); - it("app config", async () => { + it("config", async () => { const { data } = await callHandler({ - url: "/app-config", + url: "/config", }); - expect(data).toMatchInlineSnapshot(` - { - "appConfig": { - "app-config": true, - "nitro-config": true, - "server-config": true, + expect(data).toMatchObject({ + appConfig: { + dynamic: "from-middleware", + "app-config": true, + "nitro-config": true, + "server-config": true, + }, + runtimeConfig: { + dynamic: "from-env", + app: { + baseURL: "/", + }, + }, + sharedAppConfig: { + dynamic: "initial", + "app-config": true, + "nitro-config": true, + "server-config": true, + }, + sharedRuntimeConfig: { + dynamic: + // TODO + ctx.preset.includes("cloudflare") || ctx.preset === "nitro-dev" + ? "initial" + : "from-env", + app: { + baseURL: "/", }, - } - `); + }, + }); }); if (ctx.nitro!.options.timing) {