coolstyleserver 2.9.0

a proxy server for stylesheet hot reloading
let base = new URL(import.meta.url);
let coolBase = base.pathname.substring(0, base.pathname.lastIndexOf("/"));
let esrc = new EventSource(`${coolBase}/watch`);

class CoolStylesheet extends HTMLLinkElement {
	static get observedAttributes() {
		return ["media"];
	}

	sources = new Set();
	pathname;
	stylesheet;

	async update() {
		let url = new URL(`${coolBase}/fetch`, base);

		url.searchParams.append("pathname", this.pathname);

		let res = await fetch(url);
		let json = await res.json();

		if (json.css.includes("@import")) {
			return;
		}

		this.sources = new Set(json.sources);

		this.stylesheet.replaceSync(json.css);
	}

	connectedCallback() {
		let url = new URL(this.href);

		if (url.host !== new URL(import.meta.url).host) return;

		this.pathname = url.pathname;

		esrc.addEventListener("message", async (event) => {
			let data = JSON.parse(event.data);

			for (let pathname of data) {
				pathname = new URL(pathname, base).pathname;

				if (pathname !== this.pathname && !this.sources.has(pathname)) continue;

				this.update();
			}
		});

		let media = this.getAttribute("media") ?? "all";

		this.stylesheet = new CSSStyleSheet({media: media});

		let root = this.getRootNode();

		root.adoptedStyleSheets.splice(
			root.adoptedStyleSheets.length,
			1,
			this.stylesheet
		);

		this.update().then(() => {
			this.disabled = true;
		});
	}

	async attributeChangedCallback(_, old_media, new_media) {
		if (old_media === new_media) {
			return;
		}

		this.stylesheet.media = new_media;
	}

	disconnectedCallback() {
		let root = this.getRootNode();
		let index = root.adoptedStyleSheets.lastIndexOf(this.stylesheet);

		root.adoptedStyleSheets.splice(index, 1);
	}
}

customElements.define("cool-stylesheet", CoolStylesheet, {extends: "link"});