Skip to content

Instantly share code, notes, and snippets.

@adrianmgg
Last active June 25, 2026 04:07
Show Gist options
  • Select an option

  • Save adrianmgg/514158b26ffa71ba77fa9e53e16d75ae to your computer and use it in GitHub Desktop.

Select an option

Save adrianmgg/514158b26ffa71ba77fa9e53e16d75ae to your computer and use it in GitHub Desktop.
// 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