Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nuxt): prioritise vue app context when available #20910

Merged
merged 8 commits into from May 17, 2023
23 changes: 12 additions & 11 deletions 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'
Expand Down Expand Up @@ -435,19 +435,20 @@ export function callWithNuxt<T extends (...args: any[]) => 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
Expand Down
16 changes: 16 additions & 0 deletions test/basic.test.ts
Expand Up @@ -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', {
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/basic/pages/instance/error.vue
@@ -0,0 +1,13 @@
<script lang="ts">
export default defineComponent({
data () {
throw new Error('πŸ’€')
}
})
</script>

<template>
<div>
This should not display.
</div>
</template>
14 changes: 14 additions & 0 deletions test/fixtures/basic/pages/instance/next-request.vue
@@ -0,0 +1,14 @@
<script setup>
const nuxtApp = useNuxtApp()
await new Promise(resolve => setTimeout(resolve, 150))
const isSameApp = nuxtApp === useNuxtApp()
if (!isSameApp) {
throw new Error('πŸ’€')
}
</script>
<template>
<div>
This should be false: {{ $wasVueAppInstanceWronglyPreserved }}
This should be true: {{ isSameApp }}
</div>
</template>
9 changes: 9 additions & 0 deletions 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
}
}
})