nuxt
Version:
[](https://nuxt.com)
192 lines (191 loc) • 6.82 kB
JavaScript
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
}
};
}
});