dinou
Version:
Dinou is a modern React 19 framework with React Server Components, Server Functions, and streaming SSR.
135 lines (116 loc) • 3.63 kB
JavaScript
const WebSocket = require("ws");
/**
* @typedef {Object} Dependency
* @property {Set<string>} dependents
* @property {Set<string>} dependencies
* @property {boolean} isHmrEnabled
* @property {boolean} isHmrAccepted
* @property {boolean} needsReplacement
*/
const map = new Map();
const set = new Set();
class EsmHmrEngine {
constructor(options = {}) {
this.clients = set;
this.dependencyTree = map;
const wss = options.server
? new WebSocket.Server({ noServer: true })
: new WebSocket.Server({ port: 3001 });
if (options.server) {
options.server.on("upgrade", (req, socket, head) => {
if (req.headers["sec-websocket-protocol"] !== "esm-hmr") {
return;
}
wss.handleUpgrade(req, socket, head, (client) => {
wss.emit("connection", client, req);
});
});
}
wss.on("connection", (client) => {
this.connectClient(client);
this.registerListener(client);
});
}
registerListener(client) {
client.on("message", (data) => {
const message = JSON.parse(data.toString());
if (message.type === "hotAccept") {
const entry = this.getEntry(message.id, true);
entry.isHmrAccepted = true;
}
});
}
createEntry(sourceUrl) {
const newEntry = {
dependencies: new Set(),
dependents: new Set(),
needsReplacement: false,
isHmrEnabled: false,
isHmrAccepted: false,
};
this.dependencyTree.set(sourceUrl, newEntry);
return newEntry;
}
getEntry(sourceUrl, createIfNotFound = false) {
const result = this.dependencyTree.get(sourceUrl);
if (result) return result;
if (createIfNotFound) return this.createEntry(sourceUrl);
return null;
}
getDependencyTree() {
return this.dependencyTree;
}
setEntry(sourceUrl, imports, isHmrEnabled = false) {
const result = this.getEntry(sourceUrl, true);
const outdatedDependencies = new Set(result.dependencies);
result.isHmrEnabled = isHmrEnabled;
for (const importUrl of imports) {
this.addRelationship(sourceUrl, importUrl);
outdatedDependencies.delete(importUrl);
}
for (const importUrl of outdatedDependencies) {
this.removeRelationship(sourceUrl, importUrl);
}
}
removeRelationship(sourceUrl, importUrl) {
const importResult = this.getEntry(importUrl);
if (importResult) importResult.dependents.delete(sourceUrl);
const sourceResult = this.getEntry(sourceUrl);
if (sourceResult) sourceResult.dependencies.delete(importUrl);
}
addRelationship(sourceUrl, importUrl) {
if (importUrl !== sourceUrl) {
const importResult = this.getEntry(importUrl, true);
importResult.dependents.add(sourceUrl);
const sourceResult = this.getEntry(sourceUrl, true);
sourceResult.dependencies.add(importUrl);
}
}
markEntryForReplacement(entry, state) {
entry.needsReplacement = state;
}
broadcastMessage(data) {
this.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
} else {
this.disconnectClient(client);
}
});
}
connectClient(client) {
this.clients.add(client);
}
disconnectClient(client) {
client.terminate();
this.clients.delete(client);
}
disconnectAllClients() {
for (const client of this.clients) {
this.disconnectClient(client);
}
}
}
module.exports = {
EsmHmrEngine,
};