import { diagnostics as initialDiagnostics } from "./noxid-diagnostics.js";
const modules = new Map();
const instances = new Set();
const restoredState = new Map();
const pending = new Map();
let updateSequence = 0;
let flushTimer = null;
function activeInstances(component = null) {
const active = [];
for (const instance of instances) {
if (instance.owner?.disposed) {
instances.delete(instance);
} else if (component === null || instance.component === component) {
active.push(instance);
}
}
return active;
}
function classify(previous, next) {
if (!previous || !next || previous.component !== next.component) return "reload";
if (previous.interfaceHash !== next.interfaceHash) return "reload";
if (previous.stateSchemaHash !== next.stateSchemaHash) return "remount-reset-state";
if (
previous.templateHash !== next.templateHash
|| previous.bindingHash !== next.bindingHash
|| previous.reactiveHash !== next.reactiveHash
) return "remount-preserve-state";
if (previous.actionHash !== next.actionHash) return "actions";
if (previous.styleHash !== next.styleHash) return "style";
return "none";
}
function snapshotState() {
restoredState.clear();
for (const instance of activeInstances()) {
for (const [semanticId, source] of Object.entries(instance.state ?? {})) {
const values = restoredState.get(semanticId) ?? [];
values.push(source.get());
restoredState.set(semanticId, values);
}
}
}
function refreshStyles(revision) {
for (const link of document.querySelectorAll("link[data-noxid-route-style]")) {
const url = new URL(link.href, location.href);
url.searchParams.set("noxid-hmr", String(revision));
const replacement = link.cloneNode();
replacement.href = url.href;
replacement.addEventListener("load", () => link.remove(), { once: true });
link.after(replacement);
}
}
function renderDiagnostics(values) {
document.querySelector("[data-noxid-diagnostics]")?.remove();
if (!values?.length) return;
const panel = document.createElement("section");
panel.dataset.noxidDiagnostics = "";
panel.setAttribute("role", "alert");
panel.style.cssText = "position:fixed;inset:auto 1rem 1rem 1rem;z-index:2147483647;max-height:45vh;overflow:auto;padding:1rem;border:1px solid #ef4444;border-radius:.5rem;background:#1c1010;color:#fee2e2;font:13px/1.45 ui-monospace,monospace;white-space:pre-wrap;box-shadow:0 1rem 3rem #0008";
const heading = document.createElement("strong");
heading.textContent = "Noxid compiler diagnostics";
panel.appendChild(heading);
for (const diagnostic of values) {
const item = document.createElement("pre");
item.textContent = `${diagnostic.code}: ${diagnostic.message}`;
panel.appendChild(item);
}
document.body.appendChild(panel);
}
async function flush() {
flushTimer = null;
const changes = [...pending.values()];
pending.clear();
const revision = ++updateSequence;
const kinds = new Set(changes.map((change) => change.kind));
if (kinds.has("reload")) return location.reload();
if (kinds.has("remount-reset-state") || kinds.has("remount-preserve-state")) {
if (kinds.has("remount-preserve-state") && !kinds.has("remount-reset-state")) snapshotState();
else restoredState.clear();
const refreshed = await globalThis.__NOXID_APP__?.router?.refresh?.(revision);
if (!refreshed) location.reload();
return;
}
for (const change of changes) {
if (change.kind === "actions") {
for (const instance of activeInstances(change.component)) {
instance.patchActions?.(change.nextModule);
}
}
}
if (kinds.has("style")) refreshStyles(revision);
}
const api = Object.freeze({
registerModule(component, signature) {
modules.set(component, signature);
},
registerInstance(instance) {
instances.add(instance);
},
initialState(semanticId, fallback) {
const values = restoredState.get(semanticId);
if (!values?.length) return fallback;
const value = values.shift();
if (values.length === 0) restoredState.delete(semanticId);
return value;
},
acceptComponent(component, nextModule, nextSignature) {
const previous = modules.get(component);
modules.set(component, nextSignature);
pending.set(component, {
component,
nextModule,
kind: classify(previous, nextSignature),
});
if (flushTimer === null) flushTimer = setTimeout(() => void flush(), 25);
},
inspect() {
return Object.freeze({
modules: Object.freeze(Object.fromEntries(modules)),
activeInstances: activeInstances().length,
pending: pending.size,
});
},
});
globalThis.__NOXID_HMR__ = api;
renderDiagnostics(initialDiagnostics);
if (import.meta.hot) {
import.meta.hot.accept("./noxid-diagnostics.js", (nextModule) => {
renderDiagnostics(nextModule?.diagnostics ?? []);
});
}