react-file-based-routes
Version:
React Generate Router
144 lines (140 loc) • 5.36 kB
JavaScript
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 };