657 lines
25 KiB
Plaintext
657 lines
25 KiB
Plaintext
import { createControlledPromise, isPromise } from "./utils.js";
|
|
import { invariant } from "./invariant.js";
|
|
import { isNotFound } from "./not-found.js";
|
|
import { rootRouteId } from "./root.js";
|
|
import { isRedirect } from "./redirect.js";
|
|
import { isServer } from "@tanstack/router-core/isServer";
|
|
//#region src/load-matches.ts
|
|
const triggerOnReady = (inner) => {
|
|
if (!inner.rendered) {
|
|
inner.rendered = true;
|
|
return inner.onReady?.();
|
|
}
|
|
};
|
|
const hasForcePendingActiveMatch = (router) => {
|
|
return router.stores.matchesId.get().some((matchId) => {
|
|
return router.stores.matchStores.get(matchId)?.get()._forcePending;
|
|
});
|
|
};
|
|
const resolvePreload = (inner, matchId) => {
|
|
return !!(inner.preload && !inner.router.stores.matchStores.has(matchId));
|
|
};
|
|
/**
|
|
* Builds the accumulated context from router options and all matches up to (and optionally including) the given index.
|
|
* Merges __routeContext and __beforeLoadContext from each match.
|
|
*/
|
|
const buildMatchContext = (inner, index, includeCurrentMatch = true) => {
|
|
const context = { ...inner.router.options.context ?? {} };
|
|
const end = includeCurrentMatch ? index : index - 1;
|
|
for (let i = 0; i <= end; i++) {
|
|
const innerMatch = inner.matches[i];
|
|
if (!innerMatch) continue;
|
|
const m = inner.router.getMatch(innerMatch.id);
|
|
if (!m) continue;
|
|
Object.assign(context, m.__routeContext, m.__beforeLoadContext);
|
|
}
|
|
return context;
|
|
};
|
|
const getNotFoundBoundaryIndex = (inner, err) => {
|
|
if (!inner.matches.length) return;
|
|
const requestedRouteId = err.routeId;
|
|
const matchedRootIndex = inner.matches.findIndex((m) => m.routeId === inner.router.routeTree.id);
|
|
const rootIndex = matchedRootIndex >= 0 ? matchedRootIndex : 0;
|
|
let startIndex = requestedRouteId ? inner.matches.findIndex((match) => match.routeId === requestedRouteId) : inner.firstBadMatchIndex ?? inner.matches.length - 1;
|
|
if (startIndex < 0) startIndex = rootIndex;
|
|
for (let i = startIndex; i >= 0; i--) {
|
|
const match = inner.matches[i];
|
|
if (inner.router.looseRoutesById[match.routeId].options.notFoundComponent) return i;
|
|
}
|
|
return requestedRouteId ? startIndex : rootIndex;
|
|
};
|
|
const handleRedirectAndNotFound = (inner, match, err) => {
|
|
if (!isRedirect(err) && !isNotFound(err)) return;
|
|
if (isRedirect(err) && err.redirectHandled && !err.options.reloadDocument) throw err;
|
|
if (match) {
|
|
match._nonReactive.beforeLoadPromise?.resolve();
|
|
match._nonReactive.loaderPromise?.resolve();
|
|
match._nonReactive.beforeLoadPromise = void 0;
|
|
match._nonReactive.loaderPromise = void 0;
|
|
match._nonReactive.error = err;
|
|
inner.updateMatch(match.id, (prev) => ({
|
|
...prev,
|
|
status: isRedirect(err) ? "redirected" : isNotFound(err) ? "notFound" : prev.status === "pending" ? "success" : prev.status,
|
|
context: buildMatchContext(inner, match.index),
|
|
isFetching: false,
|
|
error: err
|
|
}));
|
|
if (isNotFound(err) && !err.routeId) err.routeId = match.routeId;
|
|
match._nonReactive.loadPromise?.resolve();
|
|
}
|
|
if (isRedirect(err)) {
|
|
inner.rendered = true;
|
|
err.options._fromLocation = inner.location;
|
|
err.redirectHandled = true;
|
|
err = inner.router.resolveRedirect(err);
|
|
}
|
|
throw err;
|
|
};
|
|
const shouldSkipLoader = (inner, matchId) => {
|
|
const match = inner.router.getMatch(matchId);
|
|
if (!match) return true;
|
|
if (!(isServer ?? inner.router.isServer) && match._nonReactive.dehydrated) return true;
|
|
if ((isServer ?? inner.router.isServer) && match.ssr === false) return true;
|
|
return false;
|
|
};
|
|
const syncMatchContext = (inner, matchId, index) => {
|
|
const nextContext = buildMatchContext(inner, index);
|
|
inner.updateMatch(matchId, (prev) => {
|
|
return {
|
|
...prev,
|
|
context: nextContext
|
|
};
|
|
});
|
|
};
|
|
const handleSerialError = (inner, index, err, routerCode) => {
|
|
const { id: matchId, routeId } = inner.matches[index];
|
|
const route = inner.router.looseRoutesById[routeId];
|
|
if (err instanceof Promise) throw err;
|
|
err.routerCode = routerCode;
|
|
inner.firstBadMatchIndex ??= index;
|
|
handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err);
|
|
try {
|
|
route.options.onError?.(err);
|
|
} catch (errorHandlerErr) {
|
|
err = errorHandlerErr;
|
|
handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), err);
|
|
}
|
|
inner.updateMatch(matchId, (prev) => {
|
|
prev._nonReactive.beforeLoadPromise?.resolve();
|
|
prev._nonReactive.beforeLoadPromise = void 0;
|
|
prev._nonReactive.loadPromise?.resolve();
|
|
return {
|
|
...prev,
|
|
error: err,
|
|
status: "error",
|
|
isFetching: false,
|
|
updatedAt: Date.now(),
|
|
abortController: new AbortController()
|
|
};
|
|
});
|
|
if (!inner.preload && !isRedirect(err) && !isNotFound(err)) inner.serialError ??= err;
|
|
};
|
|
const isBeforeLoadSsr = (inner, matchId, index, route) => {
|
|
const existingMatch = inner.router.getMatch(matchId);
|
|
const parentMatchId = inner.matches[index - 1]?.id;
|
|
const parentMatch = parentMatchId ? inner.router.getMatch(parentMatchId) : void 0;
|
|
if (inner.router.isShell()) {
|
|
existingMatch.ssr = route.id === rootRouteId;
|
|
return;
|
|
}
|
|
if (parentMatch?.ssr === false) {
|
|
existingMatch.ssr = false;
|
|
return;
|
|
}
|
|
const parentOverride = (tempSsr) => {
|
|
if (tempSsr === true && parentMatch?.ssr === "data-only") return "data-only";
|
|
return tempSsr;
|
|
};
|
|
const defaultSsr = inner.router.options.defaultSsr ?? true;
|
|
if (route.options.ssr === void 0) {
|
|
existingMatch.ssr = parentOverride(defaultSsr);
|
|
return;
|
|
}
|
|
if (typeof route.options.ssr !== "function") {
|
|
existingMatch.ssr = parentOverride(route.options.ssr);
|
|
return;
|
|
}
|
|
const { search, params } = existingMatch;
|
|
const ssrFnContext = {
|
|
search: makeMaybe(search, existingMatch.searchError),
|
|
params: makeMaybe(params, existingMatch.paramsError),
|
|
location: inner.location,
|
|
matches: inner.matches.map((match) => ({
|
|
index: match.index,
|
|
pathname: match.pathname,
|
|
fullPath: match.fullPath,
|
|
staticData: match.staticData,
|
|
id: match.id,
|
|
routeId: match.routeId,
|
|
search: makeMaybe(match.search, match.searchError),
|
|
params: makeMaybe(match.params, match.paramsError),
|
|
ssr: match.ssr
|
|
}))
|
|
};
|
|
const tempSsr = route.options.ssr(ssrFnContext);
|
|
if (isPromise(tempSsr)) return tempSsr.then((ssr) => {
|
|
existingMatch.ssr = parentOverride(ssr ?? defaultSsr);
|
|
});
|
|
existingMatch.ssr = parentOverride(tempSsr ?? defaultSsr);
|
|
};
|
|
const setupPendingTimeout = (inner, matchId, route, match) => {
|
|
if (match._nonReactive.pendingTimeout !== void 0) return;
|
|
const pendingMs = route.options.pendingMs ?? inner.router.options.defaultPendingMs;
|
|
if (!!(inner.onReady && !(isServer ?? inner.router.isServer) && !resolvePreload(inner, matchId) && (route.options.loader || route.options.beforeLoad || routeNeedsPreload(route)) && typeof pendingMs === "number" && pendingMs !== Infinity && (route.options.pendingComponent ?? inner.router.options?.defaultPendingComponent))) {
|
|
const pendingTimeout = setTimeout(() => {
|
|
triggerOnReady(inner);
|
|
}, pendingMs);
|
|
match._nonReactive.pendingTimeout = pendingTimeout;
|
|
}
|
|
};
|
|
const preBeforeLoadSetup = (inner, matchId, route) => {
|
|
const existingMatch = inner.router.getMatch(matchId);
|
|
if (!existingMatch._nonReactive.beforeLoadPromise && !existingMatch._nonReactive.loaderPromise) return;
|
|
setupPendingTimeout(inner, matchId, route, existingMatch);
|
|
const then = () => {
|
|
const match = inner.router.getMatch(matchId);
|
|
if (match.preload && (match.status === "redirected" || match.status === "notFound")) handleRedirectAndNotFound(inner, match, match.error);
|
|
};
|
|
return existingMatch._nonReactive.beforeLoadPromise ? existingMatch._nonReactive.beforeLoadPromise.then(then) : then();
|
|
};
|
|
const executeBeforeLoad = (inner, matchId, index, route) => {
|
|
const match = inner.router.getMatch(matchId);
|
|
let prevLoadPromise = match._nonReactive.loadPromise;
|
|
match._nonReactive.loadPromise = createControlledPromise(() => {
|
|
prevLoadPromise?.resolve();
|
|
prevLoadPromise = void 0;
|
|
});
|
|
const { paramsError, searchError } = match;
|
|
if (paramsError) handleSerialError(inner, index, paramsError, "PARSE_PARAMS");
|
|
if (searchError) handleSerialError(inner, index, searchError, "VALIDATE_SEARCH");
|
|
setupPendingTimeout(inner, matchId, route, match);
|
|
const abortController = new AbortController();
|
|
let isPending = false;
|
|
const pending = () => {
|
|
if (isPending) return;
|
|
isPending = true;
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
isFetching: "beforeLoad",
|
|
fetchCount: prev.fetchCount + 1,
|
|
abortController
|
|
}));
|
|
};
|
|
const resolve = () => {
|
|
match._nonReactive.beforeLoadPromise?.resolve();
|
|
match._nonReactive.beforeLoadPromise = void 0;
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
isFetching: false
|
|
}));
|
|
};
|
|
if (!route.options.beforeLoad) {
|
|
inner.router.batch(() => {
|
|
pending();
|
|
resolve();
|
|
});
|
|
return;
|
|
}
|
|
match._nonReactive.beforeLoadPromise = createControlledPromise();
|
|
const context = {
|
|
...buildMatchContext(inner, index, false),
|
|
...match.__routeContext
|
|
};
|
|
const { search, params, cause } = match;
|
|
const preload = resolvePreload(inner, matchId);
|
|
const beforeLoadFnContext = {
|
|
search,
|
|
abortController,
|
|
params,
|
|
preload,
|
|
context,
|
|
location: inner.location,
|
|
navigate: (opts) => inner.router.navigate({
|
|
...opts,
|
|
_fromLocation: inner.location
|
|
}),
|
|
buildLocation: inner.router.buildLocation,
|
|
cause: preload ? "preload" : cause,
|
|
matches: inner.matches,
|
|
routeId: route.id,
|
|
...inner.router.options.additionalContext
|
|
};
|
|
const updateContext = (beforeLoadContext) => {
|
|
if (beforeLoadContext === void 0) {
|
|
inner.router.batch(() => {
|
|
pending();
|
|
resolve();
|
|
});
|
|
return;
|
|
}
|
|
if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
|
|
pending();
|
|
handleSerialError(inner, index, beforeLoadContext, "BEFORE_LOAD");
|
|
}
|
|
inner.router.batch(() => {
|
|
pending();
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
__beforeLoadContext: beforeLoadContext
|
|
}));
|
|
resolve();
|
|
});
|
|
};
|
|
let beforeLoadContext;
|
|
try {
|
|
beforeLoadContext = route.options.beforeLoad(beforeLoadFnContext);
|
|
if (isPromise(beforeLoadContext)) {
|
|
pending();
|
|
return beforeLoadContext.catch((err) => {
|
|
handleSerialError(inner, index, err, "BEFORE_LOAD");
|
|
}).then(updateContext);
|
|
}
|
|
} catch (err) {
|
|
pending();
|
|
handleSerialError(inner, index, err, "BEFORE_LOAD");
|
|
}
|
|
updateContext(beforeLoadContext);
|
|
};
|
|
const handleBeforeLoad = (inner, index) => {
|
|
const { id: matchId, routeId } = inner.matches[index];
|
|
const route = inner.router.looseRoutesById[routeId];
|
|
const serverSsr = () => {
|
|
if (isServer ?? inner.router.isServer) {
|
|
const maybePromise = isBeforeLoadSsr(inner, matchId, index, route);
|
|
if (isPromise(maybePromise)) return maybePromise.then(queueExecution);
|
|
}
|
|
return queueExecution();
|
|
};
|
|
const execute = () => executeBeforeLoad(inner, matchId, index, route);
|
|
const queueExecution = () => {
|
|
if (shouldSkipLoader(inner, matchId)) return;
|
|
const result = preBeforeLoadSetup(inner, matchId, route);
|
|
return isPromise(result) ? result.then(execute) : execute();
|
|
};
|
|
return serverSsr();
|
|
};
|
|
const executeHead = (inner, matchId, route) => {
|
|
const match = inner.router.getMatch(matchId);
|
|
if (!match) return;
|
|
if (!route.options.head && !route.options.scripts && !route.options.headers) return;
|
|
const assetContext = {
|
|
ssr: inner.router.options.ssr,
|
|
matches: inner.matches,
|
|
match,
|
|
params: match.params,
|
|
loaderData: match.loaderData
|
|
};
|
|
return Promise.all([
|
|
route.options.head?.(assetContext),
|
|
route.options.scripts?.(assetContext),
|
|
route.options.headers?.(assetContext)
|
|
]).then(([headFnContent, scripts, headers]) => {
|
|
return {
|
|
meta: headFnContent?.meta,
|
|
links: headFnContent?.links,
|
|
headScripts: headFnContent?.scripts,
|
|
headers,
|
|
scripts,
|
|
styles: headFnContent?.styles
|
|
};
|
|
});
|
|
};
|
|
const getLoaderContext = (inner, matchPromises, matchId, index, route) => {
|
|
const parentMatchPromise = matchPromises[index - 1];
|
|
const { params, loaderDeps, abortController, cause } = inner.router.getMatch(matchId);
|
|
const context = buildMatchContext(inner, index);
|
|
const preload = resolvePreload(inner, matchId);
|
|
return {
|
|
params,
|
|
deps: loaderDeps,
|
|
preload: !!preload,
|
|
parentMatchPromise,
|
|
abortController,
|
|
context,
|
|
location: inner.location,
|
|
navigate: (opts) => inner.router.navigate({
|
|
...opts,
|
|
_fromLocation: inner.location
|
|
}),
|
|
cause: preload ? "preload" : cause,
|
|
route,
|
|
...inner.router.options.additionalContext
|
|
};
|
|
};
|
|
const runLoader = async (inner, matchPromises, matchId, index, route) => {
|
|
try {
|
|
const match = inner.router.getMatch(matchId);
|
|
try {
|
|
if (!(isServer ?? inner.router.isServer) || match.ssr === true) loadRouteChunk(route);
|
|
const routeLoader = route.options.loader;
|
|
const loader = typeof routeLoader === "function" ? routeLoader : routeLoader?.handler;
|
|
const loaderResult = loader?.(getLoaderContext(inner, matchPromises, matchId, index, route));
|
|
const loaderResultIsPromise = !!loader && isPromise(loaderResult);
|
|
if (!!(loaderResultIsPromise || route._lazyPromise || route._componentsPromise || route.options.head || route.options.scripts || route.options.headers || match._nonReactive.minPendingPromise)) inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
isFetching: "loader"
|
|
}));
|
|
if (loader) {
|
|
const loaderData = loaderResultIsPromise ? await loaderResult : loaderResult;
|
|
handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), loaderData);
|
|
if (loaderData !== void 0) inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
loaderData
|
|
}));
|
|
}
|
|
if (route._lazyPromise) await route._lazyPromise;
|
|
const pendingPromise = match._nonReactive.minPendingPromise;
|
|
if (pendingPromise) await pendingPromise;
|
|
if (route._componentsPromise) await route._componentsPromise;
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
error: void 0,
|
|
context: buildMatchContext(inner, index),
|
|
status: "success",
|
|
isFetching: false,
|
|
updatedAt: Date.now()
|
|
}));
|
|
} catch (e) {
|
|
let error = e;
|
|
if (error?.name === "AbortError") {
|
|
if (match.abortController.signal.aborted) {
|
|
match._nonReactive.loaderPromise?.resolve();
|
|
match._nonReactive.loaderPromise = void 0;
|
|
return;
|
|
}
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
status: prev.status === "pending" ? "success" : prev.status,
|
|
isFetching: false,
|
|
context: buildMatchContext(inner, index)
|
|
}));
|
|
return;
|
|
}
|
|
const pendingPromise = match._nonReactive.minPendingPromise;
|
|
if (pendingPromise) await pendingPromise;
|
|
if (isNotFound(e)) await route.options.notFoundComponent?.preload?.();
|
|
handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), e);
|
|
try {
|
|
route.options.onError?.(e);
|
|
} catch (onErrorError) {
|
|
error = onErrorError;
|
|
handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), onErrorError);
|
|
}
|
|
if (!isRedirect(error) && !isNotFound(error)) await loadRouteChunk(route, ["errorComponent"]);
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
error,
|
|
context: buildMatchContext(inner, index),
|
|
status: "error",
|
|
isFetching: false
|
|
}));
|
|
}
|
|
} catch (err) {
|
|
const match = inner.router.getMatch(matchId);
|
|
if (match) match._nonReactive.loaderPromise = void 0;
|
|
handleRedirectAndNotFound(inner, match, err);
|
|
}
|
|
};
|
|
const loadRouteMatch = async (inner, matchPromises, index) => {
|
|
async function handleLoader(preload, prevMatch, previousRouteMatchId, match, route) {
|
|
const age = Date.now() - prevMatch.updatedAt;
|
|
const staleAge = preload ? route.options.preloadStaleTime ?? inner.router.options.defaultPreloadStaleTime ?? 3e4 : route.options.staleTime ?? inner.router.options.defaultStaleTime ?? 0;
|
|
const shouldReloadOption = route.options.shouldReload;
|
|
const shouldReload = typeof shouldReloadOption === "function" ? shouldReloadOption(getLoaderContext(inner, matchPromises, matchId, index, route)) : shouldReloadOption;
|
|
const { status, invalid } = match;
|
|
const staleMatchShouldReload = age >= staleAge && (!!inner.forceStaleReload || match.cause === "enter" || previousRouteMatchId !== void 0 && previousRouteMatchId !== match.id);
|
|
loaderShouldRunAsync = status === "success" && (invalid || (shouldReload ?? staleMatchShouldReload));
|
|
if (preload && route.options.preload === false) {} else if (loaderShouldRunAsync && !inner.sync && shouldReloadInBackground) {
|
|
loaderIsRunningAsync = true;
|
|
(async () => {
|
|
try {
|
|
await runLoader(inner, matchPromises, matchId, index, route);
|
|
const match = inner.router.getMatch(matchId);
|
|
match._nonReactive.loaderPromise?.resolve();
|
|
match._nonReactive.loadPromise?.resolve();
|
|
match._nonReactive.loaderPromise = void 0;
|
|
match._nonReactive.loadPromise = void 0;
|
|
} catch (err) {
|
|
if (isRedirect(err)) await inner.router.navigate(err.options);
|
|
}
|
|
})();
|
|
} else if (status !== "success" || loaderShouldRunAsync) await runLoader(inner, matchPromises, matchId, index, route);
|
|
else syncMatchContext(inner, matchId, index);
|
|
}
|
|
const { id: matchId, routeId } = inner.matches[index];
|
|
let loaderShouldRunAsync = false;
|
|
let loaderIsRunningAsync = false;
|
|
const route = inner.router.looseRoutesById[routeId];
|
|
const routeLoader = route.options.loader;
|
|
const shouldReloadInBackground = ((typeof routeLoader === "function" ? void 0 : routeLoader?.staleReloadMode) ?? inner.router.options.defaultStaleReloadMode) !== "blocking";
|
|
if (shouldSkipLoader(inner, matchId)) {
|
|
if (!inner.router.getMatch(matchId)) return inner.matches[index];
|
|
syncMatchContext(inner, matchId, index);
|
|
if (isServer ?? inner.router.isServer) return inner.router.getMatch(matchId);
|
|
} else {
|
|
const prevMatch = inner.router.getMatch(matchId);
|
|
const activeIdAtIndex = inner.router.stores.matchesId.get()[index];
|
|
const previousRouteMatchId = (activeIdAtIndex && inner.router.stores.matchStores.get(activeIdAtIndex) || null)?.routeId === routeId ? activeIdAtIndex : inner.router.stores.matches.get().find((d) => d.routeId === routeId)?.id;
|
|
const preload = resolvePreload(inner, matchId);
|
|
if (prevMatch._nonReactive.loaderPromise) {
|
|
if (prevMatch.status === "success" && !inner.sync && !prevMatch.preload && shouldReloadInBackground) return prevMatch;
|
|
await prevMatch._nonReactive.loaderPromise;
|
|
const match = inner.router.getMatch(matchId);
|
|
const error = match._nonReactive.error || match.error;
|
|
if (error) handleRedirectAndNotFound(inner, match, error);
|
|
if (match.status === "pending") await handleLoader(preload, prevMatch, previousRouteMatchId, match, route);
|
|
} else {
|
|
const nextPreload = preload && !inner.router.stores.matchStores.has(matchId);
|
|
const match = inner.router.getMatch(matchId);
|
|
match._nonReactive.loaderPromise = createControlledPromise();
|
|
if (nextPreload !== match.preload) inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
preload: nextPreload
|
|
}));
|
|
await handleLoader(preload, prevMatch, previousRouteMatchId, match, route);
|
|
}
|
|
}
|
|
const match = inner.router.getMatch(matchId);
|
|
if (!loaderIsRunningAsync) {
|
|
match._nonReactive.loaderPromise?.resolve();
|
|
match._nonReactive.loadPromise?.resolve();
|
|
match._nonReactive.loadPromise = void 0;
|
|
}
|
|
clearTimeout(match._nonReactive.pendingTimeout);
|
|
match._nonReactive.pendingTimeout = void 0;
|
|
if (!loaderIsRunningAsync) match._nonReactive.loaderPromise = void 0;
|
|
match._nonReactive.dehydrated = void 0;
|
|
const nextIsFetching = loaderIsRunningAsync ? match.isFetching : false;
|
|
if (nextIsFetching !== match.isFetching || match.invalid !== false) {
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
isFetching: nextIsFetching,
|
|
invalid: false
|
|
}));
|
|
return inner.router.getMatch(matchId);
|
|
} else return match;
|
|
};
|
|
async function loadMatches(arg) {
|
|
const inner = arg;
|
|
const matchPromises = [];
|
|
if (!(isServer ?? inner.router.isServer) && hasForcePendingActiveMatch(inner.router)) triggerOnReady(inner);
|
|
let beforeLoadNotFound;
|
|
for (let i = 0; i < inner.matches.length; i++) {
|
|
try {
|
|
const beforeLoad = handleBeforeLoad(inner, i);
|
|
if (isPromise(beforeLoad)) await beforeLoad;
|
|
} catch (err) {
|
|
if (isRedirect(err)) throw err;
|
|
if (isNotFound(err)) beforeLoadNotFound = err;
|
|
else if (!inner.preload) throw err;
|
|
break;
|
|
}
|
|
if (inner.serialError || inner.firstBadMatchIndex != null) break;
|
|
}
|
|
const baseMaxIndexExclusive = inner.firstBadMatchIndex ?? inner.matches.length;
|
|
const boundaryIndex = beforeLoadNotFound && !inner.preload ? getNotFoundBoundaryIndex(inner, beforeLoadNotFound) : void 0;
|
|
const maxIndexExclusive = beforeLoadNotFound && inner.preload ? 0 : boundaryIndex !== void 0 ? Math.min(boundaryIndex + 1, baseMaxIndexExclusive) : baseMaxIndexExclusive;
|
|
let firstNotFound;
|
|
let firstUnhandledRejection;
|
|
for (let i = 0; i < maxIndexExclusive; i++) matchPromises.push(loadRouteMatch(inner, matchPromises, i));
|
|
try {
|
|
await Promise.all(matchPromises);
|
|
} catch {
|
|
const settled = await Promise.allSettled(matchPromises);
|
|
for (const result of settled) {
|
|
if (result.status !== "rejected") continue;
|
|
const reason = result.reason;
|
|
if (isRedirect(reason)) throw reason;
|
|
if (isNotFound(reason)) firstNotFound ??= reason;
|
|
else firstUnhandledRejection ??= reason;
|
|
}
|
|
if (firstUnhandledRejection !== void 0) throw firstUnhandledRejection;
|
|
}
|
|
const notFoundToThrow = firstNotFound ?? (beforeLoadNotFound && !inner.preload ? beforeLoadNotFound : void 0);
|
|
let headMaxIndex = inner.firstBadMatchIndex !== void 0 ? inner.firstBadMatchIndex : inner.matches.length - 1;
|
|
if (!notFoundToThrow && beforeLoadNotFound && inner.preload) return inner.matches;
|
|
if (notFoundToThrow) {
|
|
const renderedBoundaryIndex = getNotFoundBoundaryIndex(inner, notFoundToThrow);
|
|
if (renderedBoundaryIndex === void 0) {
|
|
if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Could not find match for notFound boundary");
|
|
invariant();
|
|
}
|
|
const boundaryMatch = inner.matches[renderedBoundaryIndex];
|
|
const boundaryRoute = inner.router.looseRoutesById[boundaryMatch.routeId];
|
|
const defaultNotFoundComponent = inner.router.options?.defaultNotFoundComponent;
|
|
if (!boundaryRoute.options.notFoundComponent && defaultNotFoundComponent) boundaryRoute.options.notFoundComponent = defaultNotFoundComponent;
|
|
notFoundToThrow.routeId = boundaryMatch.routeId;
|
|
const boundaryIsRoot = boundaryMatch.routeId === inner.router.routeTree.id;
|
|
inner.updateMatch(boundaryMatch.id, (prev) => ({
|
|
...prev,
|
|
...boundaryIsRoot ? {
|
|
status: "success",
|
|
globalNotFound: true,
|
|
error: void 0
|
|
} : {
|
|
status: "notFound",
|
|
error: notFoundToThrow
|
|
},
|
|
isFetching: false
|
|
}));
|
|
headMaxIndex = renderedBoundaryIndex;
|
|
await loadRouteChunk(boundaryRoute, ["notFoundComponent"]);
|
|
} else if (!inner.preload) {
|
|
const rootMatch = inner.matches[0];
|
|
if (!rootMatch.globalNotFound) {
|
|
if (inner.router.getMatch(rootMatch.id)?.globalNotFound) inner.updateMatch(rootMatch.id, (prev) => ({
|
|
...prev,
|
|
globalNotFound: false,
|
|
error: void 0
|
|
}));
|
|
}
|
|
}
|
|
if (inner.serialError && inner.firstBadMatchIndex !== void 0) {
|
|
const errorRoute = inner.router.looseRoutesById[inner.matches[inner.firstBadMatchIndex].routeId];
|
|
await loadRouteChunk(errorRoute, ["errorComponent"]);
|
|
}
|
|
for (let i = 0; i <= headMaxIndex; i++) {
|
|
const { id: matchId, routeId } = inner.matches[i];
|
|
const route = inner.router.looseRoutesById[routeId];
|
|
try {
|
|
const headResult = executeHead(inner, matchId, route);
|
|
if (headResult) {
|
|
const head = await headResult;
|
|
inner.updateMatch(matchId, (prev) => ({
|
|
...prev,
|
|
...head
|
|
}));
|
|
}
|
|
} catch (err) {
|
|
console.error(`Error executing head for route ${routeId}:`, err);
|
|
}
|
|
}
|
|
const readyPromise = triggerOnReady(inner);
|
|
if (isPromise(readyPromise)) await readyPromise;
|
|
if (notFoundToThrow) throw notFoundToThrow;
|
|
if (inner.serialError && !inner.preload && !inner.onReady) throw inner.serialError;
|
|
return inner.matches;
|
|
}
|
|
function preloadRouteComponents(route, componentTypesToLoad) {
|
|
const preloads = componentTypesToLoad.map((type) => route.options[type]?.preload?.()).filter(Boolean);
|
|
if (preloads.length === 0) return void 0;
|
|
return Promise.all(preloads);
|
|
}
|
|
function loadRouteChunk(route, componentTypesToLoad = componentTypes) {
|
|
if (!route._lazyLoaded && route._lazyPromise === void 0) if (route.lazyFn) route._lazyPromise = route.lazyFn().then((lazyRoute) => {
|
|
const { id: _id, ...options } = lazyRoute.options;
|
|
Object.assign(route.options, options);
|
|
route._lazyLoaded = true;
|
|
route._lazyPromise = void 0;
|
|
});
|
|
else route._lazyLoaded = true;
|
|
const runAfterLazy = () => route._componentsLoaded ? void 0 : componentTypesToLoad === componentTypes ? (() => {
|
|
if (route._componentsPromise === void 0) {
|
|
const componentsPromise = preloadRouteComponents(route, componentTypes);
|
|
if (componentsPromise) route._componentsPromise = componentsPromise.then(() => {
|
|
route._componentsLoaded = true;
|
|
route._componentsPromise = void 0;
|
|
});
|
|
else route._componentsLoaded = true;
|
|
}
|
|
return route._componentsPromise;
|
|
})() : preloadRouteComponents(route, componentTypesToLoad);
|
|
return route._lazyPromise ? route._lazyPromise.then(runAfterLazy) : runAfterLazy();
|
|
}
|
|
function makeMaybe(value, error) {
|
|
if (error) return {
|
|
status: "error",
|
|
error
|
|
};
|
|
return {
|
|
status: "success",
|
|
value
|
|
};
|
|
}
|
|
function routeNeedsPreload(route) {
|
|
for (const componentType of componentTypes) if (route.options[componentType]?.preload) return true;
|
|
return false;
|
|
}
|
|
const componentTypes = [
|
|
"component",
|
|
"errorComponent",
|
|
"pendingComponent",
|
|
"notFoundComponent"
|
|
];
|
|
//#endregion
|
|
export { loadMatches, loadRouteChunk, routeNeedsPreload };
|
|
|
|
//# sourceMappingURL=load-matches.js.map |