UNPKG

nuxt

Version:

Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.

236 lines (235 loc) 10.8 kB
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 };