staticHost
uses node:http
Serve a directory as a static host does: its files, and nothing else. Resolves with its URL and a way to stop it;
on port, if given (a site that comes back where it was, as after a deploy).
import fs from "node:fs"; import http from "node:http"; import path from "node:path"; export function staticHost( root: string, port = 0, ): Promise<{ url: string; port: number; close: () => Promise<void> }> { const types: Record<string, string> = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml", ".png": "image/png", ".webmanifest": "application/manifest+json", }; const server = http.createServer((req, res) => { let file = path.join(root, decodeURIComponent(new URL(req.url ?? "/", "http://x").pathname)); // (`/today` is today.html, as static hosts have it) if (!fs.existsSync(file) && fs.existsSync(`${file}.html`)) file = `${file}.html`; if (fs.existsSync(file) && fs.statSync(file).isDirectory()) file = path.join(file, "index.html"); if (!file.startsWith(root) || !fs.existsSync(file)) return void res.writeHead(404).end("not found"); res.writeHead(200, { "content-type": types[path.extname(file)] ?? "application/octet-stream" }); res.end(fs.readFileSync(file)); }); return new Promise((resolve) => server.listen(port, "127.0.0.1", () => { const at = (server.address() as { port: number }).port; resolve({ url: `http://127.0.0.1:${at}`, port: at, close: () => { server.closeAllConnections(); return new Promise((r) => server.close(() => r())); }, }); }), ); }