mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +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
|
// Add bridge-only auto-imports
|
||||||
autoImports.push({ name: 'useNuxt2Meta', as: 'useNuxt2Meta', from: '#app' })
|
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 = () => {
|
const isProcessingMiddleware = () => {
|
||||||
try {
|
try {
|
||||||
if (useNuxtApp()._processingMiddleware) {
|
if (useNuxtApp()._processingMiddleware) {
|
||||||
@ -207,3 +198,17 @@ export interface RouteMiddleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defineNuxtRouteMiddleware = (middleware: RouteMiddleware) => middleware
|
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 { useCookie } from './cookie'
|
||||||
export type { CookieOptions, CookieRef } from './cookie'
|
export type { CookieOptions, CookieRef } from './cookie'
|
||||||
export { useRequestHeaders } from './ssr'
|
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',
|
'useFetch',
|
||||||
'useLazyFetch',
|
'useLazyFetch',
|
||||||
'useCookie',
|
'useCookie',
|
||||||
'useRequestHeaders'
|
'useRequestHeaders',
|
||||||
|
'useRouter',
|
||||||
|
'useRoute',
|
||||||
|
'defineNuxtRouteMiddleware',
|
||||||
|
'navigateTo',
|
||||||
|
'abortNavigation',
|
||||||
|
'addRouteMiddleware'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// #meta
|
// #meta
|
||||||
|
@ -15,8 +15,9 @@ export default defineNuxtModule({
|
|||||||
const pagesDir = resolve(nuxt.options.srcDir, nuxt.options.dir.pages)
|
const pagesDir = resolve(nuxt.options.srcDir, nuxt.options.dir.pages)
|
||||||
const runtimeDir = resolve(distDir, 'pages/runtime')
|
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)) {
|
if (!existsSync(pagesDir)) {
|
||||||
|
addPlugin(resolve(distDir, 'app/plugins/router'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,19 +48,7 @@ export default defineNuxtModule({
|
|||||||
})
|
})
|
||||||
|
|
||||||
nuxt.hook('autoImports:extend', (autoImports) => {
|
nuxt.hook('autoImports:extend', (autoImports) => {
|
||||||
const composablesFile = resolve(runtimeDir, 'composables')
|
autoImports.push({ name: 'definePageMeta', as: 'definePageMeta', from: 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 })
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Extract macros from pages
|
// Extract macros from pages
|
||||||
|
@ -1,14 +1,5 @@
|
|||||||
import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
|
import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
|
||||||
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded } 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 PageMeta {
|
export interface PageMeta {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
@ -35,53 +26,3 @@ export const definePageMeta = (meta: PageMeta): void => {
|
|||||||
warnRuntimeUsage('definePageMeta')
|
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) => {
|
router.afterEach((to) => {
|
||||||
if (to.fullPath !== nuxtApp.ssrContext.url) {
|
if (to.fullPath !== nuxtApp.ssrContext.url) {
|
||||||
nuxtApp.ssrContext.res.setHeader('Location', to.fullPath)
|
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 { describe, it } from 'vitest'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
import { useRouter as vueUseRouter } from 'vue-router'
|
import { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, useRouter as vueUseRouter } from 'vue-router'
|
||||||
import { defineNuxtConfig } from '~~/../../packages/nuxt3/src'
|
import { defineNuxtConfig } from '~~/../../../packages/nuxt3/src'
|
||||||
import { useRouter } from '#imports'
|
import { useRouter } from '#imports'
|
||||||
import { isVue3 } from '#app'
|
import { isVue3 } from '#app'
|
||||||
|
|
||||||
@ -46,6 +46,19 @@ describe('middleware', () => {
|
|||||||
// @ts-expect-error Invalid middleware
|
// @ts-expect-error Invalid middleware
|
||||||
definePageMeta({ middleware: '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', () => {
|
describe('layouts', () => {
|
||||||
|
@ -10774,6 +10774,15 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
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":
|
"example-with-wasm@workspace:examples/with-wasm":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "example-with-wasm@workspace:examples/with-wasm"
|
resolution: "example-with-wasm@workspace:examples/with-wasm"
|
||||||
|
Loading…
Reference in New Issue
Block a user