UNPKG

@router-cli/react-router

Version:

File based routing cli for react-router-dom. Types and components. see @router-cli/react-router-dev for the cli and plugins.

114 lines (100 loc) 4.35 kB
import { Outlet, RouteObject, createBrowserRouter, createHashRouter } from "react-router-dom"; import React, { Fragment } from "react"; import "./utils/createLazyRoute"; import { createLazyRoute } from "./utils/createLazyRoute"; import { suspendedPromiseCache } from "./hooks/useSuspendedPromise"; import { AnyRouteComponent, AnyRouteImport, AnyRouteImports, AppRoutes } from "./types"; type RouteEntry = { fullPath: string; relativePath: string; id: string; index: boolean; route: AnyRouteImport; children: RouteEntry[]; } const createTree = (routes: { layouts: AnyRouteImports, pages: AnyRouteImports }) => { // Sort layouts by the number of segments, then alphabetically, helps ensure the output matches so we don't trigger additional writes if watching. const pageKeys = Object.keys(routes.pages).sort((a, b) => b.split("/").length - a.split("/").length || a.localeCompare(b)); // Sort layouts by the number of segments. const layoutKeys = Object.keys(routes.layouts).sort((a, b) => b.split("/").length - a.split("/").length); const result = new Map<string, RouteEntry>(pageKeys.map((key) => { const path = key.replaceAll("/$", "/:"); return [key, { children: [], fullPath: path, relativePath: path, index: false, id: key, route: routes.pages[key], }]; })); const layoutSuffixLength = "/layout".length; const layouts = new Map<string, RouteEntry>(layoutKeys.map((key) => { const path = key.replaceAll("/$", "/:").slice(0, -1 * layoutSuffixLength); return [key, { children: [], fullPath: path, relativePath: path, index: false, id: key, route: routes.layouts[key], }]; })); for (const layoutRoute of layouts.values()) { if (!layoutRoute) continue; for (const route of result.values()) { const layoutPathPrefix = layoutRoute.fullPath + "/"; const isChild = route.fullPath.startsWith(layoutPathPrefix); const isIndex = route.fullPath === layoutRoute.fullPath if (isChild || isIndex) { // Remove route from map, we are going to move it to a child of this layout. result.delete(route.id); // alter the path to be relative to the layout and remove any empty segments if the layout is also a endpoint. route.relativePath = (route.relativePath .substring(layoutPathPrefix.length, route.relativePath.length) .split("/") .filter(Boolean) .join("/") ) || "/"; route.index = isIndex; layoutRoute.children.push(route); } } result.set(layoutRoute.id, layoutRoute); } return Array.from(result.values()); } const createRoutes = ({ app, ...otherRoutes }: { layouts: AnyRouteImports, pages: AnyRouteImports, app: AppRoutes }): RouteObject[] => { const tree = createTree(otherRoutes); const mapRoute = (node: RouteEntry): RouteObject => ({ id: node.id, lazy: () => node.route().then(x => { // load the route into the module cache to be used in hooks. suspendedPromiseCache.preloadValue(x, node.id); const lazyData = createLazyRoute(x, { defaultErrorComponent: app.error }); return lazyData; }), ...(node.relativePath == "/" ? { index: true, } : { path: node.relativePath, children: node.children?.map(mapRoute) || undefined, }) }); return [ { element: React.createElement(app.app || Outlet), children: [ ...tree.map(mapRoute), { path: '*', element: React.createElement(app.notFound || Fragment), } ] } ] } export function createTypedRouter(params: { layouts: AnyRouteImports, pages: AnyRouteImports, app: AppRoutes }) { const routes = createRoutes(params); return createBrowserRouter(routes); }