Skip to content

Commit

Permalink
feat(nuxt): tryUseNuxtApp composable (#25031)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniluk4000 committed Jan 18, 2024
1 parent 2bfe01d commit 6b651cf
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 3 deletions.
2 changes: 2 additions & 0 deletions docs/2.guide/3.going-further/1.internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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).

Check [Nuxt App](/docs/api/composables/use-nuxt-app) for more information about this interface.
Expand Down
2 changes: 2 additions & 0 deletions docs/2.guide/3.going-further/6.nuxt-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

:read-more{to="/docs/guide/directory-structure/plugins"}
Expand Down
21 changes: 21 additions & 0 deletions docs/3.api/2.composables/use-nuxt-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const nuxtApp = useNuxtApp()
</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

### `provide (name, value)`
Expand Down Expand Up @@ -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
}
}
```
16 changes: 15 additions & 1 deletion packages/nuxt/src/app/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,15 +439,29 @@ export function callWithNuxt<T extends (...args: any[]) => 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
}

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`.')
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/src/imports/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
},
{
Expand Down
6 changes: 6 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
it('works after useNuxtData call', async () => {
const page = await createPage('/useAsyncData/nuxt-data')
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/basic/composables/async-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async function fn1 () {

async function fn2 () {
await delay()
const app = useNuxtApp()
const app = tryUseNuxtApp()
return {
hasApp: !!app
}
Expand Down

0 comments on commit 6b651cf

Please sign in to comment.