one
Version:
One is a new React Framework that makes Vite serve both native and web.
189 lines (186 loc) • 7.31 kB
JavaScript
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