/** * dev/server.mjs — Orbit's dev static server. * * node dev/server.mjs [port] (default 8080) * * Why not `python3 -m http.server`? That server sends `Last-Modified` * with no `Cache-Control`, so browsers apply HEURISTIC caching to the * ES modules. During iterative development the browser then keeps * running an OLD js/*.js module graph (stale GameScene.js, stale * BuildWindow.js, …) long after the files on disk changed — while * re-fetched JSON data is fresh. New data + old JS is exactly how you * get "the build console behaves like an older version" no matter how * often you clear the JSON cache or hard-reset the game. * * This server sends `Cache-Control: no-store` on every response, so a * normal reload always runs what's on disk. Zero dependencies (node). */ import http from 'node:http'; import { promises as fs } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const PORT = Number(process.argv[2] ?? 8080); const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.css': 'text/css; charset=utf-8', '.mp4': 'video/mp4', '.webm': 'video/webm', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.otf': 'font/otf', '.ttf': 'font/ttf', '.woff': 'font/woff', '.woff2': 'font/woff2', '.md': 'text/markdown; charset=utf-8', '.txt': 'text/plain; charset=utf-8', }; const server = http.createServer(async (req, res) => { const url = new URL(req.url, `http://${req.headers.host ?? 'localhost'}`); let p = decodeURIComponent(url.pathname); if (p.endsWith('/')) p += 'index.html'; const file = path.normalize(path.join(ROOT, p)); if (!file.startsWith(ROOT + path.sep) && file !== ROOT) { res.writeHead(403).end('forbidden'); return; } try { const data = await fs.readFile(file); res.writeHead(200, { 'Content-Type': MIME[path.extname(file).toLowerCase()] ?? 'application/octet-stream', 'Content-Length': data.length, // The whole point: browsers must never cache these. 'Cache-Control': 'no-store, no-cache, must-revalidate', Pragma: 'no-cache', Expires: '0', }); res.end(data); } catch { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' }); res.end(`404 — not found: ${url.pathname}\n(serving ${ROOT})`); } }); server.listen(PORT, '127.0.0.1', () => { console.log(`orbit dev server → http://127.0.0.1:${PORT} (no-store caching; root ${ROOT})`); });