diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts index 65ba6d991f96..0346f939183e 100644 --- a/packages/nuxt/src/app/nuxt.ts +++ b/packages/nuxt/src/app/nuxt.ts @@ -1,5 +1,5 @@ /* eslint-disable no-use-before-define */ -import { getCurrentInstance, reactive } from 'vue' +import { getCurrentInstance, hasInjectionContext, reactive } from 'vue' import type { App, Ref, VNode, onErrorCaptured } from 'vue' import type { RouteLocationNormalizedLoaded } from '#vue-router' import type { HookCallback, Hookable } from 'hookable' @@ -435,19 +435,20 @@ export function callWithNuxt any> (nuxt: NuxtApp | /** * Returns the current Nuxt instance. */ -export function useNuxtApp () { - const nuxtAppInstance = nuxtAppCtx.tryUse() +export function useNuxtApp (): NuxtApp { + let nuxtAppInstance + if (hasInjectionContext()) { + nuxtAppInstance = getCurrentInstance()?.appContext.app.$nuxt + } + + nuxtAppInstance = nuxtAppInstance || nuxtAppCtx.tryUse() if (!nuxtAppInstance) { - const vm = getCurrentInstance() - if (!vm) { - if (process.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#using-vue-and-nuxt-composables`.') - } else { - throw new Error('[nuxt] instance unavailable') - } + if (process.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#using-vue-and-nuxt-composables`.') + } else { + throw new Error('[nuxt] instance unavailable') } - return vm.appContext.app.$nuxt as NuxtApp } return nuxtAppInstance diff --git a/test/basic.test.ts b/test/basic.test.ts index c7ac6c52bcf1..ff5332d8fe2e 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -596,6 +596,22 @@ describe('navigate', () => { }) }) +describe('preserves current instance', () => { + // TODO: reenable when https://github.com/vuejs/core/issues/7733 is resolved + it('should not return getCurrentInstance when there\'s an error in data', async () => { + await fetch('/instance/error') + const html = await $fetch('/instance/next-request') + expect(html).toContain('This should be false: false') + }) + // TODO: re-enable when https://github.com/nuxt/nuxt/issues/15164 is resolved + it.skipIf(isWindows)('should not lose current nuxt app after await in vue component', async () => { + const requests = await Promise.all(Array.from({ length: 100 }).map(() => $fetch('/instance/next-request'))) + for (const html of requests) { + expect(html).toContain('This should be true: true') + } + }) +}) + describe('errors', () => { it('should render a JSON error page', async () => { const res = await fetch('/error', { diff --git a/test/fixtures/basic/pages/instance/error.vue b/test/fixtures/basic/pages/instance/error.vue new file mode 100644 index 000000000000..34bb64704b81 --- /dev/null +++ b/test/fixtures/basic/pages/instance/error.vue @@ -0,0 +1,13 @@ + + + diff --git a/test/fixtures/basic/pages/instance/next-request.vue b/test/fixtures/basic/pages/instance/next-request.vue new file mode 100644 index 000000000000..60ba42ba2836 --- /dev/null +++ b/test/fixtures/basic/pages/instance/next-request.vue @@ -0,0 +1,14 @@ + + diff --git a/test/fixtures/basic/plugins/context-error.ts b/test/fixtures/basic/plugins/context-error.ts new file mode 100644 index 000000000000..99ca1e5a1092 --- /dev/null +++ b/test/fixtures/basic/plugins/context-error.ts @@ -0,0 +1,9 @@ +export default defineNuxtPlugin(() => { + // this should be undefined + const vueApp = getCurrentInstance() + return { + provide: { + wasVueAppInstanceWronglyPreserved: !!vueApp + } + } +})