57 lines
1.5 KiB
TypeScript
57 lines
1.5 KiB
TypeScript
/**
|
|
* HMR hub — tracks connected browser HMR sockets and broadcasts update events.
|
|
*
|
|
* Each open page holds one WebSocket to `/__wrnexus/hmr`. The in-process file
|
|
* watcher (see index.ts) classifies a change and broadcasts a typed message:
|
|
*
|
|
* { type: "css" } -> the browser hot-swaps the stylesheet (no reload)
|
|
* { type: "reload" } -> the browser asks for fresh HTML over the HMR socket
|
|
*
|
|
* Page/component/API/middleware/realtime changes invalidate their modules and
|
|
* broadcast `reload` without closing the server or WebSocket. The browser asks
|
|
* the same process for fresh HTML and performs a soft DOM morph.
|
|
*/
|
|
|
|
export type HmrMessage = { type: "css"; version: number } | { type: "reload"; version: number };
|
|
|
|
/** Minimal shape of a Bun ServerWebSocket we rely on. */
|
|
interface Socket {
|
|
send(data: string): unknown;
|
|
}
|
|
|
|
export class HmrHub {
|
|
private sockets = new Set<Socket>();
|
|
private version = 0;
|
|
|
|
add(ws: Socket): void {
|
|
this.sockets.add(ws);
|
|
}
|
|
|
|
remove(ws: Socket): void {
|
|
this.sockets.delete(ws);
|
|
}
|
|
|
|
broadcast(message: HmrMessage): void {
|
|
const payload = JSON.stringify(message);
|
|
for (const ws of this.sockets) {
|
|
try {
|
|
ws.send(payload);
|
|
} catch {
|
|
this.sockets.delete(ws);
|
|
}
|
|
}
|
|
}
|
|
|
|
get size(): number {
|
|
return this.sockets.size;
|
|
}
|
|
|
|
css(): void {
|
|
this.broadcast({ type: "css", version: ++this.version });
|
|
}
|
|
|
|
reload(): void {
|
|
this.broadcast({ type: "reload", version: ++this.version });
|
|
}
|
|
}
|