UNPKG

react-file-based-routes

Version:
144 lines (140 loc) 5.36 kB
import { jsx } from 'react/jsx-runtime'; import { Fragment, Suspense } from 'react'; import { Outlet } from 'react-router-dom'; const patterns = { route: [/\/src\/pages\/|\.tsx$/g, ''], splat: [/\[\.{3}\w+\]/g, '*'], param: [/\[([^\]]+)\]/g, ':$1'], slash: [/^index$|\./g, '/'], lazy: [/lazy_+/g, ''], }; const generatePreservedRoutes = (files) => { return Object.keys(files).reduce((routes, key) => { const path = key.replace(...patterns.route); return { ...routes, [path]: files[key] }; }, {}); }; const generateRouteObject = (module, key) => { const index = /index\.tsx$/.test(key) && !key.includes('pages/index') ? { index: true } : {}; const Element = module?.default || Fragment; const Page = () => module?.Pending ? (jsx(Suspense, { fallback: jsx(module.Pending, {}), children: jsx(Element, {}) })) : (jsx(Element, {})); return { ...index, id: key.replace(...patterns.route), element: jsx(Page, {}), errorElement: module?.Catch ? jsx(module.Catch, {}) : undefined, loader: module?.Loader, action: module?.Action, }; }; const generateLazyRouteObject = (module, key) => { const index = /index\.tsx$/.test(key) && !key.includes('pages/index') ? { index: true } : {}; return { ...index, id: key.replace(...patterns.route), lazy: async () => { const lazyModule = await module(); const Element = lazyModule?.default || Fragment; const Pending = lazyModule?.Pending; const ErrorElement = lazyModule?.Catch; const Page = () => Pending ? (jsx(Suspense, { fallback: jsx(Pending, {}), children: jsx(Element, {}) })) : (jsx(Element, {})); return { Component: Page, errorElement: ErrorElement ? jsx(ErrorElement, {}) : undefined, loader: lazyModule?.Loader, action: lazyModule?.Action, }; }, }; }; function generateFileBasedRoutes(files) { return Object.keys(files).reduce((routes, key) => { const isLazy = /lazy_.+\.tsx$/.test(key); const module = files[key]; if (isLazy) { const routePath = key.replace(...patterns.lazy); if (files[routePath]) { throw new Error(`001: The router with path=${routePath} conflict with the lazy router with path=${key}`); } } const route = isLazy ? generateLazyRouteObject(module, key) : generateRouteObject(module, key); const segments = key .replace(...patterns.route) .replace(...patterns.splat) .replace(...patterns.param) .split('/'); segments.reduce((parent, segment, index) => { const path = segment .replace(...patterns.lazy) .replace(...patterns.slash); const isRoot = index === 0; const isFile = index === segments.length - 1 && segments.length > 1; const isFolder = !isRoot && !isFile; const insert = /^\w|\//.test(path) ? 'unshift' : 'push'; const layout = segment === 'layout'; if (isRoot) { const dynamic = path.startsWith(':') || path === '*'; if (dynamic) return parent; const file = segments.length === 1; if (file) { routes.push({ path, ...route }); return parent; } } if (isRoot || isFolder) { const routeObjects = isRoot ? routes : parent.children; const routeObject = routeObjects?.find((route) => route.path === path); if (routeObject) routeObject.children ??= []; else routeObjects?.[insert]({ path, children: [] }); return (routeObject || routeObjects?.[insert === 'unshift' ? 0 : routeObjects.length - 1]); } if (layout) { return Object.assign(parent, route); } else { parent?.children?.[insert]({ path: path === '/' ? '' : path, ...route, }); } return parent; }, {}); return routes; }, []); } const PRESERVED = import.meta.glob('/src/pages/(_app|_404).tsx', { eager: true, }); const ROUTES = import.meta.glob(['/src/pages/**/[a-z[]*.tsx', '!/**/(lazy_)[a-z[]*.tsx'], { eager: true, }); const LAZY_ROUTES = import.meta.glob('/src/pages/**/(lazy_)*'); const preservedRoutes = generatePreservedRoutes(PRESERVED); const routes = generateFileBasedRoutes({ ...ROUTES, ...LAZY_ROUTES }); const _app = preservedRoutes?.['_app']; const App = _app?.default || Outlet; const NotFound = preservedRoutes?.['_404']?.default || Fragment; const ErrorBoundary = _app?.Catch || NotFound; const app = { element: jsx(App, {}), errorElement: jsx(ErrorBoundary, {}), loader: _app?.Loader, }; const fallback = { path: '*', element: jsx(NotFound, {}) }; const fileBasedRoutes = [ { ...app, children: [...routes, fallback], }, ]; export { fileBasedRoutes as default };