react-router
Version:
Declarative routing for React
181 lines (180 loc) • 7.04 kB
JavaScript
/**
* react-router v8.0.1
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
import { createPath } from "../../router/history.js";
import { joinPaths, matchRoutesImpl } from "../../router/utils.js";
import { createClientRoutes } from "./routes.js";
import * as React$1 from "react";
//#region lib/dom/ssr/fog-of-war.ts
const nextPaths = /* @__PURE__ */ new Set();
const discoveredPathsMaxSize = 1e3;
const discoveredPaths = /* @__PURE__ */ new Set();
const URL_LIMIT = 7680;
function getPathsWithAncestors(paths) {
let result = /* @__PURE__ */ new Set();
paths.forEach((path) => {
if (!path.startsWith("/")) path = `/${path}`;
for (let i = 1; i < path.length; i++) if (path[i] === "/") result.add(path.slice(0, i));
result.add(path);
});
return Array.from(result);
}
function isFogOfWarEnabled(routeDiscovery, ssr) {
return routeDiscovery.mode === "lazy" && ssr === true;
}
function getPartialManifest({ sri, ...manifest }, router) {
let routeIds = new Set(router.state.matches.map((m) => m.route.id));
let segments = router.state.location.pathname.split("/").filter(Boolean);
let paths = ["/"];
segments.pop();
while (segments.length > 0) {
paths.push(`/${segments.join("/")}`);
segments.pop();
}
paths.forEach((path) => {
let matches = matchRoutesImpl(router.routes, path, router.basename || "/", false, router.branches);
if (matches) matches.forEach((m) => routeIds.add(m.route.id));
});
let initialRoutes = [...routeIds].reduce((acc, id) => Object.assign(acc, { [id]: manifest.routes[id] }), {});
return {
...manifest,
routes: initialRoutes,
sri: sri ? true : void 0
};
}
function getPatchRoutesOnNavigationFunction(getRouter, manifest, routeModules, ssr, routeDiscovery, isSpaMode, basename) {
if (!isFogOfWarEnabled(routeDiscovery, ssr)) return;
return async ({ path, patch, signal, fetcherKey }) => {
if (discoveredPaths.has(path)) return;
let { state } = getRouter();
await fetchAndApplyManifestPatches([path], fetcherKey ? window.location.href : createPath(state.navigation.location || state.location), manifest, routeModules, ssr, isSpaMode, basename, routeDiscovery.manifestPath, patch, signal);
};
}
function useFogOFWarDiscovery(router, manifest, routeModules, ssr, routeDiscovery, isSpaMode) {
React$1.useEffect(() => {
if (!isFogOfWarEnabled(routeDiscovery, ssr) || window.navigator?.connection?.saveData === true) return;
function registerElement(el) {
let path = el.tagName === "FORM" ? el.getAttribute("action") : el.getAttribute("href");
if (!path) return;
let pathname = el.tagName === "A" ? el.pathname : new URL(path, window.location.origin).pathname;
if (!discoveredPaths.has(pathname)) nextPaths.add(pathname);
}
async function fetchPatches() {
document.querySelectorAll("a[data-discover], form[data-discover]").forEach(registerElement);
let lazyPaths = Array.from(nextPaths.keys()).filter((path) => {
if (discoveredPaths.has(path)) {
nextPaths.delete(path);
return false;
}
return true;
});
if (lazyPaths.length === 0) return;
try {
await fetchAndApplyManifestPatches(lazyPaths, null, manifest, routeModules, ssr, isSpaMode, router.basename, routeDiscovery.manifestPath, router.patchRoutes);
} catch (e) {
console.error("Failed to fetch manifest patches", e);
}
}
let debouncedFetchPatches = debounce(fetchPatches, 100);
fetchPatches();
let observer = new MutationObserver(() => debouncedFetchPatches());
observer.observe(document.documentElement, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: [
"data-discover",
"href",
"action"
]
});
return () => observer.disconnect();
}, [
ssr,
isSpaMode,
manifest,
routeModules,
router,
routeDiscovery
]);
}
function getManifestPath(_manifestPath, basename) {
let manifestPath = _manifestPath || "/__manifest";
return basename == null ? manifestPath : joinPaths([basename, manifestPath]);
}
const MANIFEST_VERSION_STORAGE_KEY = "react-router-manifest-version";
async function fetchAndApplyManifestPatches(paths, errorReloadPath, manifest, routeModules, ssr, isSpaMode, basename, manifestPath, patchRoutes, signal) {
paths = getPathsWithAncestors(paths);
const searchParams = new URLSearchParams();
searchParams.set("paths", paths.sort().join(","));
searchParams.set("version", manifest.version);
let url = new URL(getManifestPath(manifestPath, basename), window.location.origin);
url.search = searchParams.toString();
if (url.toString().length > 7680) {
nextPaths.clear();
return;
}
let serverPatches;
try {
let res = await fetch(url, { signal });
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
else if (res.status === 204 && res.headers.has("X-Remix-Reload-Document")) {
if (!errorReloadPath) {
console.warn("Detected a manifest version mismatch during eager route discovery. The next navigation/fetch to an undiscovered route will result in a new document navigation to sync up with the latest manifest.");
return;
}
try {
if (sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) === manifest.version) {
console.error("Unable to discover routes due to manifest version mismatch.");
return;
}
sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, manifest.version);
} catch {}
window.location.href = errorReloadPath;
console.warn("Detected manifest version mismatch, reloading...");
await new Promise(() => {});
} else if (res.status >= 400) throw new Error(await res.text());
try {
sessionStorage.removeItem(MANIFEST_VERSION_STORAGE_KEY);
} catch {}
serverPatches = await res.json();
} catch (e) {
if (signal?.aborted) return;
throw e;
}
let knownRoutes = new Set(Object.keys(manifest.routes));
let patches = Object.values(serverPatches).reduce((acc, route) => {
if (route && !knownRoutes.has(route.id)) acc[route.id] = route;
return acc;
}, {});
Object.assign(manifest.routes, patches);
paths.forEach((p) => addToFifoQueue(p, discoveredPaths));
let parentIds = /* @__PURE__ */ new Set();
Object.values(patches).forEach((patch) => {
if (patch && (!patch.parentId || !patches[patch.parentId])) parentIds.add(patch.parentId);
});
parentIds.forEach((parentId) => patchRoutes(parentId || null, createClientRoutes(patches, routeModules, null, ssr, isSpaMode, parentId)));
}
function addToFifoQueue(path, queue) {
if (queue.size >= discoveredPathsMaxSize) {
let first = queue.values().next().value;
if (first !== void 0) queue.delete(first);
}
queue.add(path);
}
function debounce(callback, wait) {
let timeoutId;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => callback(...args), wait);
};
}
//#endregion
export { URL_LIMIT, getManifestPath, getPartialManifest, getPatchRoutesOnNavigationFunction, getPathsWithAncestors, isFogOfWarEnabled, useFogOFWarDiscovery };