react-router
Version:
Declarative routing for React
171 lines (170 loc) • 6.31 kB
JavaScript
/**
* 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 };