@tanstack/vue-router
Version:
Modern and scalable routing for Vue applications
256 lines (255 loc) • 11 kB
JavaScript
import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js";
import { useRouter } from "./useRouter.js";
import { matchContext, pendingMatchContext, routeIdContext } from "./matchContext.js";
import { ClientOnly } from "./ClientOnly.js";
import { CatchNotFound } from "./not-found.js";
import { renderRouteNotFound } from "./renderRouteNotFound.js";
import { ScrollRestoration } from "./scroll-restoration.js";
import { createControlledPromise, getLocationChangeInfo, invariant, isNotFound, isRedirect, rootRouteId } from "@tanstack/router-core";
import * as Vue from "vue";
import { isServer } from "@tanstack/router-core/isServer";
import { useStore } from "@tanstack/vue-store";
//#region src/Match.tsx
var Match = Vue.defineComponent({
name: "Match",
props: { matchId: {
type: String,
required: true
} },
setup(props) {
const router = useRouter();
const routeId = router.stores.matchStores.get(props.matchId)?.routeId;
if (!routeId) {
if (process.env.NODE_ENV !== "production") throw new Error(`Invariant failed: Could not find routeId for matchId "${props.matchId}". Please file an issue!`);
invariant();
}
const isChildOfRoot = router.routesById[routeId]?.parentRoute?.id === rootRouteId;
const activeMatch = useStore(router.stores.getRouteMatchStore(routeId), (value) => value);
const isPendingMatchRef = useStore(router.stores.pendingRouteIds, (pendingRouteIds) => Boolean(pendingRouteIds[routeId]), { equal: Object.is });
const loadedAt = useStore(router.stores.loadedAt, (value) => value);
const matchData = Vue.computed(() => {
const match = activeMatch.value;
if (!match) return null;
return {
matchId: match.id,
routeId,
loadedAt: loadedAt.value,
ssr: match.ssr,
_displayPending: match._displayPending
};
});
const route = Vue.computed(() => matchData.value ? router.routesById[matchData.value.routeId] : null);
const PendingComponent = Vue.computed(() => route.value?.options?.pendingComponent ?? router?.options?.defaultPendingComponent);
const pendingElement = Vue.computed(() => PendingComponent.value ? Vue.h(PendingComponent.value) : void 0);
const routeErrorComponent = Vue.computed(() => route.value?.options?.errorComponent ?? router?.options?.defaultErrorComponent);
const routeOnCatch = Vue.computed(() => route.value?.options?.onCatch ?? router?.options?.defaultOnCatch);
const routeNotFoundComponent = Vue.computed(() => route.value?.isRoot ? route.value?.options?.notFoundComponent ?? router?.options?.notFoundRoute?.options?.component : route.value?.options?.notFoundComponent);
const hasShellComponent = Vue.computed(() => {
if (!route.value?.isRoot) return false;
return !!route.value.options.shellComponent;
});
const ShellComponent = Vue.computed(() => hasShellComponent.value ? route.value.options.shellComponent : null);
Vue.provide(routeIdContext, routeId);
const matchIdRef = Vue.computed(() => activeMatch.value?.id ?? props.matchId);
Vue.provide(matchContext, matchIdRef);
Vue.provide(pendingMatchContext, isPendingMatchRef);
return () => {
const actualMatchId = matchData.value?.matchId ?? props.matchId;
const shouldClientOnly = matchData.value?.ssr === false || matchData.value?.ssr === "data-only" || !!matchData.value?._displayPending;
const renderMatchContent = () => {
const matchInner = Vue.h(MatchInner, { matchId: actualMatchId });
let content = shouldClientOnly ? Vue.h(ClientOnly, { fallback: pendingElement.value }, { default: () => matchInner }) : matchInner;
if (routeNotFoundComponent.value) content = Vue.h(CatchNotFound, {
fallback: (error) => {
error.routeId ??= matchData.value?.routeId;
if (!routeNotFoundComponent.value || error.routeId && error.routeId !== matchData.value?.routeId || !error.routeId && route.value && !route.value.isRoot) throw error;
return Vue.h(routeNotFoundComponent.value, error);
},
children: content
});
if (routeErrorComponent.value) content = CatchBoundary({
getResetKey: () => matchData.value?.loadedAt ?? 0,
errorComponent: routeErrorComponent.value || ErrorComponent,
onCatch: (error) => {
if (isNotFound(error)) {
error.routeId ??= matchData.value?.routeId;
throw error;
}
if (process.env.NODE_ENV !== "production") console.warn(`Warning: Error in route match: ${actualMatchId}`);
routeOnCatch.value?.(error);
},
children: content
});
const withScrollRestoration = [content, isChildOfRoot ? Vue.h(Vue.Fragment, null, [Vue.h(OnRendered), router.options.scrollRestoration && (isServer ?? router.isServer) ? Vue.h(ScrollRestoration) : null]) : null].filter(Boolean);
if (withScrollRestoration.length === 1) return withScrollRestoration[0];
return Vue.h(Vue.Fragment, null, withScrollRestoration);
};
if (!hasShellComponent.value) return renderMatchContent();
return Vue.h(ShellComponent.value, null, { default: () => renderMatchContent() });
};
}
});
var OnRendered = Vue.defineComponent({
name: "OnRendered",
setup() {
const router = useRouter();
const location = useStore(router.stores.resolvedLocation, (resolvedLocation) => resolvedLocation?.state.__TSR_key);
let prevHref;
Vue.watch(location, () => {
if (location.value) {
const currentHref = router.latestLocation.href;
if (prevHref === void 0 || prevHref !== currentHref) {
router.emit({
type: "onRendered",
...getLocationChangeInfo(router.stores.location.get(), router.stores.resolvedLocation.get())
});
prevHref = currentHref;
}
}
}, { immediate: true });
return () => null;
}
});
var MatchInner = Vue.defineComponent({
name: "MatchInner",
props: { matchId: {
type: String,
required: true
} },
setup(props) {
const router = useRouter();
const routeId = Vue.inject(routeIdContext);
const activeMatch = useStore(router.stores.getRouteMatchStore(routeId), (value) => value);
const combinedState = Vue.computed(() => {
const match = activeMatch.value;
if (!match) return null;
const matchRouteId = match.routeId;
const remountFn = router.routesById[matchRouteId].options.remountDeps ?? router.options.defaultRemountDeps;
let remountKey;
if (remountFn) {
const remountDeps = remountFn({
routeId: matchRouteId,
loaderDeps: match.loaderDeps,
params: match._strictParams,
search: match._strictSearch
});
remountKey = remountDeps ? JSON.stringify(remountDeps) : void 0;
}
return {
routeId: matchRouteId,
match: {
id: match.id,
status: match.status,
error: match.error,
ssr: match.ssr,
_forcePending: match._forcePending,
_displayPending: match._displayPending,
_nonReactive: match._nonReactive
},
remountKey
};
});
const route = Vue.computed(() => {
if (!combinedState.value) return null;
return router.routesById[combinedState.value.routeId];
});
const match = Vue.computed(() => combinedState.value?.match);
const remountKey = Vue.computed(() => combinedState.value?.remountKey);
const getMatchPromise = (match, key) => {
return router.getMatch(match.id)?._nonReactive[key] ?? match._nonReactive[key];
};
return () => {
if (!combinedState.value || !match.value || !route.value) return null;
if (match.value._displayPending) {
const PendingComponent = route.value.options.pendingComponent ?? router.options.defaultPendingComponent;
return PendingComponent ? Vue.h(PendingComponent) : null;
}
if (match.value._forcePending) {
const PendingComponent = route.value.options.pendingComponent ?? router.options.defaultPendingComponent;
return PendingComponent ? Vue.h(PendingComponent) : null;
}
if (match.value.status === "notFound") {
if (!isNotFound(match.value.error)) {
if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected a notFound error");
invariant();
}
return renderRouteNotFound(router, route.value, match.value.error);
}
if (match.value.status === "redirected") {
if (!isRedirect(match.value.error)) {
if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected a redirect error");
invariant();
}
throw getMatchPromise(match.value, "loadPromise");
}
if (match.value.status === "error") {
const RouteErrorComponent = route.value.options.errorComponent ?? router.options.defaultErrorComponent;
if (RouteErrorComponent) return Vue.h(RouteErrorComponent, {
error: match.value.error,
reset: () => {
router.invalidate();
},
info: { componentStack: "" }
});
throw match.value.error;
}
if (match.value.status === "pending") {
const pendingMinMs = route.value.options.pendingMinMs ?? router.options.defaultPendingMinMs;
const routerMatch = router.getMatch(match.value.id);
if (pendingMinMs && routerMatch && !routerMatch._nonReactive.minPendingPromise) {
if (!(isServer ?? router.isServer)) {
const minPendingPromise = createControlledPromise();
routerMatch._nonReactive.minPendingPromise = minPendingPromise;
setTimeout(() => {
minPendingPromise.resolve();
routerMatch._nonReactive.minPendingPromise = void 0;
}, pendingMinMs);
}
}
const PendingComponent = route.value.options.pendingComponent ?? router.options.defaultPendingComponent;
if (PendingComponent) return Vue.h(PendingComponent);
return null;
}
const Comp = route.value.options.component ?? router.options.defaultComponent;
const key = remountKey.value;
if (Comp) return Vue.h(Comp, key !== void 0 ? { key } : void 0);
return Vue.h(Outlet, key !== void 0 ? { key } : void 0);
};
}
});
var Outlet = Vue.defineComponent({
name: "Outlet",
setup() {
const router = useRouter();
const parentRouteId = Vue.inject(routeIdContext);
if (!parentRouteId) return () => null;
const parentMatch = useStore(router.stores.getRouteMatchStore(parentRouteId), (v) => v);
const route = Vue.computed(() => parentMatch.value ? router.routesById[parentMatch.value.routeId] : void 0);
const parentGlobalNotFound = Vue.computed(() => parentMatch.value?.globalNotFound ?? false);
const childMatchIdMap = useStore(router.stores.childMatchIdByRouteId, (v) => v);
const childMatchData = Vue.computed(() => {
const childId = childMatchIdMap.value[parentRouteId];
if (!childId) return null;
const child = router.stores.matchStores.get(childId)?.get();
if (!child) return null;
return {
id: child.id,
paramsKey: child.routeId + JSON.stringify(child._strictParams)
};
});
return () => {
if (parentGlobalNotFound.value) {
if (!route.value) return null;
return renderRouteNotFound(router, route.value, void 0);
}
if (!childMatchData.value) return null;
return Vue.h(Match, {
matchId: childMatchData.value.id,
key: childMatchData.value.paramsKey
});
};
}
});
//#endregion
export { Match, Outlet };
//# sourceMappingURL=Match.js.map