nuxt
Version:
236 lines (235 loc) • 10.8 kB
JavaScript
import { navigationDiagnostics } from "../../../app/diagnostics/navigation.js";
import { generateRouteKey, toArray } from "../utils.js";
import { isReadonly, reactive, shallowReactive, shallowRef } from "vue";
import { isSamePath, withoutBase } from "ufo";
import { defineNuxtPlugin, useRuntimeConfig } from "#app/nuxt";
import routerOptions, { hashMode } from "#build/router.options.mjs";
import { pageIslandRoutes } from "#build/components.islands.mjs";
import { START_LOCATION, createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from "vue-router";
import { globalMiddleware, namedMiddleware } from "#build/middleware";
import { navigateTo } from "#app/composables/router";
import { _showErrorUnlessCrawler, clearError, createError, isNuxtError, showError, useError } from "#app/composables/error";
import _routes, { handleHotUpdate } from "#build/routes";
import { getRouteRules } from "#app/composables/manifest";
//#region src/pages/runtime/plugins/router.ts
function createCurrentLocation(base, location, renderedPath) {
const { pathname, search, hash } = location;
const hashPos = base.indexOf("#");
if (hashPos > -1) {
const slicePos = hash.includes(base.slice(hashPos)) ? base.slice(hashPos).length : 1;
let pathFromHash = hash.slice(slicePos);
if (pathFromHash[0] !== "/") pathFromHash = "/" + pathFromHash;
return withoutBase(pathFromHash, "");
}
const displayedPath = withoutBase(pathname, base);
const path = !renderedPath || isSamePath(displayedPath, renderedPath) ? displayedPath : renderedPath;
return path + (path.includes("?") ? "" : search) + hash;
}
const plugin = defineNuxtPlugin({
name: "nuxt:router",
enforce: "pre",
async setup(nuxtApp) {
let routerBase = useRuntimeConfig().app.baseURL;
if (hashMode && !routerBase.includes("#")) routerBase += "#";
const history = routerOptions.history?.(routerBase) ?? (import.meta.client ? hashMode ? createWebHashHistory(routerBase) : createWebHistory(routerBase) : createMemoryHistory(routerBase));
const routes = routerOptions.routes ? await routerOptions.routes(_routes) ?? _routes : _routes;
let startPosition;
const router = createRouter({
...routerOptions,
scrollBehavior: (to, from, savedPosition) => {
if (from === START_LOCATION) {
startPosition = savedPosition;
return;
}
if (routerOptions.scrollBehavior) {
router.options.scrollBehavior = routerOptions.scrollBehavior;
if ("scrollRestoration" in window.history) {
const unsub = router.beforeEach(() => {
unsub();
window.history.scrollRestoration = "manual";
});
}
return routerOptions.scrollBehavior(to, START_LOCATION, startPosition || savedPosition);
}
},
history,
routes
});
if (import.meta.hot) handleHotUpdate(router, routerOptions.routes ? routerOptions.routes : (routes) => routes);
if (import.meta.client && "scrollRestoration" in window.history) window.history.scrollRestoration = "auto";
nuxtApp.vueApp.use(router);
const previousRoute = shallowRef(router.currentRoute.value);
router.afterEach((_to, from) => {
previousRoute.value = from;
});
Object.defineProperty(nuxtApp.vueApp.config.globalProperties, "previousRoute", { get: () => previousRoute.value });
const initialURL = import.meta.server ? nuxtApp.ssrContext.url : createCurrentLocation(routerBase, window.location, nuxtApp.payload.path);
const _route = shallowRef(router.currentRoute.value);
const syncCurrentRoute = () => {
_route.value = router.currentRoute.value;
};
router.afterEach((to, from) => {
const lastTo = to.matched.at(-1)?.components?.default;
const lastFrom = from.matched.at(-1)?.components?.default;
if (lastTo === lastFrom) {
if (generateRouteKey({
route: to,
Component: { type: lastTo }
}) === generateRouteKey({
route: from,
Component: { type: lastFrom }
})) syncCurrentRoute();
return;
}
if (to.matched.length < from.matched.length && to.matched.every((m, i) => m.components?.default === from.matched[i]?.components?.default)) syncCurrentRoute();
});
const route = { sync: syncCurrentRoute };
for (const key in _route.value) Object.defineProperty(route, key, {
get: () => _route.value[key],
enumerable: true
});
nuxtApp._route = shallowReactive(route);
nuxtApp._middleware ||= {
global: [],
named: {}
};
const error = useError();
const isServerPage = import.meta.server && nuxtApp.ssrContext?.islandContext?.name?.startsWith("page_");
if (import.meta.client || !nuxtApp.ssrContext?.islandContext || isServerPage) router.afterEach(async (to, _from, failure) => {
delete nuxtApp._processingMiddleware;
if (import.meta.server) delete nuxtApp._middlewareTo;
if (import.meta.client && !nuxtApp.isHydrating && error.value) await nuxtApp.runWithContext(clearError);
if (failure) await nuxtApp.callHook("page:loading:end");
if (import.meta.server && failure?.type === 4) return;
if (import.meta.server && to.redirectedFrom && to.fullPath !== initialURL) await nuxtApp.runWithContext(() => navigateTo(to.fullPath || "/"));
});
try {
if (import.meta.server) await router.push(initialURL);
await router.isReady();
} catch (error) {
await _showErrorUnlessCrawler(nuxtApp, error);
}
const resolvedInitialRoute = import.meta.client && initialURL !== router.currentRoute.value.fullPath ? router.resolve(initialURL) : router.currentRoute.value;
const prePluginRoutePath = import.meta.client ? router.currentRoute.value.fullPath : "";
const hasDeferredRoute = import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.prerenderedAt && nuxtApp.payload.path && initialURL !== nuxtApp.payload.path && isSamePath(router.currentRoute.value.path, nuxtApp.payload.path);
syncCurrentRoute();
if (import.meta.server && nuxtApp.ssrContext?.islandContext && !isServerPage) return { provide: { router } };
function pushErroredRoute(to) {
if (import.meta.client && !nuxtApp.isHydrating && to.fullPath !== createCurrentLocation(routerBase, window.location)) history.push(to.fullPath);
}
const initialLayout = nuxtApp.payload.state._layout;
router.beforeEach(async (to, from) => {
await nuxtApp.callHook("page:loading:start");
to.meta = reactive(to.meta);
if (nuxtApp.isHydrating && initialLayout && !isReadonly(to.meta.layout)) to.meta.layout = initialLayout;
nuxtApp._processingMiddleware = true;
if (import.meta.server) nuxtApp._middlewareTo = to;
if (import.meta.client || !nuxtApp.ssrContext?.islandContext || isServerPage) {
const middlewareEntries = /* @__PURE__ */ new Set([...globalMiddleware, ...nuxtApp._middleware.global]);
for (const component of to.matched) {
const componentMiddleware = component.meta.middleware;
if (!componentMiddleware) continue;
for (const entry of toArray(componentMiddleware)) middlewareEntries.add(entry);
}
const routeRules = getRouteRules({ path: to.path });
if (routeRules.appMiddleware) for (const key in routeRules.appMiddleware) if (routeRules.appMiddleware[key]) middlewareEntries.add(key);
else middlewareEntries.delete(key);
for (const entry of middlewareEntries) {
const middleware = typeof entry === "string" ? nuxtApp._middleware.named[entry] || await namedMiddleware[entry]?.().then((r) => r.default || r) : entry;
if (!middleware) throw navigationDiagnostics.NUXT_E2004({
entry: String(entry),
validMiddleware: import.meta.dev ? Object.keys(namedMiddleware) : void 0
});
try {
if (import.meta.dev) nuxtApp._processingMiddleware = middleware._path || (typeof entry === "string" ? entry : true);
const result = await nuxtApp.runWithContext(() => middleware(to, from));
if (import.meta.server || !nuxtApp.payload.serverRendered && nuxtApp.isHydrating) {
if (result === false || result instanceof Error) {
const error = result || createError({
status: 404,
statusText: `Page Not Found: ${initialURL}`
});
await nuxtApp.runWithContext(() => showError(error));
return false;
}
}
if (result === true) continue;
if (result === false) return result;
if (result) {
if (isNuxtError(result) && result.fatal) {
await nuxtApp.runWithContext(() => showError(result));
pushErroredRoute(to);
}
return result;
}
} catch (err) {
const error = createError(err);
if (error.fatal) {
await nuxtApp.runWithContext(() => showError(error));
pushErroredRoute(to);
}
return error;
}
}
}
});
if (isServerPage) router.beforeResolve((to) => {
const expected = pageIslandRoutes[nuxtApp.ssrContext.islandContext.name];
const actual = to.matched.find((m) => (m.components?.default)?.__nuxt_island)?.components?.default;
if (!expected || expected !== actual?.__nuxt_island) {
nuxtApp.ssrContext["~renderResponse"] = {
statusCode: 400,
statusMessage: "Invalid island request path"
};
return false;
}
});
router.onError(async () => {
delete nuxtApp._processingMiddleware;
if (import.meta.server) delete nuxtApp._middlewareTo;
await nuxtApp.callHook("page:loading:end");
});
router.afterEach((to) => {
if (to.matched.length === 0 && !error.value) return nuxtApp.runWithContext(() => showError(createError({
status: 404,
fatal: false,
statusText: `Page not found: ${to.fullPath}`,
data: { path: to.fullPath }
})));
});
nuxtApp.hooks.hookOnce("app:created", async () => {
try {
if ("name" in resolvedInitialRoute) resolvedInitialRoute.name = void 0;
if (import.meta.client && router.currentRoute.value.fullPath !== prePluginRoutePath) {} else if (hasDeferredRoute) {
const payloadRoute = router.resolve(nuxtApp.payload.path);
if ("name" in payloadRoute) payloadRoute.name = void 0;
await router.replace({
...payloadRoute,
force: true
});
const restoreDeferredRoute = () => {
if (!nuxtApp["~restoreDeferredRoute"]) return;
nuxtApp["~restoreDeferredRoute"] = void 0;
router.currentRoute.value = router.resolve(initialURL);
syncCurrentRoute();
router.replace({
...resolvedInitialRoute,
force: true
}).catch(() => {});
};
nuxtApp["~restoreDeferredRoute"] = restoreDeferredRoute;
nuxtApp.hooks.hookOnce("app:suspense:resolve", restoreDeferredRoute);
} else await router.replace({
...resolvedInitialRoute,
force: true
});
router.options.scrollBehavior = routerOptions.scrollBehavior;
} catch (error) {
await _showErrorUnlessCrawler(nuxtApp, error);
}
});
return { provide: { router } };
}
});
//#endregion
export { plugin as default };