one
Version:
One is a new React Framework that makes Vite serve both native and web.
385 lines (382 loc) • 18.3 kB
JavaScript
import { readFile } from "node:fs/promises";
import { extname, join, resolve } from "node:path";
import {
CSS_PRELOAD_JS_POSTFIX,
LOADER_JS_POSTFIX_UNCACHED,
PRELOAD_JS_POSTFIX
} from "../constants";
import {
compileManifest,
getURLfromRequestURL,
runMiddlewares
} from "../createHandleRequest";
import { getPathFromLoaderPath } from "../utils/cleanUrl";
import { toAbsolute } from "../utils/toAbsolute";
import { getFetchStaticHtml } from "./staticHtmlFetcher";
const debugRouter = process.env.ONE_DEBUG_ROUTER;
async function oneServe(oneOptions, buildInfo, app, options) {
const { resolveAPIRoute, resolveLoaderRoute, resolvePageRoute } = await import("../createHandleRequest"), { isResponse } = await import("../utils/isResponse"), { isStatusRedirect } = await import("../utils/isStatus"), isAPIRequest = /* @__PURE__ */ new WeakMap(), redirects = oneOptions.web?.redirects;
if (redirects)
for (const redirect of redirects)
app.get(redirect.source, (context) => {
const destinationUrl = redirect.destination.replace(/:\w+/g, (param) => {
const paramName = param.substring(1);
return context.req.param(paramName) || "";
});
return debugRouter && console.info(`[one] \u21AA redirect ${context.req.path} \u2192 ${destinationUrl}`), context.redirect(destinationUrl, redirect.permanent ? 301 : 302);
});
if (!buildInfo)
throw new Error("No build info found, have you run build?");
const { routeToBuildInfo, routeMap } = buildInfo;
function findNearestNotFoundPath(urlPath) {
let cur = urlPath;
for (; cur; ) {
const parent = cur.lastIndexOf("/") > 0 ? cur.slice(0, cur.lastIndexOf("/")) : "";
if (routeMap[`${parent}/+not-found`])
return `${parent}/+not-found`;
if (!parent) break;
cur = parent;
}
return "/+not-found";
}
const serverOptions = {
...oneOptions,
root: "."
}, apiCJS = oneOptions.build?.api?.outputFormat === "cjs";
let render = null;
async function getRender() {
return render || (render = (options?.lazyRoutes?.serverEntry ? await options.lazyRoutes.serverEntry() : await import(resolve(
process.cwd(),
`${serverOptions.root}/dist/server/_virtual_one-entry.${typeof oneOptions.build?.server == "object" && oneOptions.build.server.outputFormat === "cjs" ? "c" : ""}js`
))).default.render), render;
}
const requestHandlers = {
async handleAPI({ route }) {
if (options?.lazyRoutes?.api?.[route.page])
return await options.lazyRoutes.api[route.page]();
const fileName = route.page.slice(1).replace(/\[/g, "_").replace(/\]/g, "_");
return await import(join(
process.cwd(),
"dist",
"api",
fileName + (apiCJS ? ".cjs" : ".js")
));
},
async loadMiddleware(route) {
return options?.lazyRoutes?.middlewares?.[route.contextKey] ? await options.lazyRoutes.middlewares[route.contextKey]() : await import(toAbsolute(route.contextKey));
},
async handleLoader({ route, loaderProps }) {
const routeFile = route.routeFile || route.file, serverPath = route.file.includes("dist/server") ? route.file : join("./", "dist/server", route.file);
let exports;
try {
exports = options?.lazyRoutes?.pages?.[routeFile] ? await options.lazyRoutes.pages[routeFile]() : await import(toAbsolute(serverPath));
} catch (err) {
if (err?.code === "ERR_MODULE_NOT_FOUND")
return null;
throw err;
}
const { loader } = exports;
if (!loader)
return null;
const json = await loader(loaderProps);
if (isResponse(json))
throw json;
return `export function loader() { return ${JSON.stringify(json)} }`;
},
async handlePage({ route, url, loaderProps }) {
const buildInfo2 = routeToBuildInfo[route.file];
if (route.type === "ssr") {
if (!buildInfo2)
throw console.error("Error in route", route), new Error(
`No buildinfo found for ${url}, route: ${route.file}, in keys:
${Object.keys(routeToBuildInfo).join(`
`)}`
);
try {
const exported = options?.lazyRoutes?.pages?.[route.file] ? await options.lazyRoutes.pages[route.file]() : await import(toAbsolute(buildInfo2.serverJsPath));
async function runLoader(routeId, serverPath, lazyKey) {
if (!serverPath && !lazyKey)
return { loaderData: void 0, routeId };
try {
const pathToResolve = serverPath || lazyKey || "", resolvedPath = pathToResolve.includes("dist/server") ? pathToResolve : join("./", "dist/server", pathToResolve);
return { loaderData: await (lazyKey ? options?.lazyRoutes?.pages?.[lazyKey] ? await options.lazyRoutes.pages[lazyKey]() : await import(toAbsolute(resolvedPath)) : await import(toAbsolute(serverPath)))?.loader?.(loaderProps), routeId };
} catch (err) {
if (isResponse(err))
throw err;
return console.error(`[one] Error running loader for ${routeId}:`, err), { loaderData: void 0, routeId };
}
}
const layoutLoaderPromises = (route.layouts || []).map((layout) => {
const serverPath = layout.loaderServerPath || layout.contextKey;
return runLoader(layout.contextKey, serverPath, layout.contextKey);
}), pageLoaderPromise = runLoader(
route.file,
buildInfo2.serverJsPath,
route.file
);
let layoutResults, pageResult;
try {
[layoutResults, pageResult] = await Promise.all([
Promise.all(layoutLoaderPromises),
pageLoaderPromise
]);
} catch (err) {
if (isResponse(err))
return err;
throw err;
}
const matches = [
...layoutResults.map((result) => ({
routeId: result.routeId,
pathname: loaderProps?.path || "/",
params: loaderProps?.params || {},
loaderData: result.loaderData
})),
{
routeId: pageResult.routeId,
pathname: loaderProps?.path || "/",
params: loaderProps?.params || {},
loaderData: pageResult.loaderData
}
], loaderData = pageResult.loaderData, headers = new Headers();
headers.set("content-type", "text/html"), globalThis.__vxrnresetState?.();
const rendered = await (await getRender())({
mode: route.type,
loaderData,
loaderProps,
path: loaderProps?.path || "/",
// Use separated preloads for optimal loading
preloads: buildInfo2.criticalPreloads || buildInfo2.preloads,
deferredPreloads: buildInfo2.deferredPreloads,
css: buildInfo2.css,
cssContents: buildInfo2.cssContents,
matches
});
return new Response(rendered, {
headers,
status: route.isNotFound ? 404 : 200
});
} catch (err) {
if (isResponse(err))
return err;
console.error(`[one] Error rendering SSR route ${route.file}
${err?.stack ?? err}
url: ${url}`);
}
} else {
const layoutRoutes = route.layouts || [];
if (route.type === "spa" && layoutRoutes.some(
(layout) => layout.layoutRenderMode === "ssg" || layout.layoutRenderMode === "ssr"
))
try {
async function runShellLoader(routeId, serverPath, lazyKey) {
if (!serverPath && !lazyKey)
return { loaderData: void 0, routeId };
try {
const pathToResolve = serverPath || lazyKey || "", resolvedPath = pathToResolve.includes("dist/server") ? pathToResolve : join("./", "dist/server", pathToResolve);
return { loaderData: await (lazyKey ? options?.lazyRoutes?.pages?.[lazyKey] ? await options.lazyRoutes.pages[lazyKey]() : await import(toAbsolute(resolvedPath)) : await import(toAbsolute(serverPath)))?.loader?.(loaderProps), routeId };
} catch (err) {
if (isResponse(err))
throw err;
return console.error(`[one] Error running shell loader for ${routeId}:`, err), { loaderData: void 0, routeId };
}
}
const matches = (await Promise.all(
layoutRoutes.map((layout) => {
const serverPath = layout.loaderServerPath || layout.contextKey;
return runShellLoader(layout.contextKey, serverPath, layout.contextKey);
})
)).map((result) => ({
routeId: result.routeId,
pathname: loaderProps?.path || "/",
params: loaderProps?.params || {},
loaderData: result.loaderData
})), headers = new Headers();
headers.set("content-type", "text/html"), globalThis.__vxrnresetState?.();
const rendered = await (await getRender())({
mode: "spa-shell",
// don't pass loaderData for spa-shell - the page loader runs on client
// passing {} here would make useLoaderState think data is preloaded
loaderData: void 0,
loaderProps,
path: loaderProps?.path || "/",
preloads: buildInfo2?.criticalPreloads || buildInfo2?.preloads,
deferredPreloads: buildInfo2?.deferredPreloads,
css: buildInfo2?.css,
cssContents: buildInfo2?.cssContents,
matches
});
return new Response(rendered, {
headers,
status: route.isNotFound ? 404 : 200
});
} catch (err) {
if (isResponse(err))
return err;
console.error(
`[one] Error rendering spa-shell for ${route.file}
${err?.stack ?? err}
url: ${url}`
);
}
const isDynamicRoute = Object.keys(route.routeKeys).length > 0, routeCleanPath = route.urlCleanPath.replace(/\?/g, ""), notFoundKey = route.isNotFound ? route.page.replace(/\[([^\]]+)\]/g, ":$1") : null, htmlPath = notFoundKey ? routeMap[notFoundKey] : isDynamicRoute ? routeMap[routeCleanPath] || routeMap[url.pathname] : routeMap[url.pathname] || routeMap[buildInfo2?.cleanPath];
if (htmlPath) {
const fetchStaticHtml = getFetchStaticHtml();
let html = null;
if (fetchStaticHtml && (html = await fetchStaticHtml(htmlPath)), !html)
try {
html = await readFile(join("dist/client", htmlPath), "utf-8");
} catch {
}
if (html) {
const headers = new Headers();
return headers.set("content-type", "text/html"), new Response(html, {
headers,
status: route.isNotFound ? 404 : 200
});
}
}
if (isDynamicRoute) {
const notFoundRoute = findNearestNotFoundPath(url.pathname), notFoundHtmlPath = routeMap[notFoundRoute];
if (notFoundHtmlPath) {
const fetchStaticHtml = getFetchStaticHtml();
let notFoundHtml = null;
if (fetchStaticHtml && (notFoundHtml = await fetchStaticHtml(notFoundHtmlPath)), !notFoundHtml)
try {
notFoundHtml = await readFile(
join("dist/client", notFoundHtmlPath),
"utf-8"
);
} catch {
}
if (notFoundHtml) {
const headers = new Headers();
return headers.set("content-type", "text/html"), new Response(notFoundHtml, {
headers,
status: 404
});
}
}
return new Response("404 Not Found", { status: 404 });
}
}
}
};
function createHonoHandler(route) {
return async (context, next) => {
try {
const request = context.req.raw;
if ((route.page.endsWith("/+not-found") || Reflect.ownKeys(route.routeKeys).length > 0) && options?.serveStaticAssets) {
const staticAssetResponse = await options.serveStaticAssets({
context
});
if (staticAssetResponse)
return await runMiddlewares(
requestHandlers,
request,
route,
async () => staticAssetResponse
);
}
if (extname(request.url) === ".js" || extname(request.url) === ".css")
return next();
const url = getURLfromRequestURL(request), response = await (() => {
if (url.pathname.endsWith(LOADER_JS_POSTFIX_UNCACHED)) {
const originalUrl = getPathFromLoaderPath(url.pathname);
if (route.type === "ssg" && Object.keys(route.routeKeys).length > 0 && !routeMap[originalUrl]) {
const nfPath = findNearestNotFoundPath(originalUrl);
return new Response(
`export function loader(){return{__oneError:404,__oneErrorMessage:'Not Found',__oneNotFoundPath:${JSON.stringify(nfPath)}}}`,
{ headers: { "Content-Type": "text/javascript" } }
);
}
const finalUrl = new URL(originalUrl, url.origin), cleanedRequest = new Request(finalUrl, request);
return resolveLoaderRoute(requestHandlers, cleanedRequest, finalUrl, route);
}
switch (route.type) {
case "api":
return debugRouter && console.info(
`[one] \u26A1 ${url.pathname} \u2192 matched API route: ${route.page}`
), resolveAPIRoute(requestHandlers, request, url, route);
case "ssg":
case "spa":
case "ssr":
return debugRouter && console.info(
`[one] \u26A1 ${url.pathname} \u2192 matched page route: ${route.page} (${route.type})`
), resolvePageRoute(requestHandlers, request, url, route);
}
})();
if (response) {
if (isResponse(response)) {
if (isStatusRedirect(response.status)) {
const location = `${response.headers.get("location") || ""}`;
return response.headers.forEach((value, key) => {
context.header(key, value);
}), context.redirect(location, response.status);
}
if (isAPIRequest.get(request))
try {
return !response.headers.has("cache-control") && !response.headers.has("Cache-Control") && response.headers.set("cache-control", "no-store"), response;
} catch (err) {
console.info(
`Error updating cache header on api route "${context.req.path}" to no-store, it is ${response.headers.get("cache-control")}, continue`,
err
);
}
return !response.headers.has("cache-control") && !response.headers.has("Cache-Control") && response.headers.set("cache-control", "no-cache"), response;
}
return next();
}
} catch (err) {
console.error(` [one] Error handling request: ${err.stack}`);
}
return next();
};
}
const compiledManifest = compileManifest(buildInfo.manifest);
for (const route of compiledManifest.apiRoutes)
app.get(route.urlPath, createHonoHandler(route)), app.put(route.urlPath, createHonoHandler(route)), app.post(route.urlPath, createHonoHandler(route)), app.delete(route.urlPath, createHonoHandler(route)), app.patch(route.urlPath, createHonoHandler(route)), route.urlPath !== route.urlCleanPath && (app.get(route.urlCleanPath, createHonoHandler(route)), app.put(route.urlCleanPath, createHonoHandler(route)), app.post(route.urlCleanPath, createHonoHandler(route)), app.delete(route.urlCleanPath, createHonoHandler(route)), app.patch(route.urlCleanPath, createHonoHandler(route)));
for (const route of compiledManifest.pageRoutes)
app.get(route.urlPath, createHonoHandler(route)), route.urlPath !== route.urlCleanPath && app.get(route.urlCleanPath, createHonoHandler(route));
const { preloads, cssPreloads } = buildInfo;
app.get("*", async (c, next) => {
if (c.req.path.endsWith(PRELOAD_JS_POSTFIX) && !preloads[c.req.path])
return c.header("Content-Type", "text/javascript"), c.status(200), c.body("");
if (c.req.path.endsWith(CSS_PRELOAD_JS_POSTFIX) && !cssPreloads?.[c.req.path])
return c.header("Content-Type", "text/javascript"), c.status(200), c.body("export default Promise.resolve()");
if (c.req.path.endsWith(LOADER_JS_POSTFIX_UNCACHED)) {
const request = c.req.raw, url = getURLfromRequestURL(request), originalUrl = getPathFromLoaderPath(c.req.path);
for (const route of compiledManifest.pageRoutes) {
if (route.file === "" || !route.compiledRegex.test(originalUrl))
continue;
if (route.type === "ssg" && Object.keys(route.routeKeys).length > 0 && !routeMap[originalUrl]) {
const nfPath = findNearestNotFoundPath(originalUrl);
return c.header("Content-Type", "text/javascript"), c.status(200), c.body(
`export function loader(){return{__oneError:404,__oneErrorMessage:'Not Found',__oneNotFoundPath:${JSON.stringify(nfPath)}}}`
);
}
const loaderRoute = {
...route,
routeFile: route.file,
// preserve original for lazy route lookup
file: route.loaderServerPath || c.req.path
}, finalUrl = new URL(originalUrl, url.origin), cleanedRequest = new Request(finalUrl, request);
try {
return await resolveLoaderRoute(
requestHandlers,
cleanedRequest,
finalUrl,
loaderRoute
);
} catch (err) {
return err?.code === "ERR_MODULE_NOT_FOUND" ? (c.header("Content-Type", "text/javascript"), c.status(200), c.body("export function loader() { return undefined }")) : (console.error(`Error running loader: ${err}`), next());
}
}
}
return next();
});
}
export {
oneServe
};
//# sourceMappingURL=oneServe.js.map