150 lines
4.2 KiB
JavaScript
150 lines
4.2 KiB
JavaScript
import { createServer } from "node:http";
|
|
import { readFile, stat, writeFile, mkdir } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import puppeteer from "puppeteer";
|
|
import { PUBLIC_ROUTES, NOT_FOUND_ROUTE } from "./routes.mjs";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const distDir = path.resolve(__dirname, "../dist");
|
|
|
|
const routes = PUBLIC_ROUTES.filter((route) => route.prerender).map(
|
|
(route) => route.path
|
|
);
|
|
|
|
const mimeTypes = {
|
|
".html": "text/html",
|
|
".js": "application/javascript",
|
|
".css": "text/css",
|
|
".json": "application/json",
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".avif": "image/avif",
|
|
".webp": "image/webp",
|
|
".svg": "image/svg+xml",
|
|
".ico": "image/x-icon",
|
|
".txt": "text/plain",
|
|
".xml": "application/xml",
|
|
".map": "application/json",
|
|
".gz": "application/gzip",
|
|
".br": "application/brotli",
|
|
};
|
|
|
|
function serveStaticFile(res, filePath) {
|
|
const ext = path.extname(filePath).toLowerCase();
|
|
const contentType = mimeTypes[ext] ?? "application/octet-stream";
|
|
res.statusCode = 200;
|
|
res.setHeader("Content-Type", contentType);
|
|
return readFile(filePath).then((data) => res.end(data));
|
|
}
|
|
|
|
function createStaticServer() {
|
|
const server = createServer(async (req, res) => {
|
|
try {
|
|
const requestUrl = new URL(req.url ?? "/", "http://localhost");
|
|
let pathname = decodeURIComponent(requestUrl.pathname);
|
|
const hasExtension = path.extname(pathname) !== "";
|
|
|
|
if (pathname.endsWith("/")) {
|
|
pathname = pathname.slice(0, -1);
|
|
}
|
|
|
|
let filePath = path.join(distDir, pathname);
|
|
|
|
try {
|
|
const fileStat = await stat(filePath);
|
|
if (fileStat.isDirectory()) {
|
|
filePath = path.join(filePath, "index.html");
|
|
}
|
|
await serveStaticFile(res, filePath);
|
|
return;
|
|
} catch (error) {
|
|
if (hasExtension) {
|
|
res.statusCode = 404;
|
|
res.end("Not found");
|
|
return;
|
|
}
|
|
filePath = path.join(distDir, "index.html");
|
|
await serveStaticFile(res, filePath);
|
|
}
|
|
} catch (error) {
|
|
res.statusCode = 500;
|
|
res.end(`Server error: ${error instanceof Error ? error.message : error}`);
|
|
}
|
|
});
|
|
|
|
return new Promise((resolve) => {
|
|
server.listen(0, () => {
|
|
const address = server.address();
|
|
if (address && typeof address === "object") {
|
|
resolve({ server, port: address.port });
|
|
} else {
|
|
resolve({ server, port: 4173 });
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
async function renderRoute(browser, baseUrl, route, outputFile) {
|
|
const page = await browser.newPage();
|
|
const targetUrl = `${baseUrl}${route}`;
|
|
|
|
try {
|
|
await page.goto(targetUrl, {
|
|
waitUntil: ["networkidle0", "domcontentloaded"],
|
|
timeout: 60000,
|
|
});
|
|
} catch (error) {
|
|
console.warn(
|
|
`[prerender] Navigation to ${targetUrl} failed: ${
|
|
error instanceof Error ? error.message : error
|
|
}`
|
|
);
|
|
}
|
|
|
|
try {
|
|
// Donne un léger délai pour laisser React terminer le rendu asynchrone
|
|
await delay(1500);
|
|
const html = await page.content();
|
|
const outputPath =
|
|
outputFile ??
|
|
(route === "/"
|
|
? path.join(distDir, "index.html")
|
|
: path.join(distDir, route.replace(/^\//, ""), "index.html"));
|
|
|
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
await writeFile(outputPath, html, "utf8");
|
|
|
|
console.log(
|
|
`✔ prerendered ${route} -> ${path.relative(distDir, outputPath) || "index.html"}`
|
|
);
|
|
} finally {
|
|
await page.close();
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const { server, port } = await createStaticServer();
|
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
const browser = await puppeteer.launch({ headless: true });
|
|
|
|
try {
|
|
for (const route of routes) {
|
|
await renderRoute(browser, baseUrl, route);
|
|
}
|
|
await renderRoute(browser, baseUrl, NOT_FOUND_ROUTE, path.join(distDir, "404.html"));
|
|
} finally {
|
|
await browser.close();
|
|
server.close();
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error("[prerender] Failed to prerender routes:", error);
|
|
process.exit(1);
|
|
});
|