Skip to content

Instantly share code, notes, and snippets.

@trvswgnr
Created August 26, 2024 16:00
Show Gist options
  • Save trvswgnr/3f949a3c0a8046fdbc9604ae1006f2da to your computer and use it in GitHub Desktop.
Save trvswgnr/3f949a3c0a8046fdbc9604ae1006f2da to your computer and use it in GitHub Desktop.
bun build and serve with browser hmr
import path from "path";
import chokidar from "chokidar";
import fs from "fs";
import type { ServerWebSocket } from "bun";
const CONFIG = Object.freeze({
srcDir: "./src",
outDir: "./public",
entrypoints: ["index.ts"],
});
const { server, clients } = serve();
await build().then(watch);
function watch() {
chokidar
.watch(CONFIG.srcDir, {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true,
ignoreInitial: true,
})
.on("all", async (e) => {
console.log(e);
await build().then(reloadBrowser);
});
}
function reloadBrowser() {
clients.forEach((client) => client.send("reload"));
console.log("browser reloaded");
}
async function build() {
// clean public folder
fs.rmSync(CONFIG.outDir, { recursive: true });
const buildOutput = await Bun.build({
entrypoints: CONFIG.entrypoints.map((entrypoint) => path.join(CONFIG.srcDir, entrypoint)),
outdir: CONFIG.outDir,
target: "browser",
minify: true,
sourcemap: "external",
naming: {
asset: "[name].[ext]",
},
});
// inject reload script into index.html and add to public folder
await injectHotReloadScript();
return buildOutput;
}
async function injectHotReloadScript() {
const script = `<script>const ws=new WebSocket(\`ws://\${window.location.host}\`);ws.onmessage=e=>{"reload"===e.data&&(console.log("Reloading page..."),window.location.reload())};</script>`;
const inject = async (filepath: string) => {
const file = Bun.file(filepath);
const contents = await file.text();
const injected = contents.replace("</body>", `${script}</body>`);
Bun.write(filepath, injected);
};
const glob = new Bun.Glob(path.join(CONFIG.outDir, "**", "*.html"));
const scannedFiles = await Array.fromAsync(glob.scan());
await Promise.all(scannedFiles.map(inject));
}
function serve() {
const clients = new Set<ServerWebSocket<unknown>>();
const server = Bun.serve({
port: 3000,
async fetch(req, s) {
// Bun automatically returns a 101 Switching Protocols if the upgrade succeeds
if (s.upgrade(req)) return;
return serveStatic(req, { dir: "./public" });
},
websocket: {
open: (ws) => {
clients.add(ws);
},
message: (ws, message) => {},
close: (ws) => {
clients.delete(ws);
},
},
error(e) {
console.log(e);
return new Response(undefined, { status: 500 });
},
});
console.log(`server running at ${server.url}`);
return { server, clients };
}
async function serveStatic(req: Request, { dir }: { dir: string }) {
const requestUrl = new URL(req.url);
const requestPathname = requestUrl.pathname;
const filepath = getFilePath(requestPathname, dir);
const file = Bun.file(filepath);
return new Response(file);
}
function getFilePath(requestPathname: string, dir: string) {
if (requestPathname === "/") {
return path.join(dir, "index.html");
}
return path.join(dir, requestPathname);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment