mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
feat(nuxt3): add universal routing utilities (#3274)
This commit is contained in:
parent
ed411c687d
commit
dbab979a2e
35
examples/with-universal-router/app.vue
Normal file
35
examples/with-universal-router/app.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const timer = useState('timer', () => 0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtExampleLayout example="with-universal-router">
|
||||
A page...
|
||||
<br>
|
||||
|
||||
<template v-if="timer">
|
||||
Processing navigation in {{ timer }} seconds
|
||||
</template>
|
||||
|
||||
<template #nav>
|
||||
<nav class="flex align-center gap-4 p-4">
|
||||
<NuxtLink to="/" class="n-link-base">
|
||||
Home
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/forbidden" class="n-link-base">
|
||||
Forbidden
|
||||
</NuxtLink>
|
||||
<NuxtLink to="/redirect" class="n-link-base">
|
||||
Redirect
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="text-center p-4 op-50">
|
||||
Current route: <code>{{ route.path }}</code>
|
||||
</div>
|
||||
</template>
|
||||
</NuxtExampleLayout>
|
||||
</template>
|
7
examples/with-universal-router/nuxt.config.ts
Normal file
7
examples/with-universal-router/nuxt.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { defineNuxtConfig } from 'nuxt3'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'@nuxt/ui'
|
||||
]
|
||||
})
|
13
examples/with-universal-router/package.json
Normal file
13
examples/with-universal-router/package.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "example-with-universal-router",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxi build",
|
||||
"dev": "nuxi dev",
|
||||
"start": "nuxi preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/ui": "npm:@nuxt/ui-edge@latest",
|
||||
"nuxt3": "latest"
|
||||
}
|
||||
}
|
33
examples/with-universal-router/plugins/add.ts
Normal file
33
examples/with-universal-router/plugins/add.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
const timer = useState('timer', () => 0)
|
||||
|
||||
if (process.client) {
|
||||
addRouteMiddleware(async () => {
|
||||
console.log('Starting timer...')
|
||||
timer.value = 5
|
||||
do {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
timer.value--
|
||||
} while (timer.value)
|
||||
console.log('...and navigating')
|
||||
})
|
||||
}
|
||||
|
||||
addRouteMiddleware((to) => {
|
||||
if (to.path === '/forbidden') {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
addRouteMiddleware((to) => {
|
||||
const { $config } = useNuxtApp()
|
||||
if ($config) {
|
||||
console.log('Accessed runtime config within middleware.')
|
||||
}
|
||||
|
||||
if (to.path !== '/redirect') { return }
|
||||
|
||||
console.log('Heading to', to.path, 'but I think we should go somewhere else...')
|
||||
return '/secret'
|
||||
})
|
||||
})
|
3
examples/with-universal-router/tsconfig.json
Normal file
3
examples/with-universal-router/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
@ -27,14 +27,6 @@ export 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' })
|
||||
})
|
||||
|
@ -160,15 +160,6 @@ function convertToLegacyMiddleware (middleware) {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -207,3 +198,17 @@ export interface RouteMiddleware {
|
||||
}
|
||||
|
||||
export const defineNuxtRouteMiddleware = (middleware: RouteMiddleware) => middleware
|
||||
|
||||
interface AddRouteMiddleware {
|
||||
(name: string, middleware: RouteMiddleware, options?: AddRouteMiddlewareOptions): void
|
||||
(middleware: RouteMiddleware): void
|
||||
}
|
||||
|
||||
export const addRouteMiddleware: AddRouteMiddleware = (name: string | RouteMiddleware, middleware?: RouteMiddleware, options: AddRouteMiddlewareOptions = {}) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (options.global || typeof name === 'function') {
|
||||
nuxtApp._middleware.global.push(typeof name === 'function' ? name : middleware)
|
||||
} else {
|
||||
nuxtApp._middleware.named[name] = convertToLegacyMiddleware(middleware)
|
||||
}
|
||||
}
|
||||
|
@ -8,3 +8,5 @@ export type { FetchResult, UseFetchOptions } from './fetch'
|
||||
export { useCookie } from './cookie'
|
||||
export type { CookieOptions, CookieRef } from './cookie'
|
||||
export { useRequestHeaders } from './ssr'
|
||||
export { abortNavigation, addRouteMiddleware, defineNuxtRouteMiddleware, navigateTo, useRoute, useRouter } from './router'
|
||||
export type { AddRouteMiddlewareOptions, RouteMiddleware } from './router'
|
||||
|
65
packages/nuxt3/src/app/composables/router.ts
Normal file
65
packages/nuxt3/src/app/composables/router.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
|
||||
import { useNuxtApp } from '#app'
|
||||
|
||||
export const useRouter = () => {
|
||||
return useNuxtApp()?.$router as Router
|
||||
}
|
||||
|
||||
export const useRoute = () => {
|
||||
return useNuxtApp()._route as RouteLocationNormalizedLoaded
|
||||
}
|
||||
|
||||
export interface RouteMiddleware {
|
||||
(to: RouteLocationNormalized, from: RouteLocationNormalized): ReturnType<NavigationGuard>
|
||||
}
|
||||
|
||||
export const defineNuxtRouteMiddleware = (middleware: RouteMiddleware) => middleware
|
||||
|
||||
export interface AddRouteMiddlewareOptions {
|
||||
global?: boolean
|
||||
}
|
||||
|
||||
interface AddRouteMiddleware {
|
||||
(name: string, middleware: RouteMiddleware, options?: AddRouteMiddlewareOptions): void
|
||||
(middleware: RouteMiddleware): void
|
||||
}
|
||||
|
||||
export const addRouteMiddleware: AddRouteMiddleware = (name: string | RouteMiddleware, middleware?: RouteMiddleware, options: AddRouteMiddlewareOptions = {}) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (options.global || typeof name === 'function') {
|
||||
nuxtApp._middleware.global.push(typeof name === 'function' ? name : middleware)
|
||||
} else {
|
||||
nuxtApp._middleware.named[name] = middleware
|
||||
}
|
||||
}
|
||||
|
||||
const isProcessingMiddleware = () => {
|
||||
try {
|
||||
if (useNuxtApp()._processingMiddleware) {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// Within an async middleware
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export const navigateTo = (to: RouteLocationRaw) => {
|
||||
if (isProcessingMiddleware()) {
|
||||
return to
|
||||
}
|
||||
const router: Router = 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
|
||||
}
|
234
packages/nuxt3/src/app/plugins/router.ts
Normal file
234
packages/nuxt3/src/app/plugins/router.ts
Normal file
@ -0,0 +1,234 @@
|
||||
import { DefineComponent, reactive, h } from 'vue'
|
||||
import { parseURL, parseQuery } from 'ufo'
|
||||
import { NuxtApp } from '@nuxt/schema'
|
||||
import { createError } from 'h3'
|
||||
import { defineNuxtPlugin } from '..'
|
||||
import { callWithNuxt } from '../nuxt'
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
NuxtLink: DefineComponent<{ to: String }>
|
||||
}
|
||||
}
|
||||
|
||||
interface Route {
|
||||
/** Percentage encoded pathname section of the URL. */
|
||||
path: string;
|
||||
/** The whole location including the `search` and `hash`. */
|
||||
fullPath: string;
|
||||
/** Object representation of the `search` property of the current location. */
|
||||
query: Record<string, any>;
|
||||
/** Hash of the current location. If present, starts with a `#`. */
|
||||
hash: string;
|
||||
/** Name of the matched record */
|
||||
name: string | null | undefined;
|
||||
/** Object of decoded params extracted from the `path`. */
|
||||
params: Record<string, any>;
|
||||
/**
|
||||
* The location we were initially trying to access before ending up
|
||||
* on the current location.
|
||||
*/
|
||||
redirectedFrom: Route | undefined;
|
||||
/** Merged `meta` properties from all of the matched route records. */
|
||||
meta: Record<string, any>;
|
||||
}
|
||||
|
||||
function getRouteFromPath (fullPath: string) {
|
||||
const url = parseURL(fullPath.toString())
|
||||
return {
|
||||
path: url.pathname,
|
||||
fullPath,
|
||||
query: parseQuery(url.search),
|
||||
hash: url.hash,
|
||||
// stub properties for compat with vue-router
|
||||
params: {},
|
||||
name: undefined,
|
||||
matched: [],
|
||||
redirectedFrom: undefined,
|
||||
meta: {}
|
||||
}
|
||||
}
|
||||
|
||||
type RouteGuardReturn = void | Error | string | false
|
||||
|
||||
interface RouteGuard {
|
||||
(to: Route, from: Route): RouteGuardReturn | Promise<RouteGuardReturn>
|
||||
}
|
||||
|
||||
interface RouterHooks {
|
||||
'resolve:before': (to: Route, from: Route) => RouteGuardReturn | Promise<RouteGuardReturn>
|
||||
'navigate:before': (to: Route, from: Route) => RouteGuardReturn | Promise<RouteGuardReturn>
|
||||
'navigate:after': (to: Route, from: Route) => void | Promise<void>
|
||||
'error': (err: any) => void | Promise<void>
|
||||
}
|
||||
|
||||
interface Router {
|
||||
currentRoute: Route
|
||||
isReady: () => Promise<void>
|
||||
options: {}
|
||||
install: () => Promise<void>
|
||||
// Navigation
|
||||
push: (url: string) => Promise<void>
|
||||
replace: (url: string) => Promise<void>
|
||||
back: () => void
|
||||
go: (delta: number) => void
|
||||
forward: () => void
|
||||
// Guards
|
||||
beforeResolve: (guard: RouterHooks['resolve:before']) => () => void
|
||||
beforeEach: (guard: RouterHooks['navigate:before']) => () => void
|
||||
afterEach: (guard: RouterHooks['navigate:after']) => () => void
|
||||
onError: (handler: RouterHooks['error']) => () => void
|
||||
// Routes
|
||||
resolve: (url: string) => Route
|
||||
addRoute: (parentName: string, route: Route) => void
|
||||
getRoutes: () => any[]
|
||||
hasRoute: (name: string) => boolean
|
||||
removeRoute: (name: string) => void
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
|
||||
const routes = []
|
||||
|
||||
const hooks: { [key in keyof RouterHooks]: RouterHooks[key][] } = {
|
||||
'navigate:before': [],
|
||||
'resolve:before': [],
|
||||
'navigate:after': [],
|
||||
error: []
|
||||
}
|
||||
|
||||
const registerHook = <T extends keyof RouterHooks>(hook: T, guard: RouterHooks[T]) => {
|
||||
hooks[hook].push(guard)
|
||||
return () => hooks[hook].splice(hooks[hook].indexOf(guard), 1)
|
||||
}
|
||||
|
||||
const route: Route = reactive(getRouteFromPath(process.client ? window.location.href : nuxtApp.ssrContext.url))
|
||||
async function handleNavigation (url: string, replace?: boolean): Promise<void> {
|
||||
if (process.dev && !hooks.error.length) {
|
||||
console.warn('No error handlers registered to handle middleware errors. You can register an error handler with `router.onError()`')
|
||||
}
|
||||
try {
|
||||
// Resolve route
|
||||
const to = getRouteFromPath(url)
|
||||
// Run beforeEach hooks
|
||||
for (const middleware of hooks['navigate:before']) {
|
||||
const result = await middleware(to, route)
|
||||
// Cancel navigation
|
||||
if (result === false || result instanceof Error) { return }
|
||||
// Redirect
|
||||
if (result) { return handleNavigation(result, true) }
|
||||
}
|
||||
|
||||
for (const handler of hooks['resolve:before']) {
|
||||
await handler(to, route)
|
||||
}
|
||||
// Perform navigation
|
||||
Object.assign(route, to)
|
||||
if (process.client) {
|
||||
window.history[replace ? 'replaceState' : 'pushState']({}, '', url)
|
||||
}
|
||||
// Run afterEach hooks
|
||||
for (const middleware of hooks['navigate:after']) {
|
||||
await middleware(to, route)
|
||||
}
|
||||
} catch (err) {
|
||||
for (const handler of hooks.error) {
|
||||
await handler(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const router: Router = {
|
||||
currentRoute: route,
|
||||
isReady: () => Promise.resolve(),
|
||||
//
|
||||
options: {},
|
||||
install: () => Promise.resolve(),
|
||||
// Navigation
|
||||
push: (url: string) => handleNavigation(url, false),
|
||||
replace: (url: string) => handleNavigation(url, true),
|
||||
back: () => window.history.go(-1),
|
||||
go: (delta: number) => window.history.go(delta),
|
||||
forward: () => window.history.go(1),
|
||||
// Guards
|
||||
beforeResolve: (guard: RouterHooks['resolve:before']) => registerHook('resolve:before', guard),
|
||||
beforeEach: (guard: RouterHooks['navigate:before']) => registerHook('navigate:before', guard),
|
||||
afterEach: (guard: RouterHooks['navigate:after']) => registerHook('navigate:after', guard),
|
||||
onError: (handler: RouterHooks['error']) => registerHook('error', handler),
|
||||
// Routes
|
||||
resolve: getRouteFromPath,
|
||||
addRoute: (parentName: string, route: Route) => { routes.push(route) },
|
||||
getRoutes: () => routes,
|
||||
hasRoute: (name: string) => routes.some(route => route.name === name),
|
||||
removeRoute: (name: string) => {
|
||||
const index = routes.findIndex(route => route.name === name)
|
||||
if (index !== -1) {
|
||||
routes.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.client) {
|
||||
window.addEventListener('popstate', (event) => {
|
||||
const location = (event.target as Window).location
|
||||
router.replace(location.href.replace(location.origin, ''))
|
||||
})
|
||||
}
|
||||
|
||||
nuxtApp._route = route
|
||||
|
||||
// Handle middleware
|
||||
nuxtApp._middleware = nuxtApp._middleware || {
|
||||
global: [],
|
||||
named: {}
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from) => {
|
||||
to.meta = reactive(to.meta || {})
|
||||
nuxtApp._processingMiddleware = true
|
||||
|
||||
type MiddlewareDef = string | RouteGuard
|
||||
const middlewareEntries = new Set<MiddlewareDef>(nuxtApp._middleware.global)
|
||||
|
||||
for (const middleware of middlewareEntries) {
|
||||
const result = await callWithNuxt(nuxtApp as NuxtApp, middleware, [to, from])
|
||||
if (process.server) {
|
||||
if (result === false || result instanceof Error) {
|
||||
const error = result || createError({
|
||||
statusMessage: `Route navigation aborted: ${nuxtApp.ssrContext.url}`
|
||||
})
|
||||
nuxtApp.ssrContext.error = error
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if (result || result === false) { return result }
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
delete nuxtApp._processingMiddleware
|
||||
})
|
||||
|
||||
nuxtApp.vueApp.component('NuxtLink', {
|
||||
functional: true,
|
||||
props: { to: String },
|
||||
setup: (props, { slots }) => () => h('a', { href: props.to, onClick: (e) => { e.preventDefault(); router.push(props.to) } }, slots)
|
||||
})
|
||||
|
||||
if (process.server) {
|
||||
nuxtApp.hooks.hookOnce('app:created', async () => {
|
||||
await router.push(nuxtApp.ssrContext.url)
|
||||
if (route.fullPath !== nuxtApp.ssrContext.url) {
|
||||
nuxtApp.ssrContext.res.setHeader('Location', route.fullPath)
|
||||
nuxtApp.ssrContext.res.statusCode = 301
|
||||
nuxtApp.ssrContext.res.end()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
provide: {
|
||||
route,
|
||||
router
|
||||
}
|
||||
}
|
||||
})
|
@ -15,7 +15,13 @@ export const Nuxt3AutoImports: AutoImportSource[] = [
|
||||
'useFetch',
|
||||
'useLazyFetch',
|
||||
'useCookie',
|
||||
'useRequestHeaders'
|
||||
'useRequestHeaders',
|
||||
'useRouter',
|
||||
'useRoute',
|
||||
'defineNuxtRouteMiddleware',
|
||||
'navigateTo',
|
||||
'abortNavigation',
|
||||
'addRouteMiddleware'
|
||||
]
|
||||
},
|
||||
// #meta
|
||||
|
@ -15,8 +15,9 @@ export default defineNuxtModule({
|
||||
const pagesDir = resolve(nuxt.options.srcDir, nuxt.options.dir.pages)
|
||||
const runtimeDir = resolve(distDir, 'pages/runtime')
|
||||
|
||||
// Disable module if pages dir do not exists
|
||||
// Disable module (and use universal router) if pages dir do not exists
|
||||
if (!existsSync(pagesDir)) {
|
||||
addPlugin(resolve(distDir, 'app/plugins/router'))
|
||||
return
|
||||
}
|
||||
|
||||
@ -47,19 +48,7 @@ export default defineNuxtModule({
|
||||
})
|
||||
|
||||
nuxt.hook('autoImports:extend', (autoImports) => {
|
||||
const composablesFile = resolve(runtimeDir, 'composables')
|
||||
const composables = [
|
||||
'useRouter',
|
||||
'useRoute',
|
||||
'defineNuxtRouteMiddleware',
|
||||
'definePageMeta',
|
||||
'navigateTo',
|
||||
'abortNavigation',
|
||||
'addRouteMiddleware'
|
||||
]
|
||||
for (const composable of composables) {
|
||||
autoImports.push({ name: composable, as: composable, from: composablesFile })
|
||||
}
|
||||
autoImports.push({ name: 'definePageMeta', as: 'definePageMeta', from: resolve(runtimeDir, 'composables') })
|
||||
})
|
||||
|
||||
// Extract macros from pages
|
||||
|
@ -1,14 +1,5 @@
|
||||
import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
|
||||
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
|
||||
import { useNuxtApp } from '#app'
|
||||
|
||||
export const useRouter = () => {
|
||||
return useNuxtApp().$router as Router
|
||||
}
|
||||
|
||||
export const useRoute = () => {
|
||||
return useNuxtApp()._route as RouteLocationNormalizedLoaded
|
||||
}
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
export interface PageMeta {
|
||||
[key: string]: any
|
||||
@ -35,53 +26,3 @@ export const definePageMeta = (meta: PageMeta): void => {
|
||||
warnRuntimeUsage('definePageMeta')
|
||||
}
|
||||
}
|
||||
|
||||
export interface RouteMiddleware {
|
||||
(to: RouteLocationNormalized, from: RouteLocationNormalized): ReturnType<NavigationGuard>
|
||||
}
|
||||
|
||||
export const defineNuxtRouteMiddleware = (middleware: RouteMiddleware) => middleware
|
||||
|
||||
export interface AddRouteMiddlewareOptions {
|
||||
global?: boolean
|
||||
}
|
||||
|
||||
export const addRouteMiddleware = (name: string, middleware: RouteMiddleware, options: AddRouteMiddlewareOptions = {}) => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (options.global) {
|
||||
nuxtApp._middleware.global.push(middleware)
|
||||
} else {
|
||||
nuxtApp._middleware.named[name] = middleware
|
||||
}
|
||||
}
|
||||
|
||||
const isProcessingMiddleware = () => {
|
||||
try {
|
||||
if (useNuxtApp()._processingMiddleware) {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// Within an async middleware
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export const navigateTo = (to: RouteLocationRaw) => {
|
||||
if (isProcessingMiddleware()) {
|
||||
return to
|
||||
}
|
||||
const router: Router = 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
|
||||
}
|
||||
|
@ -118,6 +118,8 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
router.afterEach((to) => {
|
||||
if (to.fullPath !== nuxtApp.ssrContext.url) {
|
||||
nuxtApp.ssrContext.res.setHeader('Location', to.fullPath)
|
||||
nuxtApp.ssrContext.res.statusCode = 301
|
||||
nuxtApp.ssrContext.res.end()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
17
test/fixtures/basic/types.ts
vendored
17
test/fixtures/basic/types.ts
vendored
@ -2,8 +2,8 @@ import { expectTypeOf } from 'expect-type'
|
||||
import { describe, it } from 'vitest'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useRouter as vueUseRouter } from 'vue-router'
|
||||
import { defineNuxtConfig } from '~~/../../packages/nuxt3/src'
|
||||
import { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, useRouter as vueUseRouter } from 'vue-router'
|
||||
import { defineNuxtConfig } from '~~/../../../packages/nuxt3/src'
|
||||
import { useRouter } from '#imports'
|
||||
import { isVue3 } from '#app'
|
||||
|
||||
@ -46,6 +46,19 @@ describe('middleware', () => {
|
||||
// @ts-expect-error Invalid middleware
|
||||
definePageMeta({ middleware: 'invalid-middleware' })
|
||||
})
|
||||
it('handles adding middleware', () => {
|
||||
addRouteMiddleware('example', (to, from) => {
|
||||
expectTypeOf(to).toMatchTypeOf<RouteLocationNormalizedLoaded>()
|
||||
expectTypeOf(from).toMatchTypeOf<RouteLocationNormalizedLoaded>()
|
||||
expectTypeOf(navigateTo).toMatchTypeOf<(to: RouteLocationRaw) => RouteLocationRaw | Promise<void | NavigationFailure>>()
|
||||
navigateTo('/')
|
||||
abortNavigation()
|
||||
abortNavigation('error string')
|
||||
abortNavigation(new Error('my error'))
|
||||
// @ts-expect-error Must return error or string
|
||||
abortNavigation(true)
|
||||
}, { global: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe('layouts', () => {
|
||||
|
@ -10774,6 +10774,15 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-with-universal-router@workspace:examples/with-universal-router":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-with-universal-router@workspace:examples/with-universal-router"
|
||||
dependencies:
|
||||
"@nuxt/ui": "npm:@nuxt/ui-edge@latest"
|
||||
nuxt3: latest
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"example-with-wasm@workspace:examples/with-wasm":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "example-with-wasm@workspace:examples/with-wasm"
|
||||
|
Loading…
Reference in New Issue
Block a user