From 6b651cf7bf6f0df599155df88c314eb66ae153ba Mon Sep 17 00:00:00 2001 From: Danila Rodichkin Date: Thu, 18 Jan 2024 12:59:59 +0300 Subject: [PATCH] feat(nuxt): `tryUseNuxtApp` composable (#25031) --- docs/2.guide/3.going-further/1.internals.md | 2 ++ docs/2.guide/3.going-further/6.nuxt-app.md | 2 ++ docs/3.api/2.composables/use-nuxt-app.md | 21 +++++++++++++++++++ packages/nuxt/src/app/nuxt.ts | 16 +++++++++++++- packages/nuxt/src/imports/presets.ts | 2 +- test/basic.test.ts | 6 ++++++ .../basic/composables/async-context.ts | 2 +- 7 files changed, 48 insertions(+), 3 deletions(-) diff --git a/docs/2.guide/3.going-further/1.internals.md b/docs/2.guide/3.going-further/1.internals.md index e0e60f733d..f63be69e7a 100644 --- a/docs/2.guide/3.going-further/1.internals.md +++ b/docs/2.guide/3.going-further/1.internals.md @@ -28,6 +28,8 @@ You can think of it as **Runtime Core**. This context can be accessed using [`useNuxtApp()`](/docs/api/composables/use-nuxt-app) composable within Nuxt plugins and ` ``` +If runtime context is unavailable in your scope, `useNuxtApp` will throw an exception when called. You can use [`tryUseNuxtApp`](#tryusenuxtapp) instead for composables that do not require `nuxtApp`, or to simply check if context is available or not without an exception. + ## Methods ### `provide (name, value)` @@ -257,3 +259,22 @@ Native async context support works currently in Bun and Node. :: :read-more{to="/docs/guide/going-further/experimental-features#asynccontext"} + +## tryUseNuxtApp + +This function works exactly the same as `useNuxtApp`, but returns `null` if context is unavailable instead of throwing an exception. + +You can use it for composables that do not require `nuxtApp`, or to simply check if context is available or not without an exception. + +Example usage: + +```ts [composable.ts] +export function useStandType() { + // Always works on the client + if (tryUseNuxtApp()) { + return useRuntimeConfig().public.STAND_TYPE + } else { + return process.env.STAND_TYPE + } +} +``` diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index 218e252c50..28468fccba 100644 --- a/packages/nuxt/src/app/nuxt.ts +++ b/packages/nuxt/src/app/nuxt.ts @@ -439,8 +439,10 @@ export function callWithNuxt any> (nuxt: NuxtApp | /*@__NO_SIDE_EFFECTS__*/ /** * Returns the current Nuxt instance. + * + * Returns `null` if Nuxt instance is unavailable. */ -export function useNuxtApp (): NuxtApp { +export function tryUseNuxtApp (): NuxtApp | null { let nuxtAppInstance if (hasInjectionContext()) { nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt @@ -448,6 +450,18 @@ export function useNuxtApp (): NuxtApp { nuxtAppInstance = nuxtAppInstance || nuxtAppCtx.tryUse() + return nuxtAppInstance || null +} + +/*@__NO_SIDE_EFFECTS__*/ +/** + * Returns the current Nuxt instance. + * + * Throws an error if Nuxt instance is unavailable. + */ +export function useNuxtApp (): NuxtApp { + const nuxtAppInstance = tryUseNuxtApp() + if (!nuxtAppInstance) { if (import.meta.dev) { throw new Error('[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables`.') diff --git a/packages/nuxt/src/imports/presets.ts b/packages/nuxt/src/imports/presets.ts index 4404893b4a..2296d8610b 100644 --- a/packages/nuxt/src/imports/presets.ts +++ b/packages/nuxt/src/imports/presets.ts @@ -18,7 +18,7 @@ const granularAppPresets: InlinePreset[] = [ imports: ['defineNuxtLink'] }, { - imports: ['useNuxtApp', 'defineNuxtPlugin', 'definePayloadPlugin', 'useRuntimeConfig', 'defineAppConfig'], + imports: ['useNuxtApp', 'tryUseNuxtApp', 'defineNuxtPlugin', 'definePayloadPlugin', 'useRuntimeConfig', 'defineAppConfig'], from: '#app/nuxt' }, { diff --git a/test/basic.test.ts b/test/basic.test.ts index 63d904512c..51d284ba4f 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -2130,6 +2130,12 @@ describe.skipIf(process.env.TEST_CONTEXT !== 'async')('Async context', () => { }) }) +describe.skipIf(process.env.TEST_CONTEXT === 'async')('Async context', () => { + it('should be unavailable', async () => { + expect(await $fetch('/async-context')).toContain('"hasApp": false') + }) +}) + describe.skipIf(isWindows)('useAsyncData', () => { it('works after useNuxtData call', async () => { const page = await createPage('/useAsyncData/nuxt-data') diff --git a/test/fixtures/basic/composables/async-context.ts b/test/fixtures/basic/composables/async-context.ts index 6ebdf88ec7..0b928d1ce9 100644 --- a/test/fixtures/basic/composables/async-context.ts +++ b/test/fixtures/basic/composables/async-context.ts @@ -12,7 +12,7 @@ async function fn1 () { async function fn2 () { await delay() - const app = useNuxtApp() + const app = tryUseNuxtApp() return { hasApp: !!app }