vorma 0.86.0-pre.3

Vorma framework.
Documentation
const SCROLL_Y_KEY = "vorma-dev-refresh-scroll-y";
const SYMBOLS = {
	data_revalidate_fn: Symbol.for("vorma-data-revalidate-fn"),
};
const EL_IDS = {
	critical_css: "vorma-critical-css",
	rebuilding_overlay: "vorma-rebuilding-overlay",
};
const REFRESH_PAYLOAD_KEY = {
	change_type: "change_type",
	critical_css: "critical_css",
	build_error: "build_error",
};
const CHANGE_TYPE = {
	show_rebuilding_overlay: "show_rebuilding_overlay",
	hide_rebuilding_overlay: "hide_rebuilding_overlay",
	show_build_error: "show_build_error",
	hard_reload: "hard_reload",
	update_critical_css: "update_critical_css",
	revalidate_client: "revalidate_client",
};
const DEV_REFRESH_EVENTS_PATH_PREFIX = "/vorma-dev-refresh-";

const vorma_msg_prefix = "[vorma]:";
const log_info = (...args) => {
	return console.info(vorma_msg_prefix, ...args);
};
const log_err = (...args) => {
	return console.error(vorma_msg_prefix, ...args);
};

function missing_el_err(el_id) {
	return new Error(`${vorma_msg_prefix} expected element with id '#${el_id}'`);
}

const scroll_y_val = sessionStorage.getItem(SCROLL_Y_KEY);
if (scroll_y_val) {
	setTimeout(() => {
		sessionStorage.removeItem(SCROLL_Y_KEY);
		log_info("Restoring scroll position");
		window.scrollTo({ top: scroll_y_val, behavior: "smooth" });
	}, 150);
}

const refresh_ws_url = new URL(window.location.href);
refresh_ws_url.protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
refresh_ws_url.port = "__REPLACE_ME_WITH_REFRESH_PORT__";
refresh_ws_url.pathname =
	DEV_REFRESH_EVENTS_PATH_PREFIX + "__REPLACE_ME_WITH_REFRESH_TOKEN__";
refresh_ws_url.search = "";
refresh_ws_url.hash = "";

const ws = new WebSocket(refresh_ws_url.toString());
let is_data_revalidating = false;

function upsert_rebuilding_overlay(inner_html, background_color) {
	let el = document.getElementById(EL_IDS.rebuilding_overlay);
	if (!el) {
		el = document.createElement("div");
		el.id = EL_IDS.rebuilding_overlay;
		el.style.display = "flex";
		el.style.position = "fixed";
		el.style.inset = "0";
		el.style.width = "100%";
		el.style.color = "white";
		el.style.textAlign = "center";
		el.style.padding = "10px";
		el.style.zIndex = "1000";
		el.style.fontFamily = "monospace";
		el.style.fontSize = "7vw";
		el.style.fontWeight = "bold";
		el.style.textShadow = "2px 2px 2px #000";
		el.style.justifyContent = "center";
		el.style.alignItems = "center";
		el.style.opacity = "0";
		el.style.transition = "opacity 0.05s";
		document.body.appendChild(el);
		setTimeout(() => {
			el.style.opacity = "1";
		}, 12);
	}
	el.innerHTML = inner_html;
	el.style.backgroundColor = background_color;
	return el;
}

function hide_rebuilding_overlay() {
	if (is_data_revalidating) {
		return;
	}
	document.getElementById(EL_IDS.rebuilding_overlay)?.remove();
}

ws.onmessage = (e) => {
	const parsed = JSON.parse(e.data);
	const change_type = parsed[REFRESH_PAYLOAD_KEY.change_type];
	const critical_css = parsed[REFRESH_PAYLOAD_KEY.critical_css];
	const build_error = parsed[REFRESH_PAYLOAD_KEY.build_error];

	if (change_type === CHANGE_TYPE.show_rebuilding_overlay) {
		log_info("Rebuilding");
		upsert_rebuilding_overlay("Rebuilding...", "#333a");
	}

	if (change_type === CHANGE_TYPE.hide_rebuilding_overlay) {
		hide_rebuilding_overlay();
	}

	if (change_type === CHANGE_TYPE.show_build_error) {
		log_err("Build error", build_error);
		upsert_rebuilding_overlay("Build error", "#700e");
	}

	if (change_type === CHANGE_TYPE.hard_reload) {
		const y = window.scrollY;
		if (y > 0) {
			sessionStorage.setItem(SCROLL_Y_KEY, y);
		}
		window.location.reload();
	}

	if (change_type === CHANGE_TYPE.update_critical_css) {
		const _old = document.getElementById(EL_IDS.critical_css);
		if (!_old) {
			throw missing_el_err(EL_IDS.critical_css);
		}
		const _new = document.createElement("style");
		_new.id = EL_IDS.critical_css;
		_new.innerHTML = critical_css;
		document.head.replaceChild(_new, _old);
	}

	if (change_type === CHANGE_TYPE.revalidate_client) {
		is_data_revalidating = true;
		if (window[SYMBOLS.data_revalidate_fn]) {
			log_info("Revalidating data");
			Promise.resolve()
				.then(() => {
					return window[SYMBOLS.data_revalidate_fn]();
				})
				.then(() => {
					log_info("Data revalidation complete");
				})
				.catch((err) => {
					log_err("Data revalidation failed", err);
				})
				.finally(() => {
					is_data_revalidating = false;
					hide_rebuilding_overlay();
				});
		} else {
			is_data_revalidating = false;
			log_err("Data revalidation function not found");
			hide_rebuilding_overlay();
		}
	}
};

ws.onclose = () => {
	log_info("WebSocket closed (will reload page)");
	window.location.reload();
};

ws.onerror = (e) => {
	log_err("WebSocket error (will reload page)", e);
	ws.onclose = () => {};
	ws.close();
	window.location.reload();
};

window.addEventListener("beforeunload", () => {
	ws.onclose = () => {};
	ws.close();
});