UNPKG

dinou

Version:

Dinou is a modern React 19 framework with React Server Components, Server Functions, and streaming SSR.

162 lines (134 loc) 3.48 kB
/** * esm-hmr/client.js * A client-side implementation of the ESM-HMR spec, for reference. */ function debug(...args) { console.log("[ESM-HMR]", ...args); } function reload() { location.reload(true); } let SOCKET_MESSAGE_QUEUE = []; function _sendSocketMessage(msg) { socket.send(JSON.stringify(msg)); } function sendSocketMessage(msg) { if (socket.readyState !== socket.OPEN) { SOCKET_MESSAGE_QUEUE.push(msg); } else { _sendSocketMessage(msg); } } const socketURL = window.HMR_WEBSOCKET_URL || (location.protocol === "http:" ? "ws://" : "wss://") + location.host + "/"; const socket = new WebSocket(socketURL, "esm-hmr"); socket.addEventListener("open", () => { SOCKET_MESSAGE_QUEUE.forEach(_sendSocketMessage); SOCKET_MESSAGE_QUEUE = []; }); const REGISTERED_MODULES = {}; class HotModuleState { constructor(id) { this.id = id; this.data = {}; this.isLocked = false; this.isDeclined = false; this.isAccepted = false; this.acceptCallbacks = []; this.disposeCallbacks = []; } lock() { this.isLocked = true; } dispose(callback) { this.disposeCallbacks.push(callback); } invalidate() { reload(); } decline() { this.isDeclined = true; } accept(_deps = [], callback = true) { if (this.isLocked) { return; } if (!this.isAccepted) { sendSocketMessage({ id: this.id, type: "hotAccept" }); this.isAccepted = true; } if (!Array.isArray(_deps)) { callback = _deps || callback; _deps = []; } if (callback === true) { callback = () => {}; } const deps = _deps.map((dep) => { const ext = dep.split(".").pop(); if (!ext) { dep += ".js"; } else if (ext !== "js") { dep += ".proxy.js"; } return new URL(dep, `${window.location.origin}${this.id}`).pathname; }); this.acceptCallbacks.push({ deps, callback, }); } } export function createHotContext(id) { const existing = REGISTERED_MODULES[id]; if (existing) { existing.lock(); return existing; } const state = new HotModuleState(id); REGISTERED_MODULES[id] = state; return state; } async function applyUpdate(id) { const state = REGISTERED_MODULES[id]; if (!state || state.isDeclined) { return false; } const acceptCallbacks = state.acceptCallbacks; const disposeCallbacks = state.disposeCallbacks; state.disposeCallbacks = []; state.data = {}; disposeCallbacks.forEach((callback) => callback()); const updateID = Date.now(); for (const { deps, callback: acceptCallback } of acceptCallbacks) { const [module, ...depModules] = await Promise.all([ import(`/${id}` + `?mtime=${updateID}`), ...deps.map((d) => import(`/${d}` + `?mtime=${updateID}`)), ]); acceptCallback({ module, deps: depModules }); } return true; } socket.addEventListener("message", ({ data: _data }) => { if (!_data) return; const data = JSON.parse(_data); if (data.type === "reload") { reload(); return; } if (data.type !== "update") { return; } applyUpdate(data.url) .then((ok) => { if (!ok) { reload(); } }) .catch((err) => { console.error(err); reload(); }); }); debug("listening for file changes...");