UNPKG

nuxt

Version:

[![Nuxt banner](./.github/assets/banner.png)](https://nuxt.com)

192 lines (191 loc) 6.82 kB
import { h, isReadonly, reactive } from "vue"; import { isEqual, joinURL, parseQuery, parseURL, stringifyParsedURL, stringifyQuery, withoutBase } from "ufo"; import { createError } from "h3"; import { defineNuxtPlugin, useRuntimeConfig } from "../nuxt.js"; import { clearError, showError } from "../composables/error.js"; import { navigateTo } from "../composables/router.js"; import { useState } from "../composables/state.js"; import { globalMiddleware } from "#build/middleware"; function getRouteFromPath(fullPath) { if (typeof fullPath === "object") { fullPath = stringifyParsedURL({ pathname: fullPath.path || "", search: stringifyQuery(fullPath.query || {}), hash: fullPath.hash || "" }); } 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: void 0, matched: [], redirectedFrom: void 0, meta: {}, href: fullPath }; } export default defineNuxtPlugin({ name: "nuxt:router", enforce: "pre", setup(nuxtApp) { const initialURL = process.client ? withoutBase(window.location.pathname, useRuntimeConfig().app.baseURL) + window.location.search + window.location.hash : nuxtApp.ssrContext.url; const routes = []; const hooks = { "navigate:before": [], "resolve:before": [], "navigate:after": [], error: [] }; const registerHook = (hook, guard) => { hooks[hook].push(guard); return () => hooks[hook].splice(hooks[hook].indexOf(guard), 1); }; const baseURL = useRuntimeConfig().app.baseURL; const route = reactive(getRouteFromPath(initialURL)); async function handleNavigation(url, replace) { try { const to = getRouteFromPath(url); for (const middleware of hooks["navigate:before"]) { const result = await middleware(to, route); if (result === false || result instanceof Error) { return; } if (result) { return handleNavigation(result, true); } } for (const handler of hooks["resolve:before"]) { await handler(to, route); } Object.assign(route, to); if (process.client) { window.history[replace ? "replaceState" : "pushState"]({}, "", joinURL(baseURL, to.fullPath)); if (!nuxtApp.isHydrating) { await nuxtApp.runWithContext(clearError); } } for (const middleware of hooks["navigate:after"]) { await middleware(to, route); } } catch (err) { 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()`", err); } for (const handler of hooks.error) { await handler(err); } } } const router = { currentRoute: route, isReady: () => Promise.resolve(), // These options provide a similar API to vue-router but have no effect options: {}, install: () => Promise.resolve(), // Navigation push: (url) => handleNavigation(url, false), replace: (url) => handleNavigation(url, true), back: () => window.history.go(-1), go: (delta) => window.history.go(delta), forward: () => window.history.go(1), // Guards beforeResolve: (guard) => registerHook("resolve:before", guard), beforeEach: (guard) => registerHook("navigate:before", guard), afterEach: (guard) => registerHook("navigate:after", guard), onError: (handler) => registerHook("error", handler), // Routes resolve: getRouteFromPath, addRoute: (parentName, route2) => { routes.push(route2); }, getRoutes: () => routes, hasRoute: (name) => routes.some((route2) => route2.name === name), removeRoute: (name) => { const index = routes.findIndex((route2) => route2.name === name); if (index !== -1) { routes.splice(index, 1); } } }; nuxtApp.vueApp.component("RouterLink", { functional: true, props: { to: String, custom: Boolean, replace: Boolean, // Not implemented activeClass: String, exactActiveClass: String, ariaCurrentValue: String }, setup: (props, { slots }) => { const navigate = () => handleNavigation(props.to, props.replace); return () => { const route2 = router.resolve(props.to); return props.custom ? slots.default?.({ href: props.to, navigate, route: route2 }) : h("a", { href: props.to, onClick: (e) => { e.preventDefault(); return navigate(); } }, slots); }; } }); if (process.client) { window.addEventListener("popstate", (event) => { const location = event.target.location; router.replace(location.href.replace(location.origin, "")); }); } nuxtApp._route = route; nuxtApp._middleware = nuxtApp._middleware || { global: [], named: {} }; const initialLayout = useState("_layout"); nuxtApp.hooks.hookOnce("app:created", async () => { router.beforeEach(async (to, from) => { to.meta = reactive(to.meta || {}); if (nuxtApp.isHydrating && initialLayout.value && !isReadonly(to.meta.layout)) { to.meta.layout = initialLayout.value; } nuxtApp._processingMiddleware = true; if (process.client || !nuxtApp.ssrContext?.islandContext) { const middlewareEntries = /* @__PURE__ */ new Set([...globalMiddleware, ...nuxtApp._middleware.global]); for (const middleware of middlewareEntries) { const result = await nuxtApp.runWithContext(() => middleware(to, from)); if (process.server) { if (result === false || result instanceof Error) { const error = result || createError({ statusCode: 404, statusMessage: `Page Not Found: ${initialURL}` }); delete nuxtApp._processingMiddleware; return nuxtApp.runWithContext(() => showError(error)); } } if (result || result === false) { return result; } } } }); router.afterEach(() => { delete nuxtApp._processingMiddleware; }); await router.replace(initialURL); if (!isEqual(route.fullPath, initialURL)) { await nuxtApp.runWithContext(() => navigateTo(route.fullPath)); } }); return { provide: { route, router } }; } });