feat(nuxt): tryUseNuxtApp composable (#25031)

This commit is contained in:
Danila Rodichkin 2024-01-18 12:59:59 +03:00 committed by GitHub
parent 2bfe01d26f
commit 6b651cf7bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 48 additions and 3 deletions

View File

@ -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 `<script setup>` and vue composables. This context can be accessed using [`useNuxtApp()`](/docs/api/composables/use-nuxt-app) composable within Nuxt plugins and `<script setup>` and vue composables.
Global usage is possible for the browser but not on the server, to avoid sharing context between users. Global usage is possible for the browser but not on the server, to avoid sharing context between users.
Since [`useNuxtApp`](/docs/api/composables/use-nuxt-app) throws an exception if context is currently unavailable, if your composable does not always require `nuxtApp`, you can use [`tryUseNuxtApp`](/docs/api/composables/use-nuxt-app#tryusenuxtapp) instead, which will return `null` instead of throwing an exception.
To extend the `nuxtApp` interface and hook into different stages or access contexts, we can use [Nuxt Plugins](/docs/guide/directory-structure/plugins). To extend the `nuxtApp` interface and hook into different stages or access contexts, we can use [Nuxt Plugins](/docs/guide/directory-structure/plugins).
Check [Nuxt App](/docs/api/composables/use-nuxt-app) for more information about this interface. Check [Nuxt App](/docs/api/composables/use-nuxt-app) for more information about this interface.

View File

@ -30,6 +30,8 @@ export function useMyComposable () {
} }
``` ```
If your composable does not always need `nuxtApp` or you simply want to check if it is present or not, since [`useNuxtApp`](/docs/api/composables/use-nuxt-app) throws an exception, you can use [`tryUseNuxtApp`](/docs/api/composables/use-nuxt-app#tryusenuxtapp) instead.
Plugins also receive `nuxtApp` as the first argument for convenience. Plugins also receive `nuxtApp` as the first argument for convenience.
:read-more{to="/docs/guide/directory-structure/plugins"} :read-more{to="/docs/guide/directory-structure/plugins"}

View File

@ -16,6 +16,8 @@ const nuxtApp = useNuxtApp()
</script> </script>
``` ```
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 ## Methods
### `provide (name, value)` ### `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"} :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
}
}
```

View File

@ -439,8 +439,10 @@ export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtApp |
/*@__NO_SIDE_EFFECTS__*/ /*@__NO_SIDE_EFFECTS__*/
/** /**
* Returns the current Nuxt instance. * Returns the current Nuxt instance.
*
* Returns `null` if Nuxt instance is unavailable.
*/ */
export function useNuxtApp (): NuxtApp { export function tryUseNuxtApp (): NuxtApp | null {
let nuxtAppInstance let nuxtAppInstance
if (hasInjectionContext()) { if (hasInjectionContext()) {
nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt
@ -448,6 +450,18 @@ export function useNuxtApp (): NuxtApp {
nuxtAppInstance = nuxtAppInstance || nuxtAppCtx.tryUse() 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 (!nuxtAppInstance) {
if (import.meta.dev) { 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`.') 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`.')

View File

@ -18,7 +18,7 @@ const granularAppPresets: InlinePreset[] = [
imports: ['defineNuxtLink'] imports: ['defineNuxtLink']
}, },
{ {
imports: ['useNuxtApp', 'defineNuxtPlugin', 'definePayloadPlugin', 'useRuntimeConfig', 'defineAppConfig'], imports: ['useNuxtApp', 'tryUseNuxtApp', 'defineNuxtPlugin', 'definePayloadPlugin', 'useRuntimeConfig', 'defineAppConfig'],
from: '#app/nuxt' from: '#app/nuxt'
}, },
{ {

View File

@ -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('&quot;hasApp&quot;: false')
})
})
describe.skipIf(isWindows)('useAsyncData', () => { describe.skipIf(isWindows)('useAsyncData', () => {
it('works after useNuxtData call', async () => { it('works after useNuxtData call', async () => {
const page = await createPage('/useAsyncData/nuxt-data') const page = await createPage('/useAsyncData/nuxt-data')

View File

@ -12,7 +12,7 @@ async function fn1 () {
async function fn2 () { async function fn2 () {
await delay() await delay()
const app = useNuxtApp() const app = tryUseNuxtApp()
return { return {
hasApp: !!app hasApp: !!app
} }