UNPKG

rakkasjs

Version:

Bleeding-edge React framework powered by Vite

187 lines (184 loc) 6.09 kB
import { version } from "./chunk-NPN2JRYM.js"; // src/cli/prerender.ts import installNodeFetch from "@hattip/polyfills/node-fetch"; import { resolveConfig } from "vite"; import pico from "picocolors"; import fs from "fs"; import path from "path"; import { load } from "cheerio"; import { pathToFileURL } from "url"; async function prerender(paths, options) { const config = await resolveConfig( { root: options.root, base: options.base, mode: options.mode, configFile: options.config, logLevel: options.logLevel, clearScreen: options.clearScreen, build: { ssr: true } }, "build" ); config.logger.info( pico.black(pico.bgMagenta(" RAKKAS ")) + " " + pico.magenta(version) + " \u{1F483}" ); config.logger.info( "\n" + pico.magenta("rakkas") + ": Rendering static routes (" + pico.green("1/1") + ")" ); await doPrerender(config, paths.length ? paths : void 0, options.crawl); } async function doPrerender(config, defaultPaths, autoCrawl) { autoCrawl = autoCrawl ?? !defaultPaths?.length; const outDir = path.resolve(config.root, config.build.outDir); let pathNames = defaultPaths ?? (config.api?.rakkas?.prerender || []); const origin = "http://localhost"; if (pathNames.length === 0) { pathNames = ["/"]; } installNodeFetch(); process.env.RAKKAS_PRERENDER = "true"; const fileUrl = pathToFileURL(outDir + "/server/hattip.js").href; const { default: handler } = await import(fileUrl); const paths = new Set(pathNames); const files = /* @__PURE__ */ new Map(); const dirs = /* @__PURE__ */ new Set(); function crawl(href) { const url = new URL(href, origin); if (url.origin !== origin) { return; } paths.add(url.pathname); } if (process.stdout.isTTY) { process.stdout.write("\x1B[?25l"); } for (const currentPath of paths) { if (process.stdout.isTTY) { process.stdout.clearLine(0); process.stdout.cursorTo(0); } process.stdout.write(pico.gray("Crawling ") + currentPath); const request = new Request(origin + currentPath, { headers: { "User-Agent": "rakkasjs-crawler" } }); await handler({ request, ip: "127.0.0.1", passThrough() { }, waitUntil() { }, platform: { async render(pathname, response, options, error) { const url = new URL(pathname, origin); const { shouldPrerender = true, shouldCrawl = autoCrawl ? shouldPrerender : false, links = [] } = options || {}; links.forEach((link) => crawl(new URL(link, url))); if (!shouldPrerender && !shouldCrawl) { return; } const isRedirect = response.status >= 300 && response.status < 400; const isPage = response.headers.get("content-type")?.split(";")[0] === "text/html" || isRedirect; if (isPage) { if (!pathname.endsWith("/")) { pathname += "/"; } pathname += "index.html"; } else if (pathname.endsWith("/")) { pathname = pathname.slice(0, -1); } const filename = outDir + "/client" + pathname; if (!files.has(pathname)) { const dirname = path.dirname(filename); if (!dirs.has(dirname)) { await fs.promises.mkdir(dirname, { recursive: true }); dirs.add(dirname); } let body; if (isPage && shouldCrawl) { const html = await response.text(); const dom = load(html); dom("a[href]").each((_, el) => { crawl(new URL(el.attribs.href, url)); }); dom("area[href]").each((_, el) => { crawl(new URL(el.attribs.href, url)); }); body = html; } else { body = response.body; } if (shouldPrerender) { if (!body) { body = isRedirect ? `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=${escapeHtml( response.headers.get("location") )}"></head></html>` : ""; } await fs.promises.writeFile(filename, body); files.set(pathname, [response.status, error]); } else { files.set(pathname, [-1, void 0]); } } } } }); } if (process.stdout.isTTY) { process.stdout.write("\x1B[?25h"); process.stdout.clearLine(0); process.stdout.cursorTo(0); } const fileNames = [...files.entries()].filter(([, v]) => v[0] !== -1).sort((a, b) => { if (!!a[1][1] === !!b[1][1]) { return a[0].localeCompare(b[0]); } else { return a[1][1] ? 1 : -1; } }).map(([name]) => name); if (fileNames.length > 0) { let errorCount = 0; for (const [, [, error]] of files) { if (error) { errorCount++; } } config.logger.info( (errorCount ? pico.yellow("!") : pico.green("\u2713")) + ` ${plural(fileNames.length, "static route")} prerendered` + (errorCount ? ` (${plural(errorCount, "error")})` : ".") ); let errorSeen = false; for (const fileName of fileNames) { const [status, error] = files.get(fileName); if (error) { if (!errorSeen) { config.logger.info( pico.red("\nSome pages were rendered with errors:") ); errorSeen = true; } } config.logger.info( (status < 300 ? status : status < 400 ? pico.yellow(status) : pico.red(status)) + " " + pico.gray(config.build.outDir + "/client/") + pico.cyan(fileName.slice(1)) ); if (error) { config.logger.error(error?.stack); } } } } function plural(n, s) { return n + " " + (n === 1 ? s : s + "s"); } function escapeHtml(text) { return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;"); } export { prerender, doPrerender };