UNPKG

react-router

Version:
171 lines (170 loc) • 6.31 kB
/** * react-router v8.0.0 * * 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 { loadRouteModule } from "./routeModules.js"; //#region lib/dom/ssr/links.ts /** * Gets all the links for a set of matches. The modules are assumed to have been * loaded already. */ function getKeyedLinksForMatches(matches, routeModules, manifest) { return dedupeLinkDescriptors(matches.map((match) => { let module = routeModules[match.route.id]; let route = manifest.routes[match.route.id]; return [route && route.css ? route.css.map((href) => ({ rel: "stylesheet", href })) : [], module?.links?.() || []]; }).flat(2), getModuleLinkHrefs(matches, manifest)); } function getRouteCssDescriptors(route) { if (!route.css) return []; return route.css.map((href) => ({ rel: "stylesheet", href })); } async function prefetchRouteCss(route) { if (!route.css) return; let descriptors = getRouteCssDescriptors(route); await Promise.all(descriptors.map(prefetchStyleLink)); } async function prefetchStyleLinks(route, routeModule) { if (!route.css && !routeModule.links || !isPreloadSupported()) return; let descriptors = []; if (route.css) descriptors.push(...getRouteCssDescriptors(route)); if (routeModule.links) descriptors.push(...routeModule.links()); if (descriptors.length === 0) return; let styleLinks = []; for (let descriptor of descriptors) if (!isPageLinkDescriptor(descriptor) && descriptor.rel === "stylesheet") styleLinks.push({ ...descriptor, rel: "preload", as: "style" }); await Promise.all(styleLinks.map(prefetchStyleLink)); } async function prefetchStyleLink(descriptor) { return new Promise((resolve) => { if (descriptor.media && !window.matchMedia(descriptor.media).matches || document.querySelector(`link[rel="stylesheet"][href="${descriptor.href}"]`)) return resolve(); let link = document.createElement("link"); Object.assign(link, descriptor); function removeLink() { if (document.head.contains(link)) document.head.removeChild(link); } link.onload = () => { removeLink(); resolve(); }; link.onerror = () => { removeLink(); resolve(); }; document.head.appendChild(link); }); } function isPageLinkDescriptor(object) { return object != null && typeof object.page === "string"; } function isHtmlLinkDescriptor(object) { if (object == null) return false; if (object.href == null) return object.rel === "preload" && typeof object.imageSrcSet === "string" && typeof object.imageSizes === "string"; return typeof object.rel === "string" && typeof object.href === "string"; } async function getKeyedPrefetchLinks(matches, manifest, routeModules) { return dedupeLinkDescriptors((await Promise.all(matches.map(async (match) => { let route = manifest.routes[match.route.id]; if (route) { let mod = await loadRouteModule(route, routeModules); return mod.links ? mod.links() : []; } return []; }))).flat(1).filter(isHtmlLinkDescriptor).filter((link) => link.rel === "stylesheet" || link.rel === "preload").map((link) => link.rel === "stylesheet" ? { ...link, rel: "prefetch", as: "style" } : { ...link, rel: "prefetch" })); } function getNewMatchesForLinks(page, nextMatches, currentMatches, manifest, location, mode) { let isNew = (match, index) => { if (!currentMatches[index]) return true; return match.route.id !== currentMatches[index].route.id; }; let matchPathChanged = (match, index) => { return currentMatches[index].pathname !== match.pathname || currentMatches[index].route.path?.endsWith("*") && currentMatches[index].params["*"] !== match.params["*"]; }; if (mode === "assets") return nextMatches.filter((match, index) => isNew(match, index) || matchPathChanged(match, index)); if (mode === "data") return nextMatches.filter((match, index) => { let manifestRoute = manifest.routes[match.route.id]; if (!manifestRoute || !manifestRoute.hasLoader) return false; if (isNew(match, index) || matchPathChanged(match, index)) return true; if (match.route.shouldRevalidate) { let routeChoice = match.route.shouldRevalidate({ currentUrl: new URL(location.pathname + location.search + location.hash, window.origin), currentParams: currentMatches[0]?.params || {}, nextUrl: new URL(page, window.origin), nextParams: match.params, defaultShouldRevalidate: true }); if (typeof routeChoice === "boolean") return routeChoice; } return true; }); return []; } function getModuleLinkHrefs(matches, manifest, { includeHydrateFallback } = {}) { return dedupeHrefs(matches.map((match) => { let route = manifest.routes[match.route.id]; if (!route) return []; let hrefs = [route.module]; if (route.clientActionModule) hrefs = hrefs.concat(route.clientActionModule); if (route.clientLoaderModule) hrefs = hrefs.concat(route.clientLoaderModule); if (includeHydrateFallback && route.hydrateFallbackModule) hrefs = hrefs.concat(route.hydrateFallbackModule); if (route.imports) hrefs = hrefs.concat(route.imports); return hrefs; }).flat(1)); } function dedupeHrefs(hrefs) { return [...new Set(hrefs)]; } function sortKeys(obj) { let sorted = {}; let keys = Object.keys(obj).sort(); for (let key of keys) sorted[key] = obj[key]; return sorted; } function dedupeLinkDescriptors(descriptors, preloads) { let set = /* @__PURE__ */ new Set(); let preloadsSet = new Set(preloads); return descriptors.reduce((deduped, descriptor) => { if (preloads && !isPageLinkDescriptor(descriptor) && descriptor.as === "script" && descriptor.href && preloadsSet.has(descriptor.href)) return deduped; let key = JSON.stringify(sortKeys(descriptor)); if (!set.has(key)) { set.add(key); deduped.push({ key, link: descriptor }); } return deduped; }, []); } let _isPreloadSupported; function isPreloadSupported() { if (_isPreloadSupported !== void 0) return _isPreloadSupported; let el = document.createElement("link"); _isPreloadSupported = el.relList.supports("preload"); el = null; return _isPreloadSupported; } //#endregion export { getKeyedLinksForMatches, getKeyedPrefetchLinks, getModuleLinkHrefs, getNewMatchesForLinks, isPageLinkDescriptor, prefetchRouteCss, prefetchStyleLinks };