feat(nuxt): add nuxtApp.runWithContext (#20608)

This commit is contained in:
Daniel Roe 2023-05-03 11:02:07 +01:00 committed by GitHub
parent a81f9e4c82
commit da3357449f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 36 additions and 29 deletions

View File

@ -9,7 +9,7 @@
<script setup>
import { defineAsyncComponent, onErrorCaptured, onServerPrefetch, provide } from 'vue'
import { callWithNuxt, useNuxtApp } from '#app/nuxt'
import { useNuxtApp } from '#app/nuxt'
import { isNuxtError, showError, useError } from '#app/composables/error'
import { useRoute } from '#app/composables/router'
import AppComponent from '#build/app-component.mjs'
@ -40,7 +40,7 @@ const error = useError()
onErrorCaptured((err, target, info) => {
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
if (process.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
const p = callWithNuxt(nuxtApp, showError, [err])
const p = nuxtApp.runWithContext(() => showError(err))
onServerPrefetch(() => p)
return false // suppress error from breaking render
}

View File

@ -2,7 +2,7 @@ import { getCurrentInstance, reactive, toRefs } from 'vue'
import type { DefineComponent, defineComponent } from 'vue'
import { useHead } from '@unhead/vue'
import type { NuxtApp } from '../nuxt'
import { callWithNuxt, useNuxtApp } from '../nuxt'
import { useNuxtApp } from '../nuxt'
import { useAsyncData } from './asyncData'
import { useRoute } from './router'
import { createError } from './error'
@ -10,12 +10,12 @@ import { createError } from './error'
export const NuxtComponentIndicator = '__nuxt_component'
async function runLegacyAsyncData (res: Record<string, any> | Promise<Record<string, any>>, fn: (nuxtApp: NuxtApp) => Promise<Record<string, any>>) {
const nuxt = useNuxtApp()
const nuxtApp = useNuxtApp()
const route = useRoute()
const vm = getCurrentInstance()!
const { fetchKey } = vm.proxy!.$options
const key = typeof fetchKey === 'function' ? fetchKey(() => '') : fetchKey || route.fullPath
const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => callWithNuxt(nuxt, fn, [nuxt]))
const { data, error } = await useAsyncData(`options:asyncdata:${key}`, () => nuxtApp.runWithContext(() => fn(nuxtApp)))
if (error.value) {
throw createError(error.value)
}
@ -43,7 +43,7 @@ export const defineNuxtComponent: typeof defineComponent =
...options,
setup (props, ctx) {
const nuxtApp = useNuxtApp()
const res = setup ? Promise.resolve(callWithNuxt(nuxtApp, setup, [props, ctx])).then(r => r || {}) : {}
const res = setup ? Promise.resolve(nuxtApp.runWithContext(() => setup(props, ctx))).then(r => r || {}) : {}
const promises: Promise<any>[] = []
if (options.asyncData) {

View File

@ -76,6 +76,8 @@ interface _NuxtApp {
hook: _NuxtApp['hooks']['hook']
callHook: _NuxtApp['hooks']['callHook']
runWithContext: <T extends () => any>(fn: T) => ReturnType<T> | Promise<Awaited<ReturnType<T>>>
[key: string]: unknown
/** @internal */
@ -193,6 +195,7 @@ export function createNuxtApp (options: CreateOptions) {
static: {
data: {}
},
runWithContext: (fn: any) => callWithNuxt(nuxtApp, fn),
isHydrating: process.client,
deferHydration () {
if (!nuxtApp.isHydrating) { return () => {} }
@ -224,7 +227,7 @@ export function createNuxtApp (options: CreateOptions) {
if (process.server) {
async function contextCaller (hooks: HookCallback[], args: any[]) {
for (const hook of hooks) {
await nuxtAppCtx.callAsync(nuxtApp, () => hook(...args))
await nuxtApp.runWithContext(() => hook(...args))
}
}
// Patch callHook to preserve NuxtApp context on server
@ -288,7 +291,7 @@ export function createNuxtApp (options: CreateOptions) {
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin) {
if (typeof plugin !== 'function') { return }
const { provide } = await callWithNuxt(nuxtApp, plugin, [nuxtApp]) || {}
const { provide } = await nuxtApp.runWithContext(() => plugin(nuxtApp)) || {}
if (provide && typeof provide === 'object') {
for (const key in provide) {
nuxtApp.provide(key, provide[key])

View File

@ -1,7 +1,7 @@
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
import { definePayloadReviver, getNuxtClientPayload } from '#app/composables/payload'
import { createError } from '#app/composables/error'
import { callWithNuxt, defineNuxtPlugin } from '#app/nuxt'
import { defineNuxtPlugin } from '#app/nuxt'
const revivers = {
NuxtError: (data: any) => createError(data),
@ -20,7 +20,7 @@ export default defineNuxtPlugin({
for (const reviver in revivers) {
definePayloadReviver(reviver, revivers[reviver as keyof typeof revivers])
}
Object.assign(nuxtApp.payload, await callWithNuxt(nuxtApp, getNuxtClientPayload, []))
Object.assign(nuxtApp.payload, await nuxtApp.runWithContext(getNuxtClientPayload))
// For backwards compatibility - TODO: remove later
window.__NUXT__ = nuxtApp.payload
}

View File

@ -1,7 +1,7 @@
import { h, isReadonly, reactive } from 'vue'
import { isEqual, joinURL, parseQuery, parseURL, stringifyParsedURL, stringifyQuery, withoutBase } from 'ufo'
import { createError } from 'h3'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig } from '../nuxt'
import { defineNuxtPlugin, useRuntimeConfig } from '../nuxt'
import { clearError, showError } from '../composables/error'
import { navigateTo } from '../composables/router'
import { useState } from '../composables/state'
@ -142,7 +142,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
window.history[replace ? 'replaceState' : 'pushState']({}, '', joinURL(baseURL, to.fullPath))
if (!nuxtApp.isHydrating) {
// Clear any existing errors
await callWithNuxt(nuxtApp, clearError)
await nuxtApp.runWithContext(clearError)
}
}
// Run afterEach hooks
@ -238,7 +238,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
const middlewareEntries = new Set<RouteGuard>([...globalMiddleware, ...nuxtApp._middleware.global])
for (const middleware of middlewareEntries) {
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
const result = await nuxtApp.runWithContext(() => middleware(to, from))
if (process.server) {
if (result === false || result instanceof Error) {
const error = result || createError({
@ -246,7 +246,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
statusMessage: `Page Not Found: ${initialURL}`
})
delete nuxtApp._processingMiddleware
return callWithNuxt(nuxtApp, showError, [error])
return nuxtApp.runWithContext(() => showError(error))
}
}
if (result || result === false) { return result }
@ -257,7 +257,7 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>({
await router.replace(initialURL)
if (!isEqual(route.fullPath, initialURL)) {
await callWithNuxt(nuxtApp, navigateTo, [route.fullPath])
await nuxtApp.runWithContext(() => navigateTo(route.fullPath))
}
})

View File

@ -11,7 +11,7 @@ import { createError } from 'h3'
import { withoutBase } from 'ufo'
import type { PageMeta, Plugin, RouteMiddleware } from '../../../app/index'
import { callWithNuxt, defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
import { defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
import { clearError, showError, useError } from '#app/composables/error'
import { useState } from '#app/composables/state'
import { navigateTo } from '#app/composables/router'
@ -113,7 +113,7 @@ export default defineNuxtPlugin({
await router.isReady()
} catch (error: any) {
// We'll catch 404s here
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
}
const initialLayout = useState('_layout')
@ -148,14 +148,14 @@ export default defineNuxtPlugin({
throw new Error(`Unknown route middleware: '${entry}'.`)
}
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
const result = await nuxtApp.runWithContext(() => middleware(to, from))
if (process.server || (!nuxtApp.payload.serverRendered && nuxtApp.isHydrating)) {
if (result === false || result instanceof Error) {
const error = result || createError({
statusCode: 404,
statusMessage: `Page Not Found: ${initialURL}`
})
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
return false
}
}
@ -170,19 +170,19 @@ export default defineNuxtPlugin({
if (process.client && !nuxtApp.isHydrating && error.value) {
// Clear any existing errors
await callWithNuxt(nuxtApp, clearError)
await nuxtApp.runWithContext(clearError)
}
if (process.server && failure?.type === 4 /* ErrorTypes.NAVIGATION_ABORTED */) {
return
}
if (to.matched.length === 0) {
await callWithNuxt(nuxtApp, showError, [createError({
await nuxtApp.runWithContext(() => showError(createError({
statusCode: 404,
fatal: false,
statusMessage: `Page not found: ${to.fullPath}`
})])
})))
} else if (process.server && to.redirectedFrom) {
await callWithNuxt(nuxtApp, navigateTo, [to.fullPath || '/'])
await nuxtApp.runWithContext(() => navigateTo(to.fullPath || '/'))
}
})
@ -195,7 +195,7 @@ export default defineNuxtPlugin({
})
} catch (error: any) {
// We'll catch middleware errors or deliberate exceptions here
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
}
})

View File

@ -1,5 +1,5 @@
import { createError, showError } from '#app/composables/error'
import { callWithNuxt, useNuxtApp } from '#app/nuxt'
import { useNuxtApp } from '#app/nuxt'
import { defineNuxtRouteMiddleware, useRouter } from '#app/composables/router'
export default defineNuxtRouteMiddleware(async (to) => {
@ -24,7 +24,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
if (final === to) {
const unsub = router.afterEach(async () => {
unsub()
await callWithNuxt(nuxtApp, showError, [error])
await nuxtApp.runWithContext(() => showError(error))
// We pretend to have navigated to the invalid route so
// that the user can return to the previous page with
// the back button.

View File

@ -34,7 +34,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default client bundle size', async () => {
stats.client = await analyzeSizes('**/*.js', publicDir)
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"94.0k"')
expect(roundToKilobytes(stats.client.totalBytes)).toMatchInlineSnapshot('"94.1k"')
expect(stats.client.files.map(f => f.replace(/\..*\.js/, '.js'))).toMatchInlineSnapshot(`
[
"_nuxt/entry.js",
@ -45,7 +45,7 @@ describe.skipIf(isWindows || process.env.TEST_BUILDER === 'webpack' || process.e
it('default server bundle size', async () => {
stats.server = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"66.6k"')
expect(roundToKilobytes(stats.server.totalBytes)).toMatchInlineSnapshot('"66.7k"')
const modules = await analyzeSizes('node_modules/**/*', serverDir)
expect(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot('"2654k"')

View File

@ -96,7 +96,7 @@ describe('middleware', () => {
addRouteMiddleware('example', (to, from) => {
expectTypeOf(to).toEqualTypeOf<RouteLocationNormalizedLoaded>()
expectTypeOf(from).toEqualTypeOf<RouteLocationNormalizedLoaded>()
expectTypeOf(navigateTo).toEqualTypeOf <(to: RouteLocationRaw | null | undefined, options ?: NavigateToOptions) => RouteLocationRaw | void | false | Promise<void | NavigationFailure | false>>()
expectTypeOf(navigateTo).toEqualTypeOf<(to: RouteLocationRaw | null | undefined, options?: NavigateToOptions) => RouteLocationRaw | void | false | Promise<void | NavigationFailure | false>>()
navigateTo('/')
abortNavigation()
abortNavigation('error string')
@ -315,4 +315,8 @@ describe('composables inference', () => {
const bob = callWithNuxt({} as any, () => true)
expectTypeOf<typeof bob>().toEqualTypeOf<boolean | Promise<boolean>>()
})
it('runWithContext', () => {
const bob = useNuxtApp().runWithContext(() => true)
expectTypeOf<typeof bob>().toEqualTypeOf<boolean | Promise<boolean>>()
})
})