Last active
June 25, 2026 04:07
-
-
Save adrianmgg/514158b26ffa71ba77fa9e53e16d75ae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // serve a page bundled with deno (with an HTML entrypoint) | |
| // - all in-memory | |
| // - re-builds automatically on refresh | |
| // - rudimentary auto-reload (not hot reloading, it just refreshes the page) | |
| import { extname } from "@std/path"; | |
| import { typeByExtension } from "@std/media-types"; | |
| import { debounce } from "@std/async"; | |
| import { DOMParser } from "@b-fuze/deno-dom"; | |
| import { serveDir } from "@std/http/file-server"; | |
| let lastBuild: Deno.bundle.Result | null; | |
| async function rebuild() { | |
| console.log("rebuilding..."); | |
| try { | |
| const result = await Deno.bundle({ | |
| entrypoints: ["./src/index.html"], | |
| outputDir: "/", | |
| platform: "browser", | |
| write: false, | |
| minify: false, | |
| keepNames: true, | |
| codeSplitting: false, | |
| packages: "bundle", | |
| sourcemap: "linked", | |
| }); | |
| lastBuild = result; | |
| } catch (err) { | |
| console.error("build failed internally!"); | |
| console.error(err); | |
| lastBuild = null; | |
| return; | |
| } | |
| if (!lastBuild.success) console.error("build failed!"); | |
| lastBuild.warnings.forEach((error) => console.warn(error)); | |
| lastBuild.errors.forEach((error) => console.error(error)); | |
| } | |
| const reloadListenerClients: WebSocket[] = []; | |
| (async () => { | |
| const watcher = Deno.watchFs("./src/", { recursive: true }); | |
| const onChange = debounce(() => { | |
| reloadListenerClients.forEach((ws) => ws.send("reload")); | |
| }, 100); | |
| for await (const event of watcher) { | |
| onChange(); | |
| } | |
| })(); | |
| addEventListener("srcModified", console.log); | |
| const BUNDLE_PATH_ALIASES: Record<string, string> = { | |
| "/": "/index.html", | |
| }; | |
| const RELOAD_ALERTS_PATH = "/_devServer/reload-alerts"; | |
| const RELOAD_ALERTS_SCRIPT = ` | |
| (function(){ | |
| const ws = new WebSocket(${JSON.stringify(RELOAD_ALERTS_PATH)}); | |
| ws.addEventListener("message", () => { window.location.reload(); }); | |
| })(); | |
| `; | |
| const CACHE_CONTROL_NOCACHE = "max-age=0, no-transform, no-store"; | |
| function serveBundleFile(file: Deno.bundle.OutputFile): Response { | |
| const headers: Record<string, string> = { | |
| "Cache-Control": CACHE_CONTROL_NOCACHE, | |
| }; | |
| const extension = extname(file.path); | |
| const mime = typeByExtension(extension); | |
| if (mime !== undefined) { | |
| headers["Content-Type"] = mime; | |
| } | |
| let respData; | |
| if (file.path === "/index.html") { | |
| // patch the bundled HTML to include the reload script | |
| const dom = new DOMParser().parseFromString(file.text(), "text/html"); | |
| const reloadScript = dom.createElement("script"); | |
| reloadScript.textContent = RELOAD_ALERTS_SCRIPT; | |
| dom.head.appendChild(reloadScript); | |
| respData = `<!DOCTYPE html>\n${dom.documentElement!.outerHTML}`; | |
| } else { | |
| respData = file.contents; | |
| } | |
| return new Response(respData, { status: 200, headers }); | |
| } | |
| Deno.serve(async (req) => { | |
| const url = new URL(req.url); | |
| const path = BUNDLE_PATH_ALIASES[url.pathname] ?? url.pathname; | |
| if (req.headers.get("upgrade") === "websocket" && path === RELOAD_ALERTS_PATH) { | |
| const { socket, response } = Deno.upgradeWebSocket(req); | |
| reloadListenerClients.push(socket); | |
| socket.addEventListener("close", () => { | |
| reloadListenerClients.splice(reloadListenerClients.indexOf(socket), 1); | |
| }); | |
| return response; | |
| } | |
| // rebuild on first ever request to any path, and on subsequent requests for index page | |
| if (lastBuild === undefined || path === "/index.html") { | |
| await rebuild(); | |
| } | |
| if (lastBuild === null || !lastBuild.success) { | |
| return new Response(null, { status: 500 }); | |
| } | |
| for (const file of lastBuild?.outputFiles ?? []) { | |
| if (file.path === path) { | |
| return serveBundleFile(file); | |
| } | |
| } | |
| return serveDir(req, { | |
| fsRoot: "./src/", | |
| headers: [`Cache-Control: ${CACHE_CONTROL_NOCACHE}`], | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment