Files
WRNexusJS/packages/dev-server/src/hmr.ts
T
2026-07-21 12:30:46 +05:30

68 lines
1.7 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);
}
broadcastJson(message: unknown): void {
const payload = JSON.stringify(message);
for (const ws of this.sockets) {
try {
ws.send(payload);
} catch {
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 });
}
}