Created
August 26, 2024 16:00
-
-
Save trvswgnr/3f949a3c0a8046fdbc9604ae1006f2da to your computer and use it in GitHub Desktop.
bun build and serve with browser hmr
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
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