UNPKG

8.97 kB JavaScript View Raw
1/**
2 * react-router v8.5.1
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { createPath } from "../../router/history.js";
12import { joinPaths } from "../../router/utils.js";
13import { createClientRoutes } from "./routes.js";
14import * as React$1 from "react";
15//#region lib/dom/ssr/fog-of-war.ts
16const nextPaths = /* @__PURE__ */ new Set();
17const discoveredPathsMaxSize = 1e3;
18const discoveredPaths = /* @__PURE__ */ new Set();
19const URL_LIMIT = 7680;
20function getPathsWithAncestors(paths) {
21 let result = /* @__PURE__ */ new Set();
22 paths.forEach((path) => {
23 if (!path.startsWith("/")) path = `/${path}`;
24 for (let i = 1; i < path.length; i++) if (path[i] === "/") result.add(path.slice(0, i));
25 result.add(path);
26 });
27 return Array.from(result);
28}
29function isFogOfWarEnabled(routeDiscovery, ssr) {
30 return routeDiscovery.mode === "lazy" && ssr;
31}
32const MANIFEST_MATCH_COST_LIMIT = 5e4;
33const MANIFEST_MATCH_COST_SEGMENT_THRESHOLD = 10;
34function shouldUseInitialRouteDiscovery(pathname, matchCost = 0) {
35 let segmentCount = pathname.split("/").length - 1;
36 return Math.max(segmentCount - 1, 0) * pathname.length + 1 > 7680 || segmentCount > MANIFEST_MATCH_COST_SEGMENT_THRESHOLD && segmentCount * matchCost > MANIFEST_MATCH_COST_LIMIT;
37}
38function getPartialManifest({ sri, ...manifest }, router) {
39 let routeIds = new Set(router.state.matches.map((m) => m.route.id));
40 let segments = router.state.location.pathname.split("/").filter(Boolean);
41 let paths = ["/"];
42 segments.pop();
43 while (segments.length > 0) {
44 paths.push(`/${segments.join("/")}`);
45 segments.pop();
46 }
47 paths.forEach((path) => {
48 let matches = router.match(path);
49 if (matches) matches.forEach((m) => routeIds.add(m.route.id));
50 });
51 let initialRoutes = [...routeIds].reduce((acc, id) => Object.assign(acc, { [id]: manifest.routes[id] }), {});
52 return {
53 ...manifest,
54 routes: initialRoutes,
55 sri: sri ? true : void 0
56 };
57}
58function getPatchRoutesOnNavigationFunction(getRouter, manifest, routeModules, ssr, routeDiscovery, isSpaMode, basename, fetchImplementation = (request) => fetch(request), serverOrigin, isApiOnly = false) {
59 if (!isFogOfWarEnabled(routeDiscovery, ssr)) return;
60 return async ({ path, patch, signal, fetcherKey }) => {
61 if (discoveredPaths.has(path)) return;
62 let { state } = getRouter();
63 await fetchAndApplyManifestPatches([path], fetcherKey ? window.location.href : createPath(state.navigation.location || state.location), manifest, routeModules, ssr, isSpaMode, basename, routeDiscovery.manifestPath, patch, signal, fetchImplementation, serverOrigin, isApiOnly);
64 };
65}
66function useFogOFWarDiscovery(router, manifest, routeModules, ssr, routeDiscovery, isSpaMode, fetchImplementation = (request) => fetch(request), serverOrigin, isApiOnly = false) {
67 React$1.useEffect(() => {
68 if (!isFogOfWarEnabled(routeDiscovery, ssr) || window.navigator?.connection?.saveData === true) return;
69 function registerElement(el) {
70 let path = el.tagName === "FORM" ? el.getAttribute("action") : el.getAttribute("href");
71 if (!path) return;
72 let pathname = el.tagName === "A" ? el.pathname : new URL(path, window.location.origin).pathname;
73 if (!discoveredPaths.has(pathname)) nextPaths.add(pathname);
74 }
75 async function fetchPatches() {
76 document.querySelectorAll("a[data-discover], form[data-discover]").forEach(registerElement);
77 let lazyPaths = Array.from(nextPaths.keys()).filter((path) => {
78 if (discoveredPaths.has(path)) {
79 nextPaths.delete(path);
80 return false;
81 }
82 return true;
83 });
84 if (lazyPaths.length === 0) return;
85 try {
86 await fetchAndApplyManifestPatches(lazyPaths, null, manifest, routeModules, ssr, isSpaMode, router.basename, routeDiscovery.manifestPath, router.patchRoutes, void 0, fetchImplementation, serverOrigin, isApiOnly);
87 } catch (e) {
88 console.error("Failed to fetch manifest patches", e);
89 }
90 }
91 let debouncedFetchPatches = debounce(fetchPatches, 100);
92 fetchPatches();
93 let observer = new MutationObserver(() => debouncedFetchPatches());
94 observer.observe(document.documentElement, {
95 subtree: true,
96 childList: true,
97 attributes: true,
98 attributeFilter: [
99 "data-discover",
100 "href",
101 "action"
102 ]
103 });
104 return () => observer.disconnect();
105 }, [
106 ssr,
107 isSpaMode,
108 manifest,
109 routeModules,
110 router,
111 routeDiscovery,
112 fetchImplementation,
113 serverOrigin,
114 isApiOnly
115 ]);
116}
117function getManifestPathsParam(paths) {
118 return paths.slice().sort().map((path) => path.replace(/,/g, "%2C")).join(",");
119}
120function getManifestPath(_manifestPath, basename) {
121 let manifestPath = _manifestPath || "/__manifest";
122 return basename == null ? manifestPath : joinPaths([basename, manifestPath]);
123}
124const MANIFEST_VERSION_STORAGE_KEY = "react-router-manifest-version";
125const MANIFEST_RELOAD_TIMEOUT = 5e3;
126let pendingManifestReload;
127async function handleClientVersionMismatch(needsReload, version, errorReloadPath) {
128 if (pendingManifestReload) return pendingManifestReload;
129 if (!needsReload) {
130 try {
131 sessionStorage.removeItem(MANIFEST_VERSION_STORAGE_KEY);
132 } catch {}
133 return false;
134 }
135 if (!errorReloadPath) {
136 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.");
137 return true;
138 }
139 let alreadyReloaded = false;
140 try {
141 alreadyReloaded = sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) === version;
142 if (!alreadyReloaded) sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, version);
143 } catch {}
144 if (alreadyReloaded) throw new Error("Unable to discover routes due to manifest version mismatch.");
145 let rejectReload;
146 let reloadPromise = new Promise((_, reject) => {
147 rejectReload = reject;
148 });
149 pendingManifestReload = reloadPromise;
150 let failReload = () => {
151 clearTimeout(timeout);
152 window.removeEventListener("pageshow", onPageShow);
153 pendingManifestReload = void 0;
154 rejectReload(/* @__PURE__ */ new Error("Unable to discover routes due to manifest version mismatch."));
155 };
156 let timeout = setTimeout(failReload, MANIFEST_RELOAD_TIMEOUT);
157 let onPageShow = (event) => {
158 if (event.persisted) failReload();
159 };
160 window.addEventListener("pageshow", onPageShow);
161 try {
162 window.location.href = errorReloadPath;
163 console.warn("Detected manifest version mismatch, reloading...");
164 } catch {
165 failReload();
166 }
167 return reloadPromise;
168}
169async function fetchAndApplyManifestPatches(paths, errorReloadPath, manifest, routeModules, ssr, isSpaMode, basename, manifestPath, patchRoutes, signal, fetchImplementation = (request) => fetch(request), serverOrigin, isApiOnly = false) {
170 paths = getPathsWithAncestors(paths);
171 const searchParams = new URLSearchParams();
172 searchParams.set("paths", getManifestPathsParam(paths));
173 searchParams.set("version", manifest.version);
174 let url = new URL(getManifestPath(manifestPath, basename), serverOrigin ?? window.location.origin);
175 url.search = searchParams.toString();
176 if (url.toString().length > 7680) {
177 nextPaths.clear();
178 return;
179 }
180 let serverPatches;
181 try {
182 let res = await fetchImplementation(new Request(url, { signal }), { type: "manifest" });
183 if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
184 if (await handleClientVersionMismatch(res.status === 204 && res.headers.has("X-Remix-Reload-Document"), manifest.version, errorReloadPath)) return;
185 serverPatches = await res.json();
186 } catch (e) {
187 if (signal?.aborted) return;
188 throw e;
189 }
190 if (signal?.aborted) return;
191 let knownRoutes = new Set(Object.keys(manifest.routes));
192 let patches = Object.values(serverPatches).reduce((acc, route) => {
193 if (route && !knownRoutes.has(route.id)) acc[route.id] = route;
194 return acc;
195 }, {});
196 Object.assign(manifest.routes, patches);
197 paths.forEach((p) => addToFifoQueue(p, discoveredPaths));
198 let parentIds = /* @__PURE__ */ new Set();
199 Object.values(patches).forEach((patch) => {
200 if (patch && (!patch.parentId || !patches[patch.parentId])) parentIds.add(patch.parentId);
201 });
202 parentIds.forEach((parentId) => patchRoutes(parentId || null, createClientRoutes(patches, routeModules, null, ssr, isSpaMode, parentId, void 0, void 0, isApiOnly)));
203}
204function addToFifoQueue(path, queue) {
205 if (queue.size >= discoveredPathsMaxSize) {
206 let first = queue.values().next().value;
207 if (first !== void 0) queue.delete(first);
208 }
209 queue.add(path);
210}
211function debounce(callback, wait) {
212 let timeoutId;
213 return (...args) => {
214 window.clearTimeout(timeoutId);
215 timeoutId = window.setTimeout(() => callback(...args), wait);
216 };
217}
218//#endregion
219export { URL_LIMIT, getManifestPath, getManifestPathsParam, getPartialManifest, getPatchRoutesOnNavigationFunction, getPathsWithAncestors, handleClientVersionMismatch, isFogOfWarEnabled, shouldUseInitialRouteDiscovery, useFogOFWarDiscovery };