UNPKG

7.91 kBJavaScriptView Raw
1/**
2 * react-router v8.4.0
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 === true;
31}
32function getPartialManifest({ sri, ...manifest }, router) {
33 let routeIds = new Set(router.state.matches.map((m) => m.route.id));
34 let segments = router.state.location.pathname.split("/").filter(Boolean);
35 let paths = ["/"];
36 segments.pop();
37 while (segments.length > 0) {
38 paths.push(`/${segments.join("/")}`);
39 segments.pop();
40 }
41 paths.forEach((path) => {
42 let matches = router.match(path);
43 if (matches) matches.forEach((m) => routeIds.add(m.route.id));
44 });
45 let initialRoutes = [...routeIds].reduce((acc, id) => Object.assign(acc, { [id]: manifest.routes[id] }), {});
46 return {
47 ...manifest,
48 routes: initialRoutes,
49 sri: sri ? true : void 0
50 };
51}
52function getPatchRoutesOnNavigationFunction(getRouter, manifest, routeModules, ssr, routeDiscovery, isSpaMode, basename) {
53 if (!isFogOfWarEnabled(routeDiscovery, ssr)) return;
54 return async ({ path, patch, signal, fetcherKey }) => {
55 if (discoveredPaths.has(path)) return;
56 let { state } = getRouter();
57 await fetchAndApplyManifestPatches([path], fetcherKey ? window.location.href : createPath(state.navigation.location || state.location), manifest, routeModules, ssr, isSpaMode, basename, routeDiscovery.manifestPath, patch, signal);
58 };
59}
60function useFogOFWarDiscovery(router, manifest, routeModules, ssr, routeDiscovery, isSpaMode) {
61 React$1.useEffect(() => {
62 if (!isFogOfWarEnabled(routeDiscovery, ssr) || window.navigator?.connection?.saveData === true) return;
63 function registerElement(el) {
64 let path = el.tagName === "FORM" ? el.getAttribute("action") : el.getAttribute("href");
65 if (!path) return;
66 let pathname = el.tagName === "A" ? el.pathname : new URL(path, window.location.origin).pathname;
67 if (!discoveredPaths.has(pathname)) nextPaths.add(pathname);
68 }
69 async function fetchPatches() {
70 document.querySelectorAll("a[data-discover], form[data-discover]").forEach(registerElement);
71 let lazyPaths = Array.from(nextPaths.keys()).filter((path) => {
72 if (discoveredPaths.has(path)) {
73 nextPaths.delete(path);
74 return false;
75 }
76 return true;
77 });
78 if (lazyPaths.length === 0) return;
79 try {
80 await fetchAndApplyManifestPatches(lazyPaths, null, manifest, routeModules, ssr, isSpaMode, router.basename, routeDiscovery.manifestPath, router.patchRoutes);
81 } catch (e) {
82 console.error("Failed to fetch manifest patches", e);
83 }
84 }
85 let debouncedFetchPatches = debounce(fetchPatches, 100);
86 fetchPatches();
87 let observer = new MutationObserver(() => debouncedFetchPatches());
88 observer.observe(document.documentElement, {
89 subtree: true,
90 childList: true,
91 attributes: true,
92 attributeFilter: [
93 "data-discover",
94 "href",
95 "action"
96 ]
97 });
98 return () => observer.disconnect();
99 }, [
100 ssr,
101 isSpaMode,
102 manifest,
103 routeModules,
104 router,
105 routeDiscovery
106 ]);
107}
108function getManifestPath(_manifestPath, basename) {
109 let manifestPath = _manifestPath || "/__manifest";
110 return basename == null ? manifestPath : joinPaths([basename, manifestPath]);
111}
112const MANIFEST_VERSION_STORAGE_KEY = "react-router-manifest-version";
113const MANIFEST_RELOAD_TIMEOUT = 5e3;
114let pendingManifestReload;
115async function handleClientVersionMismatch(needsReload, version, errorReloadPath) {
116 if (pendingManifestReload) return pendingManifestReload;
117 if (!needsReload) {
118 try {
119 sessionStorage.removeItem(MANIFEST_VERSION_STORAGE_KEY);
120 } catch {}
121 return false;
122 }
123 if (!errorReloadPath) {
124 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.");
125 return true;
126 }
127 let alreadyReloaded = false;
128 try {
129 alreadyReloaded = sessionStorage.getItem(MANIFEST_VERSION_STORAGE_KEY) === version;
130 if (!alreadyReloaded) sessionStorage.setItem(MANIFEST_VERSION_STORAGE_KEY, version);
131 } catch {}
132 if (alreadyReloaded) throw new Error("Unable to discover routes due to manifest version mismatch.");
133 let rejectReload;
134 let reloadPromise = new Promise((_, reject) => {
135 rejectReload = reject;
136 });
137 pendingManifestReload = reloadPromise;
138 let failReload = () => {
139 clearTimeout(timeout);
140 window.removeEventListener("pageshow", onPageShow);
141 pendingManifestReload = void 0;
142 rejectReload(/* @__PURE__ */ new Error("Unable to discover routes due to manifest version mismatch."));
143 };
144 let timeout = setTimeout(failReload, MANIFEST_RELOAD_TIMEOUT);
145 let onPageShow = (event) => {
146 if (event.persisted) failReload();
147 };
148 window.addEventListener("pageshow", onPageShow);
149 try {
150 window.location.href = errorReloadPath;
151 console.warn("Detected manifest version mismatch, reloading...");
152 } catch {
153 failReload();
154 }
155 return reloadPromise;
156}
157async function fetchAndApplyManifestPatches(paths, errorReloadPath, manifest, routeModules, ssr, isSpaMode, basename, manifestPath, patchRoutes, signal) {
158 paths = getPathsWithAncestors(paths);
159 const searchParams = new URLSearchParams();
160 searchParams.set("paths", paths.sort().join(","));
161 searchParams.set("version", manifest.version);
162 let url = new URL(getManifestPath(manifestPath, basename), window.location.origin);
163 url.search = searchParams.toString();
164 if (url.toString().length > 7680) {
165 nextPaths.clear();
166 return;
167 }
168 let serverPatches;
169 try {
170 let res = await fetch(url, { signal });
171 if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
172 if (await handleClientVersionMismatch(res.status === 204 && res.headers.has("X-Remix-Reload-Document"), manifest.version, errorReloadPath)) return;
173 serverPatches = await res.json();
174 } catch (e) {
175 if (signal?.aborted) return;
176 throw e;
177 }
178 if (signal?.aborted) return;
179 let knownRoutes = new Set(Object.keys(manifest.routes));
180 let patches = Object.values(serverPatches).reduce((acc, route) => {
181 if (route && !knownRoutes.has(route.id)) acc[route.id] = route;
182 return acc;
183 }, {});
184 Object.assign(manifest.routes, patches);
185 paths.forEach((p) => addToFifoQueue(p, discoveredPaths));
186 let parentIds = /* @__PURE__ */ new Set();
187 Object.values(patches).forEach((patch) => {
188 if (patch && (!patch.parentId || !patches[patch.parentId])) parentIds.add(patch.parentId);
189 });
190 parentIds.forEach((parentId) => patchRoutes(parentId || null, createClientRoutes(patches, routeModules, null, ssr, isSpaMode, parentId)));
191}
192function addToFifoQueue(path, queue) {
193 if (queue.size >= discoveredPathsMaxSize) {
194 let first = queue.values().next().value;
195 if (first !== void 0) queue.delete(first);
196 }
197 queue.add(path);
198}
199function debounce(callback, wait) {
200 let timeoutId;
201 return (...args) => {
202 window.clearTimeout(timeoutId);
203 timeoutId = window.setTimeout(() => callback(...args), wait);
204 };
205}
206//#endregion
207export { URL_LIMIT, getManifestPath, getPartialManifest, getPatchRoutesOnNavigationFunction, getPathsWithAncestors, handleClientVersionMismatch, isFogOfWarEnabled, useFogOFWarDiscovery };