UNPKG

one

Version:

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

771 lines (769 loc) 29.8 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var oneServe_exports = {}; __export(oneServe_exports, { oneServe: () => oneServe }); module.exports = __toCommonJS(oneServe_exports); var import_promises = require("node:fs/promises"); var import_node_path = require("node:path"); var import_constants = require("../constants.cjs"); var import_createHandleRequest = require("../createHandleRequest.cjs"); var import_cleanUrl = require("../utils/cleanUrl.cjs"); var import_toAbsolute = require("../utils/toAbsolute.cjs"); var import_ssrLoaderData = require("./ssrLoaderData.cjs"); var import_staticHtmlFetcher = require("./staticHtmlFetcher.cjs"); const debugRouter = process.env.ONE_DEBUG_ROUTER; function forwardHeaders(response, context) { const setCookies = response.headers.getSetCookie?.(); if (setCookies?.length) { for (const cookie of setCookies) { context.header("set-cookie", cookie, { append: true }); } } response.headers.forEach((value, key) => { if (key === "set-cookie") return; context.header(key, value); }); } async function readStaticHtml(htmlPath, outDir = "dist") { const fetchStaticHtml = (0, import_staticHtmlFetcher.getFetchStaticHtml)(); if (fetchStaticHtml) { const html = await fetchStaticHtml(htmlPath); if (html) return html; } try { return await (0, import_promises.readFile)((0, import_node_path.join)(`${outDir}/client`, htmlPath), "utf-8"); } catch { return null; } } async function oneServe(oneOptions, buildInfo, app, options) { const outDir = buildInfo.outDir || "dist"; const { resolveAPIRoute, resolveLoaderRoute, resolvePageRoute } = await import("../createHandleRequest"); const { isResponse } = await import("../utils/isResponse"); const { isStatusRedirect } = await import("../utils/isStatus"); const { resolveResponse } = await import("../vite/resolveResponse"); const isAPIRequest = /* @__PURE__ */new WeakMap(); const 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) || ""; }); if (debugRouter) { console.info(`[one] \u21AA redirect ${context.req.path} \u2192 ${destinationUrl}`); } return 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; while (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"; } function make404LoaderJs(path, logReason) { const nfPath = findNearestNotFoundPath(path); if (logReason) { console.error(`[one] 404 loader for ${path}: ${logReason}`); } return `export function loader(){return{__oneError:404,__oneErrorMessage:'Not Found',__oneNotFoundPath:${JSON.stringify(nfPath)}}}`; } const serverOptions = { ...oneOptions, root: "." }; const apiCJS = oneOptions.build?.api?.outputFormat === "cjs"; const useStreaming = !process.env.ONE_BUFFERED_SSR; const htmlHeaders = { "content-type": "text/html" }; const ssrHtmlHeaders = { "content-type": "text/html", "cache-control": "no-cache" }; const loaderCache = /* @__PURE__ */new Map(); const moduleImportCache = /* @__PURE__ */new Map(); const loaderCacheFnMap = /* @__PURE__ */new Map(); const pendingLoaderResults = /* @__PURE__ */new Map(); function resolveLoaderSync(serverPath, lazyKey) { const cacheKey = lazyKey || serverPath || ""; const cached = loaderCache.get(cacheKey); if (cached !== void 0) return cached; return (async () => { const pathToResolve = serverPath || lazyKey || ""; const resolvedPath = pathToResolve.includes(`${outDir}/server`) ? pathToResolve : (0, import_node_path.join)("./", `${outDir}/server`, pathToResolve); let routeExported; if (moduleImportCache.has(cacheKey)) { routeExported = moduleImportCache.get(cacheKey); } else { routeExported = lazyKey ? options?.lazyRoutes?.pages?.[lazyKey] ? await options.lazyRoutes.pages[lazyKey]() : await import((0, import_toAbsolute.toAbsolute)(resolvedPath)) : await import((0, import_toAbsolute.toAbsolute)(serverPath)); moduleImportCache.set(cacheKey, routeExported); } const loader = routeExported?.loader || null; loaderCache.set(cacheKey, loader); const loaderCacheFn = routeExported?.loaderCache ?? null; loaderCacheFnMap.set(cacheKey, loaderCacheFn); return loader; })(); } async function importAndRunLoader(routeId, serverPath, lazyKey, loaderProps) { if (!serverPath && !lazyKey) { return { loaderData: void 0, routeId }; } const cacheMapKey = lazyKey || serverPath || ""; const loaderCacheFn = loaderCacheFnMap.get(cacheMapKey); let coalFullKey; let coalTtl = 0; if (loaderCacheFn) { const cacheResult = loaderCacheFn(loaderProps?.params, loaderProps?.request); const cacheKey = typeof cacheResult === "string" ? cacheResult : cacheResult?.key; coalTtl = typeof cacheResult === "string" ? 0 : cacheResult?.ttl ?? 0; if (cacheKey != null) { coalFullKey = routeId + "\0" + cacheKey; const existing = pendingLoaderResults.get(coalFullKey); if (existing && (!existing.expires || Date.now() < existing.expires)) { const loaderData = await existing.promise; return { loaderData, routeId }; } } } try { const loaderOrPromise = resolveLoaderSync(serverPath, lazyKey); const loader = loaderOrPromise instanceof Promise ? await loaderOrPromise : loaderOrPromise; if (!loader) { return { loaderData: void 0, routeId }; } if (coalFullKey) { const promise = loader(loaderProps); const entry = { promise, expires: 0 }; pendingLoaderResults.set(coalFullKey, entry); promise.then(() => { entry.expires = coalTtl > 0 ? Date.now() + coalTtl : 0; if (coalTtl <= 0) { Promise.resolve().then(() => pendingLoaderResults.delete(coalFullKey)); } }, () => { pendingLoaderResults.delete(coalFullKey); }); const loaderData2 = await promise; return { loaderData: loaderData2, routeId }; } const loaderData = await loader(loaderProps); return { loaderData, routeId }; } catch (err) { if (isResponse(err)) { throw err; } if (err?.code === "ENOENT") { return { loaderData: void 0, routeId, isEnoent: true }; } console.error(`[one] Error running loader for ${routeId}:`, err); return { loaderData: void 0, routeId }; } } let render = null; let renderStream = null; let renderLoading = null; function ensureRenderLoaded() { if (render) return; if (renderLoading) return renderLoading; renderLoading = (async () => { const entry = options?.lazyRoutes?.serverEntry ? await options.lazyRoutes.serverEntry() : await import((0, import_node_path.resolve)(process.cwd(), `${serverOptions.root}/${outDir}/server/_virtual_one-entry.${typeof oneOptions.build?.server === "object" && oneOptions.build.server.outputFormat === "cjs" ? "c" : ""}js`)); render = entry.default.render; renderStream = entry.default.renderStream; })(); return renderLoading; } const clientDir = (0, import_node_path.join)(process.cwd(), outDir, "client"); const requestHandlers = { async handleStaticFile(filePath) { try { const fullPath = (0, import_node_path.join)(clientDir, filePath); const content = await (0, import_promises.readFile)(fullPath, "utf-8"); return new Response(content, { headers: { "Content-Type": "text/javascript" } }); } catch { return null; } }, 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, "_"); const apiFile = (0, import_node_path.join)(process.cwd(), outDir, "api", fileName + (apiCJS ? ".cjs" : ".js")); return await import(apiFile); }, async loadMiddleware(route) { if (options?.lazyRoutes?.middlewares?.[route.contextKey]) { return await options.lazyRoutes.middlewares[route.contextKey](); } return await import((0, import_toAbsolute.toAbsolute)(route.contextKey)); }, async handleLoader({ route, loaderProps }) { const routeFile = route.routeFile || route.file; const serverPath = route.file.includes(`${outDir}/server`) ? route.file : (0, import_node_path.join)("./", `${outDir}/server`, route.file); let loader; try { const loaderResult = resolveLoaderSync(serverPath, routeFile); loader = loaderResult instanceof Promise ? await loaderResult : loaderResult; } catch (err) { if (err?.code === "ERR_MODULE_NOT_FOUND") { return null; } throw err; } if (!loader) { return null; } let json; try { json = await loader(loaderProps); } catch (err) { if (err?.code === "ENOENT") { return make404LoaderJs(loaderProps?.path || "/", `ENOENT ${err?.path || err}`); } throw err; } 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) { console.error(`Error in route`, route); throw new Error(`No buildinfo found for ${url}, route: ${route.file}, in keys: ${Object.keys(routeToBuildInfo).join("\n ")}`); } try { const layoutRoutes = route.layouts || []; const layoutLoaderPromises = []; const noLoaderResults = []; for (const layout of layoutRoutes) { const serverPath = layout.loaderServerPath || layout.contextKey; const cacheKey = layout.contextKey || serverPath || ""; const cachedLoader = loaderCache.get(cacheKey); if (cachedLoader === null) { noLoaderResults.push({ loaderData: void 0, routeId: layout.contextKey }); } else { layoutLoaderPromises.push(importAndRunLoader(layout.contextKey, serverPath, layout.contextKey, loaderProps)); } } const pageLoaderPromise = importAndRunLoader(route.file, buildInfo2.serverJsPath, route.file, loaderProps); let layoutResults; let pageResult; try { if (layoutLoaderPromises.length === 0) { layoutResults = noLoaderResults; pageResult = await pageLoaderPromise; } else { const [asyncLayoutResults, pr] = await Promise.all([Promise.all(layoutLoaderPromises), pageLoaderPromise]); layoutResults = [...noLoaderResults, ...asyncLayoutResults]; pageResult = pr; } } catch (err) { if (isResponse(err)) { return err; } throw err; } if (pageResult.isEnoent) { const nfPath = findNearestNotFoundPath(loaderProps?.path || "/"); const nfHtml = routeMap[nfPath]; if (nfHtml) { try { const html = await (0, import_promises.readFile)((0, import_node_path.join)(process.cwd(), `${outDir}/client`, nfHtml), "utf-8"); return new Response(html, { headers: { "content-type": "text/html" }, status: 404 }); } catch {} } return new Response("404 Not Found", { status: 404 }); } const matchPathname = loaderProps?.path || "/"; const matchParams = loaderProps?.params || {}; const matches = new Array(layoutResults.length + 1); for (let i = 0; i < layoutResults.length; i++) { const result = layoutResults[i]; matches[i] = { routeId: result.routeId, pathname: matchPathname, params: matchParams, loaderData: result.loaderData }; } matches[layoutResults.length] = { routeId: pageResult.routeId, pathname: matchPathname, params: matchParams, loaderData: pageResult.loaderData }; const loaderData = pageResult.loaderData; for (const layout of layoutRoutes) { const key = layout.contextKey; const loaderFn = loaderCache.get(key); if (loaderFn) { const result = layoutResults.find(r => r.routeId === key); if (result) { (0, import_ssrLoaderData.setSSRLoaderData)(loaderFn, result.loaderData); } } } const pageLoaderFn = loaderCache.get(route.file); if (pageLoaderFn) { (0, import_ssrLoaderData.setSSRLoaderData)(pageLoaderFn, pageResult.loaderData); } globalThis["__vxrnresetState"]?.(); const renderProps = { mode: route.type, loaderData, loaderProps, path: loaderProps?.path || "/", preloads: buildInfo2.criticalPreloads || buildInfo2.preloads, deferredPreloads: buildInfo2.deferredPreloads, css: buildInfo2.css, cssContents: buildInfo2.cssContents, matches }; const _rl = ensureRenderLoaded(); if (_rl) await _rl; const status = route.isNotFound ? 404 : 200; const responseHeaders = route.isNotFound ? htmlHeaders : ssrHtmlHeaders; if (useStreaming) { const stream = await renderStream(renderProps); return new Response(stream, { headers: responseHeaders, status }); } const rendered = await render(renderProps); return new Response(rendered, { headers: responseHeaders, status }); } 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 || []; const needsSpaShell = route.type === "spa" && layoutRoutes.some(layout => layout.layoutRenderMode === "ssg" || layout.layoutRenderMode === "ssr"); if (needsSpaShell) { try { const layoutResults = await Promise.all(layoutRoutes.map(layout => { const serverPath = layout.loaderServerPath || layout.contextKey; return importAndRunLoader(layout.contextKey, serverPath, layout.contextKey, loaderProps); })); const matches = layoutResults.map(result => ({ routeId: result.routeId, pathname: loaderProps?.path || "/", params: loaderProps?.params || {}, loaderData: result.loaderData })); globalThis["__vxrnresetState"]?.(); const _rl3 = ensureRenderLoaded(); if (_rl3) await _rl3; const rendered = await render({ 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: htmlHeaders, 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; const routeCleanPath = route.urlCleanPath.replace(/\?/g, ""); const notFoundKey = route.isNotFound ? route.page.replace(/\[([^\]]+)\]/g, ":$1") : null; const htmlPath = notFoundKey ? routeMap[notFoundKey] : isDynamicRoute ? routeMap[routeCleanPath] || routeMap[url.pathname] : routeMap[url.pathname] || routeMap[buildInfo2?.cleanPath]; if (htmlPath) { const html = await readStaticHtml(htmlPath, outDir); if (html) { const headers = new Headers(); headers.set("content-type", "text/html"); return new Response(html, { headers, status: route.isNotFound ? 404 : 200 }); } } if (isDynamicRoute) { const notFoundRoute = findNearestNotFoundPath(url.pathname); const notFoundHtmlPath = routeMap[notFoundRoute]; if (notFoundHtmlPath) { const notFoundHtml = await readStaticHtml(notFoundHtmlPath, outDir); if (notFoundHtml) { const notFoundMarker = `<script>window.__one404=${JSON.stringify({ originalPath: url.pathname, notFoundPath: notFoundRoute })}</script>`; const injectedHtml = notFoundHtml.includes("</head>") ? notFoundHtml.replace("</head>", `${notFoundMarker}</head>`) : notFoundHtml.replace("<body", `${notFoundMarker}<body`); const headers = new Headers(); headers.set("content-type", "text/html"); return new Response(injectedHtml, { headers, status: 404 }); } } return new Response("404 Not Found", { status: 404 }); } } } }; function createHonoHandler(route) { const isDynamicOrNotFound = route.page.endsWith("/+not-found") || Object.keys(route.routeKeys).length > 0; return async (context, next) => { try { const request = context.req.raw; if (isDynamicOrNotFound) { if (options?.serveStaticAssets) { const staticAssetResponse = await options.serveStaticAssets({ context }); if (staticAssetResponse) { return await (0, import_createHandleRequest.runMiddlewares)(requestHandlers, request, route, async () => staticAssetResponse); } } } const reqPath = context.req.path; if (reqPath.endsWith(".js") || reqPath.endsWith(".css")) { return next(); } if (route.type === "ssr" && !route.middlewares?.length && !reqPath.endsWith(import_constants.LOADER_JS_POSTFIX_UNCACHED)) { if (debugRouter) { console.info(`[one] \u26A1 ${reqPath} \u2192 matched page route: ${route.page} (ssr)`); } const pathname = reqPath; const rawUrl = request.url; const qIdx = rawUrl.indexOf("?"); const search = qIdx >= 0 ? rawUrl.slice(qIdx) : ""; const params = {}; const match = route.compiledRegex.exec(pathname); if (match?.groups) { for (const [key, value] of Object.entries(match.groups)) { const namedKey = route.routeKeys[key]; params[namedKey] = value; } } const loaderProps = { path: pathname, search, subdomain: (0, import_createHandleRequest.getSubdomain)((0, import_createHandleRequest.getURLfromRequestURL)(request)), request, params }; const url2 = (0, import_createHandleRequest.getURLfromRequestURL)(request); const response2 = await resolveResponse(async () => { try { return await requestHandlers.handlePage({ request, route, url: url2, loaderProps }); } catch (err) { if (isResponse(err)) { return err; } throw err; } }); if (response2) { if (isResponse(response2)) { if (isStatusRedirect(response2.status)) { const location = `${response2.headers.get("location") || ""}`; forwardHeaders(response2, context); return context.redirect(location, response2.status); } return response2; } return next(); } return next(); } const url = (0, import_createHandleRequest.getURLfromRequestURL)(request); const response = await (() => { if (url.pathname.endsWith(import_constants.LOADER_JS_POSTFIX_UNCACHED)) { const originalUrl = (0, import_cleanUrl.getPathFromLoaderPath)(url.pathname); if (route.type === "ssg" && Object.keys(route.routeKeys).length > 0) { if (!routeMap[originalUrl]) { return new Response(make404LoaderJs(originalUrl, "ssg route not in routeMap"), { headers: { "Content-Type": "text/javascript" } }); } } const finalUrl = new URL(originalUrl, url.origin); finalUrl.search = url.search; const cleanedRequest = new Request(finalUrl, request); return resolveLoaderRoute(requestHandlers, cleanedRequest, finalUrl, route); } switch (route.type) { case "api": { if (debugRouter) { console.info(`[one] \u26A1 ${url.pathname} \u2192 matched API route: ${route.page}`); } return resolveAPIRoute(requestHandlers, request, url, route); } case "ssg": case "spa": case "ssr": { if (debugRouter) { console.info(`[one] \u26A1 ${url.pathname} \u2192 matched page route: ${route.page} (${route.type})`); } return resolvePageRoute(requestHandlers, request, url, route); } } })(); if (response) { if (isResponse(response)) { if (isStatusRedirect(response.status)) { const location = `${response.headers.get("location") || ""}`; forwardHeaders(response, context); return context.redirect(location, response.status); } if (isAPIRequest.get(request)) { try { if (!response.headers.has("cache-control") && !response.headers.has("Cache-Control")) { response.headers.set("cache-control", "no-store"); } return 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); } } if (!response.headers.has("cache-control") && !response.headers.has("Cache-Control")) { if (route.type === "ssg" || route.type === "spa") { response.headers.set("cache-control", "public, s-maxage=60, stale-while-revalidate=120"); } else { response.headers.set("cache-control", "no-cache"); } } return response; } return next(); } } catch (err) { console.error(` [one] Error handling request: ${err["stack"]}`); } return next(); }; } const compiledManifest = (0, import_createHandleRequest.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)); if (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)); if (route.urlPath !== route.urlCleanPath) { app.get(route.urlCleanPath, createHonoHandler(route)); } } const { preloads, cssPreloads } = buildInfo; app.get("*", async (c, next) => { if (c.req.path.endsWith(import_constants.PRELOAD_JS_POSTFIX)) { if (!preloads[c.req.path]) { c.header("Content-Type", "text/javascript"); c.status(200); return c.body(``); } } if (c.req.path.endsWith(import_constants.CSS_PRELOAD_JS_POSTFIX)) { if (!cssPreloads?.[c.req.path]) { c.header("Content-Type", "text/javascript"); c.status(200); return c.body(`export default Promise.resolve()`); } } if (c.req.path.endsWith(import_constants.LOADER_JS_POSTFIX_UNCACHED)) { const request = c.req.raw; const url = (0, import_createHandleRequest.getURLfromRequestURL)(request); const originalUrl = (0, import_cleanUrl.getPathFromLoaderPath)(c.req.path); for (const route of compiledManifest.pageRoutes) { if (route.file === "") { continue; } if (!route.compiledRegex.test(originalUrl)) { continue; } if (route.type === "ssg" && Object.keys(route.routeKeys).length > 0 && !routeMap[originalUrl]) { c.header("Content-Type", "text/javascript"); c.status(200); return c.body(make404LoaderJs(originalUrl, "ssg route not in routeMap")); } const loaderRoute = { ...route, routeFile: route.file, // preserve original for lazy route lookup file: route.loaderServerPath || c.req.path }; const finalUrl = new URL(originalUrl, url.origin); finalUrl.search = url.search; const cleanedRequest = new Request(finalUrl, request); try { const resolved = await resolveLoaderRoute(requestHandlers, cleanedRequest, finalUrl, loaderRoute); return resolved; } catch (err) { if (err?.code === "ERR_MODULE_NOT_FOUND") { c.header("Content-Type", "text/javascript"); c.status(200); return c.body(`export function loader() { return undefined }`); } console.error(`Error running loader: ${err}`); return next(); } } } return next(); }); }