Last active
July 31, 2024 12:06
-
-
Save djalmajr/54f2b0a2ffa1d85232b9571f34ab35d2 to your computer and use it in GitHub Desktop.
Revisions
-
djalmajr revised this gist
Jul 31, 2024 . 1 changed file with 58 additions and 57 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,4 @@ import type { Obj } from "help-es"; import { createElement, isValidElement } from "react"; import { Outlet, type RouteObject } from "react-router-dom"; @@ -13,7 +14,7 @@ const getSegments = (path: string) => { const parsePath = (path: string) => { return path .replace(/\/app\/routes|index|_index|route|\.tsx?$/g, "") .replace(/\[\.{3}.+\]|\$(?=[\/.]|$)/g, "*") .replace(/\(\$([^)]+)\)/g, ":$1?") .replace(/\[(.+)\]/g, ":$1") @@ -34,68 +35,68 @@ const parseRoute = (route: Obj): RO => { // https://omarelhawary.me/blog/file-based-routing-with-react-router/ export function generateRoutes() { const EAGER = import.meta.glob("/app/routes/**/[_$a-z()[]!(*.lazy).(ts|tsx)", { eager: true }); const LAZY = import.meta.glob("/app/routes/**/[_$a-z()[]*.lazy.(ts|tsx)"); const ROOT = import.meta.glob("/app/root.tsx", { eager: true }); const ROUTES = { ...ROOT, ...EAGER, ...LAZY } as Obj<RO>; const rootKeys = ["/app/root.tsx", "/app/routes/_layout.tsx", "/app/routes/__root.tsx"]; const { Component = Outlet, ...root } = parseRoute(ROUTES[rootKeys.find((k) => ROUTES[k])!]); const routes = [{ ...root, path: "/", Component, children: [] }] as RO[]; const validateRoute = (v: Obj) => Object.values(LAZY).includes(v as never) || ["action", "loader", "handle", "Component", "ErrorBoundary"].some((k) => k in v) || (v.default && isValidElement(createElement(v.default))); Object.entries(ROUTES) .filter(([key, val]) => !rootKeys.includes(key) && validateRoute(val)) .reduce((result: RO[], [key, val]) => { const route = parseRoute(val); (function parse(children, [segment, ...segments], parent?: RO) { const isGroup = /^[_(]|_$/.test(segment); const isLayout = segments[0] === "_layout"; if (!isGroup && !isLayout && !segments.length) { const [slug = ""] = segment.split(/\./) || []; if (/lazy/.test(segment)) route.lazy = ROUTES[key] as never; if ((parent = findRoute(segment, children))) Object.assign(parent, route); else children.push((slug ? (route.path = slug) : (route.index = true), route)); return; } if (/_$/.test(segment)) { segments = [`${segment.replace(/_$/g, "")}/${segments[0]}`].concat(segments.slice(1)); } if ((parent = findRoute(segment, children))) { parent.children ||= []; if (isLayout) parent.Component = route.Component || Outlet; else if (segments.length) parse(parent.children, segments); return; } if (isLayout) { children.push({ ...(route as object), path: segment, Component: route.Component || Outlet, children: [] }); } else { children.push({ [isGroup ? "_path" : "path"]: segment, Component: Outlet, children: [] }); if (segments.length) parse(children.at(-1)!.children!, segments); } })(result, getSegments(parsePath(key))); return result; }, routes[0].children!); return routes; } -
djalmajr revised this gist
Jul 26, 2024 . 1 changed file with 50 additions and 34 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,17 +1,12 @@ import { createElement, isValidElement } from "react"; import { Outlet, type RouteObject } from "react-router-dom"; type RO = RouteObject & { _path?: string }; const findRoute = (path: string, routes: RO[]) => { return routes.find((r) => r.path === path || r._path === path); }; const getSegments = (path: string) => { return path.replace(/^\//, "").split(/\/|\.(?!lazy)/); }; @@ -26,55 +21,76 @@ const parsePath = (path: string) => { .replace(/\$/g, ":"); }; const parseRoute = (route: Obj): RO => { const data = { ...route }; if (data?.default) { data.Component = data.default; Reflect.deleteProperty(data, "default"); } return data; }; // https://omarelhawary.me/blog/file-based-routing-with-react-router/ export function generateRoutes() { const ROUTES_EAGER = import.meta.glob("@/routes/**/[_$a-z()[]!(*.lazy).tsx", { eager: true }); const ROUTES_LAZY = import.meta.glob("@/routes/**/[_$a-z()[]*.lazy.tsx"); const RESERVED = import.meta.glob("@/routes/(_layout|404).tsx", { eager: true }); const ROUTES = { ...ROUTES_EAGER, ...ROUTES_LAZY } as Obj<RO>; const rootKey = "/app/routes/_layout.tsx"; const root = parseRoute(RESERVED[rootKey] as RO); const routes = [ { ...root, path: "/", Component: root.Component || Outlet, children: [] } ] as RO[]; const validateRoute = (v: Obj) => Object.values(ROUTES_LAZY).includes(v as never) || ["action", "loader", "handle", "Component", "ErrorBoundary"].some((k) => k in v) || (v.default && isValidElement(createElement(v.default))); Object.entries(ROUTES).reduce((result: RO[], [key, val]) => { if (key === rootKey || !validateRoute(val)) return result; const route = parseRoute(val); (function parse(children, [segment, ...segments], parent?: RO) { const isGroup = /^[_(]|_$/.test(segment); const isLayout = segments[0] === "_layout"; if (!isGroup && !isLayout && !segments.length) { const [slug = ""] = segment.split(/\./) || []; (/lazy/.test(segment)) && (route.lazy = ROUTES[key] as never); return children.push((slug ? (route.path = slug) : (route.index = true), route)); } if (/_$/.test(segment)) { segments = [`${segment.replace(/_$/g, "")}/${segments[0]}`].concat(segments.slice(1)); } if ((parent = findRoute(segment, children))) { parent.children ||= []; if (isLayout) parent.Component = route.Component || Outlet; else if (segments.length) parse(parent.children, segments); return; } if (isLayout) { children.push({ ...(route as object), path: segment, Component: route.Component || Outlet, children: [] }); } else { children.push({ [isGroup ? "_path" : "path"]: segment, Component: Outlet, children: [] }); if (segments.length) parse(children.at(-1)!.children!, segments); } })(result, getSegments(parsePath(key))); -
djalmajr revised this gist
Jul 20, 2024 . No changes.There are no files selected for viewing
-
djalmajr revised this gist
Jul 20, 2024 . No changes.There are no files selected for viewing
-
djalmajr created this gist
Jul 20, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,85 @@ import { Outlet, type RouteObject } from "react-router-dom"; type RO = RouteObject & { _path?: string }; const find = (path: string, routes: RO[]) => { return routes.find((r) => r.path === path || r._path === path); }; const getComponent = (data: unknown): RO["Component"] => { const { Component, default: defaults } = <Obj>data || {}; return Component || defaults; }; const getSegments = (path: string) => { return path.replace(/^\//, "").split(/\/|\.(?!lazy)/); }; const parsePath = (path: string) => { return path .replace(/\/app\/routes|index|_index|\.tsx$/g, "") .replace(/\[\.{3}.+\]|\$(?=[\/.]|$)/g, "*") .replace(/\(\$([^)]+)\)/g, ":$1?") .replace(/\[(.+)\]/g, ":$1") .replace(/\.(?!lazy)/g, "/") .replace(/\$/g, ":"); }; // https://omarelhawary.me/blog/file-based-routing-with-react-router/ export function generateRoutes({ Root }: { Root?: RO["Component"] } = {}) { const ROUTES_EAGER = import.meta.glob("@/routes/**/[_$a-z()[]!(*.lazy).tsx", { eager: true }); const ROUTES_LAZY = import.meta.glob("@/routes/**/[_$a-z()[]*.lazy.tsx"); const ROUTES = { ...ROUTES_EAGER, ...ROUTES_LAZY } as Obj<RO>; const RESERVED = import.meta.glob("@/routes/(_layout|404).tsx", { eager: true }); Root ||= getComponent(RESERVED["/app/routes/_layout.tsx"]) || Outlet; const routes = [{ path: "/", Component: Root, children: [] }] as RO[]; const routeProps = ["action", "loader", "handle", "Component", "ErrorBoundary"]; Object.entries(ROUTES).reduce((result: RO[], [key, val]) => { if (!routeProps.some((attr) => attr in val)) return result; const Component = getComponent(val); (function parse(children, [segment, ...segments], layout?: RO) { const isGroup = /^[_(]|_$/.test(segment); const isLayout = (isGroup && !segments.length) || segments[0] === "_layout"; if (!isGroup && !segments.length) { // Returns if path is _pattern.tsx or (pattern).tsx if (segment === undefined) return; const [slug = ""] = segment.split(/\./) || []; const route = find(slug, children) || { ...val, Component }; if (/lazy/.test(segment)) route.lazy = ROUTES[key] as never; if (route._path || route.path) route.Component = Component; else children.push((slug ? (route.path = slug) : (route.index = true), route)); return; } if (/_$/.test(segment)) { segments = [`${segment.replace(/_$/g, "")}/${segments[0]}`].concat(segments.slice(1)); } if ((layout = find(segment, children))) { layout.children ||= []; if (isLayout) layout.Component = Component || Outlet; else parse(layout.children, segments); } else { children.push({ children: [], [isGroup ? "_path" : "path"]: segment, Component: (isLayout && Component) || Outlet }); parse(children.at(-1)!.children!, segments); } })(result, getSegments(parsePath(key))); return result; }, routes[0].children!); return routes; }