rakkasjs
Version:
Bleeding-edge React framework powered by Vite
187 lines (184 loc) • 6.09 kB
JavaScript
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
}
export {
prerender,
doPrerender
};