UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

189 lines (186 loc) 7.31 kB
import { LOADER_JS_POSTFIX_UNCACHED } from "./constants"; import { getPathFromLoaderPath } from "./utils/cleanUrl"; import { isResponse } from "./utils/isResponse"; import { getManifest } from "./vite/getManifest"; import { resolveAPIEndpoint, resolveResponse } from "./vite/resolveResponse"; const debugRouter = process.env.ONE_DEBUG_ROUTER; async function runMiddlewares(handlers, request, route, getResponse) { const middlewares = route.middlewares; if (!middlewares?.length) return await getResponse(); if (!handlers.loadMiddleware) throw new Error("No middleware handler configured"); debugRouter && console.info(`[one] \u{1F517} middleware chain (${middlewares.length}) for ${route.page}`); const context = {}; async function dispatch(index) { const middlewareModule = middlewares[index]; if (!middlewareModule) return debugRouter && console.info("[one] \u2713 middleware chain complete"), await getResponse(); debugRouter && console.info(`[one] \u2192 middleware[${index}]: ${middlewareModule.contextKey}`); const exported = (await handlers.loadMiddleware(middlewareModule))?.default; if (!exported) throw new Error(`No valid export found in middleware: ${middlewareModule.contextKey}`); const response = await exported({ request, next: async () => dispatch(index + 1), context }); return response ? (debugRouter && console.info(`[one] \u2190 middleware[${index}] returned early (status: ${response.status})`), response) : dispatch(index + 1); } return dispatch(0); } async function resolveAPIRoute(handlers, request, url, route) { const { pathname } = url, params = getRouteParams(pathname, route); return debugRouter && console.info(`[one] \u{1F4E1} API ${request.method} ${pathname} \u2192 ${route.file}`, params), await runMiddlewares(handlers, request, route, async () => { try { return resolveAPIEndpoint( () => handlers.handleAPI({ request, route, url, loaderProps: { path: pathname, search: url.search, params } }), request, params || {} ); } catch (err) { if (isResponse(err)) return err; throw process.env.NODE_ENV === "development" && console.error(` [one] Error importing API route at ${pathname}: ${err} If this is an import error, you can likely fix this by adding this dependency to the "optimizeDeps.include" array in your vite.config.ts. `), err; } }); } async function resolveLoaderRoute(handlers, request, url, route) { return debugRouter && console.info(`[one] \u{1F4E6} loader ${url.pathname} \u2192 ${route.file}`), await runMiddlewares(handlers, request, route, async () => await resolveResponse(async () => { const headers = new Headers(); headers.set("Content-Type", "text/javascript"); try { const loaderResponse = await handlers.handleLoader({ request, route, url, loaderProps: { path: url.pathname, search: url.search, request: route.type === "ssr" ? request : void 0, params: getLoaderParams(url, route) } }); return new Response(loaderResponse, { headers }); } catch (err) { if (isResponse(err)) return err; throw console.error(`Error running loader: ${err}`), err; } })); } async function resolvePageRoute(handlers, request, url, route) { const { pathname, search } = url; return debugRouter && console.info(`[one] \u{1F4C4} page ${pathname} \u2192 ${route.file} (${route.type})`), resolveResponse(async () => await runMiddlewares(handlers, request, route, async () => await handlers.handlePage({ request, route, url, loaderProps: { path: pathname, search, // Ensure SSR loaders receive the original request request: route.type === "ssr" ? request : void 0, params: getLoaderParams(url, route) } }))); } function getURLfromRequestURL(request) { const urlString = request.url || ""; return new URL( urlString || "", request.headers.get("host") ? `http://${request.headers.get("host")}` : "" ); } function compileRouteRegex(route) { return { ...route, compiledRegex: new RegExp(route.namedRegex) }; } function compileManifest(manifest) { return { pageRoutes: manifest.pageRoutes.map(compileRouteRegex), apiRoutes: manifest.apiRoutes.map(compileRouteRegex) }; } function createHandleRequest(handlers, { routerRoot }) { const manifest = getManifest({ routerRoot }); if (!manifest) throw new Error("No routes manifest"); const compiledManifest = compileManifest(manifest); return { manifest, handler: async function(request) { const url = getURLfromRequestURL(request), { pathname, search } = url; if (pathname === "/__vxrnhmr" || pathname.startsWith("/@")) return null; if (handlers.handleAPI) { const apiRoute = compiledManifest.apiRoutes.find((route) => route.compiledRegex.test(pathname)); if (apiRoute) return debugRouter && console.info(`[one] \u26A1 ${pathname} \u2192 matched API route: ${apiRoute.page}`), await resolveAPIRoute(handlers, request, url, apiRoute); } if (request.method !== "GET") return null; if (handlers.handleLoader && pathname.endsWith(LOADER_JS_POSTFIX_UNCACHED)) { const originalUrl = getPathFromLoaderPath(pathname); for (const route of compiledManifest.pageRoutes) { if (route.file === "") continue; const finalUrl = new URL(originalUrl, url.origin); if (finalUrl.search = url.search, !route.compiledRegex.test(finalUrl.pathname)) continue; const cleanedRequest = new Request(finalUrl, request); return resolveLoaderRoute(handlers, cleanedRequest, finalUrl, route); } return process.env.NODE_ENV === "development" && console.error("No matching route found for loader!", { originalUrl, pathname, routes: manifest.pageRoutes }), Response.error(); } if (handlers.handlePage) { for (const route of compiledManifest.pageRoutes) if (route.compiledRegex.test(pathname)) return debugRouter && console.info(`[one] \u26A1 ${pathname} \u2192 matched page route: ${route.page} (${route.type})`), resolvePageRoute(handlers, request, url, route); } return null; } }; } function getLoaderParams(url, config) { const params = {}, match = new RegExp(config.compiledRegex).exec(url.pathname); if (match?.groups) for (const [key, value] of Object.entries(match.groups)) { const namedKey = config.routeKeys[key]; params[namedKey] = value; } return params; } function getRouteParams(pathname, route) { const match = new RegExp(route.namedRegex).exec(pathname); return match ? Object.fromEntries( Object.entries(route.routeKeys).map(([key, value]) => [value, match.groups?.[key] || ""]) ) : {}; } export { compileManifest, createHandleRequest, getURLfromRequestURL, resolveAPIRoute, resolveLoaderRoute, resolvePageRoute, runMiddlewares }; //# sourceMappingURL=createHandleRequest.js.map