Skip to content

Instantly share code, notes, and snippets.

@djalmajr
Last active July 31, 2024 12:06
Show Gist options
  • Select an option

  • Save djalmajr/54f2b0a2ffa1d85232b9571f34ab35d2 to your computer and use it in GitHub Desktop.

Select an option

Save djalmajr/54f2b0a2ffa1d85232b9571f34ab35d2 to your computer and use it in GitHub Desktop.

Revisions

  1. djalmajr revised this gist Jul 31, 2024. 1 changed file with 58 additions and 57 deletions.
    115 changes: 58 additions & 57 deletions generate-routes.ts
    Original 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|\.tsx$/g, "")
    .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 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 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 rootKey = "/app/routes/_layout.tsx";
    const root = parseRoute(RESERVED[rootKey] as RO);
    const routes = [
    { ...root, path: "/", Component: root.Component || Outlet, children: [] }
    ] as 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(ROUTES_LAZY).includes(v as never) ||
    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).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)));

    return result;
    }, routes[0].children!);
    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;
    }
  2. djalmajr revised this gist Jul 26, 2024. 1 changed file with 50 additions and 34 deletions.
    84 changes: 50 additions & 34 deletions generate-routes.ts
    Original 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 find = (path: string, routes: RO[]) => {
    const findRoute = (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)/);
    };
    @@ -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({ Root }: { Root?: RO["Component"] } = {}) {
    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 ROUTES = { ...ROUTES_EAGER, ...ROUTES_LAZY } as Obj<RO>;
    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[];

    Root ||= getComponent(RESERVED["/app/routes/_layout.tsx"]) || Outlet;
    const routes = [{ path: "/", Component: Root, children: [] }] as RO[];
    const routeProps = ["action", "loader", "handle", "Component", "ErrorBoundary"];
    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 (!routeProps.some((attr) => attr in val)) return result;
    if (key === rootKey || !validateRoute(val)) return result;

    const Component = getComponent(val);
    const route = parseRoute(val);

    (function parse(children, [segment, ...segments], layout?: RO) {
    (function parse(children, [segment, ...segments], parent?: 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 isLayout = segments[0] === "_layout";

    if (!isGroup && !isLayout && !segments.length) {
    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;
    (/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 ((layout = find(segment, children))) {
    layout.children ||= [];
    if (isLayout) layout.Component = Component || Outlet;
    else parse(layout.children, segments);
    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({
    children: [],
    [isGroup ? "_path" : "path"]: segment,
    Component: (isLayout && Component) || Outlet
    Component: Outlet,
    children: []
    });
    parse(children.at(-1)!.children!, segments);

    if (segments.length) parse(children.at(-1)!.children!, segments);
    }
    })(result, getSegments(parsePath(key)));

  3. djalmajr revised this gist Jul 20, 2024. No changes.
  4. djalmajr revised this gist Jul 20, 2024. No changes.
  5. djalmajr created this gist Jul 20, 2024.
    85 changes: 85 additions & 0 deletions generate-routes.ts
    Original 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;
    }