feat(bridge): support addRouteMiddleware, navigateTo and abortNavigation (#3193)

This commit is contained in:
Daniel Roe 2022-02-15 09:51:19 +00:00 committed by GitHub
parent d046c9620b
commit 3c563fa48f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 8 deletions

View File

@ -30,6 +30,10 @@ export async function setupAutoImports () {
// Add auto-imports that are added by ad-hoc modules in nuxt 3
autoImports.push({ name: 'useRouter', as: 'useRouter', from: '#app' })
autoImports.push({ name: 'useRoute', as: 'useRoute', from: '#app' })
autoImports.push({ name: 'addRouteMiddleware', as: 'addRouteMiddleware', from: '#app' })
autoImports.push({ name: 'navigateTo', as: 'navigateTo', from: '#app' })
autoImports.push({ name: 'abortNavigation', as: 'abortNavigation', from: '#app' })
autoImports.push({ name: 'defineNuxtRouteMiddleware', as: 'defineNuxtRouteMiddleware', from: '#app' })
// Add bridge-only auto-imports
autoImports.push({ name: 'useNuxt2Meta', as: 'useNuxt2Meta', from: '#app' })

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { createHooks } from 'hookable'
import { setNuxtAppInstance } from '#app'
import { callWithNuxt, setNuxtAppInstance } from '#app'
// Reshape payload to match key `useLazyAsyncData` expects
function proxiedState (state) {
@ -19,7 +19,7 @@ function proxiedState (state) {
})
}
export default (ctx, inject) => {
export default async (ctx, inject) => {
const nuxtApp = {
vueApp: {
component: Vue.component.bind(Vue),
@ -48,6 +48,27 @@ export default (ctx, inject) => {
nuxtApp.hook = nuxtApp.hooks.hook
nuxtApp.callHook = nuxtApp.hooks.callHook
const middleware = await import('#build/middleware').then(r => r.default)
nuxtApp._middleware = nuxtApp._middleware || {
global: [],
named: middleware
}
ctx.app.router.beforeEach(async (to, from, next) => {
nuxtApp._processingMiddleware = true
for (const middleware of nuxtApp._middleware.global) {
const result = await callWithNuxt(nuxtApp, middleware, [to, from])
if (result || result === false) { return next(result) }
}
next()
})
ctx.app.router.afterEach(() => {
delete nuxtApp._processingMiddleware
})
if (!Array.isArray(ctx.app.created)) {
ctx.app.created = [ctx.app.created].filter(Boolean)
}

View File

@ -58,12 +58,24 @@ export const setNuxtAppInstance = (nuxt: NuxtAppCompat | null) => {
currentNuxtAppInstance = nuxt
}
export function defineNuxtPlugin (plugin: (nuxtApp: NuxtAppCompat) => void): (ctx: Context) => void {
return (ctx) => {
setNuxtAppInstance(ctx.$_nuxtApp)
plugin(ctx.$_nuxtApp)
/**
* Ensures that the setup function passed in has access to the Nuxt instance via `useNuxt`.
*
* @param nuxt A Nuxt instance
* @param setup The function to call
*/
export function callWithNuxt<T extends (...args: any[]) => any> (nuxt: NuxtAppCompat, setup: T, args?: Parameters<T>) {
setNuxtAppInstance(nuxt)
const p: ReturnType<T> = args ? setup(...args as Parameters<T>) : setup()
if (process.server) {
// Unset nuxt instance to prevent context-sharing in server-side
setNuxtAppInstance(null)
}
return p
}
export function defineNuxtPlugin (plugin: (nuxtApp: NuxtAppCompat) => void): (ctx: Context) => void {
return ctx => callWithNuxt(ctx.$_nuxtApp, plugin, [ctx.$_nuxtApp])
}
export const useNuxtApp = (): NuxtAppCompat => {

View File

@ -2,7 +2,7 @@ import { getCurrentInstance, onBeforeUnmount, isRef, watch, reactive, toRef, isR
import type { CombinedVueInstance } from 'vue/types/vue'
import type { MetaInfo } from 'vue-meta'
import type VueRouter from 'vue-router'
import type { Route } from 'vue-router'
import type { Location, Route } from 'vue-router'
import type { RuntimeConfig } from '@nuxt/schema'
import defu from 'defu'
import { useNuxtApp } from './app'
@ -141,3 +141,69 @@ export const useNuxt2Meta = (metaOptions: Reffed<MetaInfo> | (() => Reffed<MetaI
}
}
}
export interface AddRouteMiddlewareOptions {
global?: boolean
}
/** internal */
function convertToLegacyMiddleware (middleware) {
return async (ctx: any) => {
const result = await middleware(ctx.route, ctx.from)
if (result instanceof Error) {
return ctx.error(result)
}
if (result) {
return ctx.redirect(result)
}
return result
}
}
export const addRouteMiddleware = (name: string, middleware: any, options: AddRouteMiddlewareOptions = {}) => {
const nuxtApp = useNuxtApp()
if (options.global) {
nuxtApp._middleware.global.push(middleware)
} else {
nuxtApp._middleware.named[name] = convertToLegacyMiddleware(middleware)
}
}
const isProcessingMiddleware = () => {
try {
if (useNuxtApp()._processingMiddleware) {
return true
}
} catch {
// Within an async middleware
return true
}
return false
}
export const navigateTo = (to: Route) => {
if (isProcessingMiddleware()) {
return to
}
const router: VueRouter = process.server ? useRouter() : (window as any).$nuxt.router
return router.push(to)
}
/** This will abort navigation within a Nuxt route middleware handler. */
export const abortNavigation = (err?: Error | string) => {
if (process.dev && !isProcessingMiddleware()) {
throw new Error('abortNavigation() is only usable inside a route middleware handler.')
}
if (err) {
throw err instanceof Error ? err : new Error(err)
}
return false
}
type RouteMiddlewareReturn = void | Error | string | Location | boolean
export interface RouteMiddleware {
(to: Route, from: Route): RouteMiddlewareReturn | Promise<RouteMiddlewareReturn>
}
export const defineNuxtRouteMiddleware = (middleware: RouteMiddleware) => middleware

View File

@ -60,7 +60,10 @@ const isProcessingMiddleware = () => {
if (useNuxtApp()._processingMiddleware) {
return true
}
} catch {}
} catch {
// Within an async middleware
return true
}
return false
}