UNPKG

@redwoodjs/sdk

Version:

Build fast, server-driven webapps on Cloudflare with SSR, RSC, and realtime

116 lines (115 loc) 4 kB
import { isValidElementType } from "react-is"; function matchPath(routePath, requestPath) { const pattern = routePath .replace(/:[a-zA-Z0-9]+/g, "([^/]+)") // Convert :param to capture group .replace(/\*/g, "(.*)"); // Convert * to wildcard capture group const regex = new RegExp(`^${pattern}$`); const matches = requestPath.match(regex); if (!matches) { return null; } // Extract named parameters and wildcards const params = {}; const paramNames = [...routePath.matchAll(/:[a-zA-Z0-9]+/g)].map((m) => m[0].slice(1)); const wildcardCount = (routePath.match(/\*/g) || []).length; // Add named parameters paramNames.forEach((name, i) => { params[name] = matches[i + 1]; }); // Add wildcard parameters with numeric indices for (let i = 0; i < wildcardCount; i++) { const wildcardIndex = paramNames.length + i + 1; params[`$${i}`] = matches[wildcardIndex]; } return params; } function flattenRoutes(routes) { return routes.reduce((acc, route) => { if (Array.isArray(route)) { return [...acc, ...flattenRoutes(route)]; } return [...acc, route]; }, []); } export function defineRoutes(routes) { const flattenedRoutes = flattenRoutes(routes); return { routes: flattenedRoutes, async handle({ request, renderPage, getRequestInfo, runWithRequestInfoOverrides, }) { const url = new URL(request.url); let path = url.pathname; // Must end with a trailing slash. if (path !== "/" && !path.endsWith("/")) { path = path + "/"; } // Find matching route let match = null; for (const route of flattenedRoutes) { if (typeof route === "function") { const r = await route(getRequestInfo()); if (r instanceof Response) { return r; } continue; } const params = matchPath(route.path, path); if (params) { match = { params, handler: route.handler }; break; } } if (!match) { // todo(peterp, 2025-01-28): Allow the user to define their own "not found" route. return new Response("Not Found", { status: 404 }); } let { params, handler } = match; return runWithRequestInfoOverrides({ params }, async () => { const handlers = Array.isArray(handler) ? handler : [handler]; for (const h of handlers) { if (isRouteComponent(h)) { return await renderPage(getRequestInfo(), h); } else { const r = await h(getRequestInfo()); if (r instanceof Response) { return r; } } } // Add fallback return return new Response("Response not returned from route handler", { status: 500, }); }); }, }; } export function route(path, handler) { if (!path.endsWith("/")) { path = path + "/"; } return { path, handler, }; } export function index(handler) { return route("/", handler); } export function prefix(prefix, routes) { return routes.map((r) => { return { path: prefix + r.path, handler: r.handler, }; }); } export function render(Document, routes) { const documentMiddleware = ({ rw }) => { rw.Document = Document; }; return [documentMiddleware, ...routes]; } function isRouteComponent(handler) { return isValidElementType(handler) && handler.toString().includes("jsx"); }