From 62fab080e8520d9c898c907c247d1af8b4f99e33 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Tue, 15 Sep 2026 17:02:37 +0800 Subject: [PATCH] chore(site): add static export preview script --- docs/package.json | 1 + docs/scripts/preview-server.mjs | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 docs/scripts/preview-server.mjs diff --git a/docs/package.json b/docs/package.json index 793c111288..219b67bf4b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,6 +4,7 @@ "license": "Apache-2.0", "scripts": { "dev": "next dev", + "preview": "node scripts/preview-server.mjs", "content:check": "node scripts/verify-doc-content.mjs", "prebuild": "node scripts/verify-doc-content.mjs", "build": "next build", diff --git a/docs/scripts/preview-server.mjs b/docs/scripts/preview-server.mjs new file mode 100644 index 0000000000..d73d34404c --- /dev/null +++ b/docs/scripts/preview-server.mjs @@ -0,0 +1,66 @@ +// Local preview for the static export, mimicking Cloudflare Workers asset +// resolution: exact file first, then /foo -> /foo.html, then /foo/ -> +// /foo/index.html, and unknown paths fall back to 404.html like +// not_found_handling: "404-page". Run `pnpm build` before this script. +// +// Usage: node scripts/preview-server.mjs [port] +import { createServer } from "node:http"; +import { readFile, stat } from "node:fs/promises"; +import { extname, join, resolve } from "node:path"; + +const ROOT = resolve(import.meta.dirname, "../out"); +const PORT = Number(process.argv[2] ?? 3000); +const types = { + ".html": "text/html; charset=utf-8", + ".js": "text/javascript", + ".css": "text/css", + ".txt": "text/plain", + ".png": "image/png", + ".svg": "image/svg+xml", + ".webp": "image/webp", + ".json": "application/json", + ".xml": "application/xml", + ".ico": "image/x-icon", + ".map": "application/json", + ".woff2": "font/woff2", +}; + +async function tryFile(rel) { + const file = join(ROOT, rel); + try { + const info = await stat(file); + if (!info.isFile()) return null; + return { file, type: types[extname(file)] ?? "application/octet-stream" }; + } catch { + return null; + } +} + +createServer(async (req, res) => { + try { + const path = decodeURIComponent(new URL(req.url, "http://localhost").pathname); + if (path.includes("..")) { + res.writeHead(400); + return res.end(); + } + const hit = + (await tryFile(path)) ?? + (path.endsWith("/") ? await tryFile(join(path, "index.html")) : await tryFile(`${path}.html`)); + if (hit) { + res.writeHead(200, { "content-type": hit.type, "cache-control": "no-store" }); + return res.end(await readFile(hit.file)); + } + const fallback = await tryFile("404.html"); + if (fallback) { + res.writeHead(404, { "content-type": fallback.type, "cache-control": "no-store" }); + return res.end(await readFile(fallback.file)); + } + res.writeHead(404, { "content-type": "text/plain" }); + res.end("404"); + } catch (error) { + res.writeHead(500); + res.end(String(error)); + } +}).listen(PORT, () => { + console.log(`Preview serving ${ROOT} at http://localhost:${PORT}`); +});