Add on-demand CSS: registry, pre-computed component classes, header compression
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 42s

- Parse tw.css into per-class lookup registry at startup
- Pre-scan component CSS classes at registration time (avoid per-request regex)
- Compress SX-Css header: 8-char hash replaces full class list (LRU cache)
- Add ;@css comment annotation for dynamically constructed class names
- Safelist bg-sky-{100..400} in Tailwind config for menu-row-sx dynamic shades
- Client sends/receives hash, falls back gracefully on cache miss

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 21:39:57 +00:00
parent ab45e21c7c
commit 1447122a0c
12 changed files with 603 additions and 31 deletions

View File

@@ -1520,6 +1520,10 @@
});
if (loadedNames.length) headers["SX-Components"] = loadedNames.join(",");
// Send known CSS classes so server only sends new rules
var cssHeader = _getSxCssHeader();
if (cssHeader) headers["SX-Css"] = cssHeader;
// Extra headers from sx-headers
var extraH = el.getAttribute("sx-headers");
if (extraH) {
@@ -1647,6 +1651,8 @@
// Strip and load any <script type="text/sx" data-components> blocks
text = text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
function (_, defs) { Sx.loadComponents(defs); return ""; });
// Process on-demand CSS: extract <style data-sx-css> and inject into head
text = _processCssResponse(text, resp);
var sxSource = text.trim();
// Parse and render to live DOM nodes (skip renderToString + DOMParser)
@@ -2150,9 +2156,12 @@
var main = document.getElementById("main-panel");
if (!main) { location.reload(); return; }
var histOpts = {
headers: { "SX-Request": "true", "SX-History-Restore": "true" }
};
var histHeaders = { "SX-Request": "true", "SX-History-Restore": "true" };
var cssH = _getSxCssHeader();
if (cssH) histHeaders["SX-Css"] = cssH;
var loadedN = Object.keys(_componentEnv).filter(function (k) { return k.charAt(0) === "~"; });
if (loadedN.length) histHeaders["SX-Components"] = loadedN.join(",");
var histOpts = { headers: histHeaders };
try {
var hHost = new URL(url, location.href).hostname;
if (hHost !== location.hostname &&
@@ -2162,11 +2171,15 @@
} catch (e) {}
fetch(url, histOpts).then(function (resp) {
return resp.text();
}).then(function (text) {
return resp.text().then(function (t) { return { text: t, resp: resp }; });
}).then(function (r) {
var text = r.text;
var resp = r.resp;
// Strip and load any <script type="text/sx" data-components> blocks
text = text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
function (_, defs) { Sx.loadComponents(defs); return ""; });
// Process on-demand CSS
text = _processCssResponse(text, resp);
text = text.trim();
if (text.charAt(0) === "(") {
@@ -2279,11 +2292,64 @@
// Auto-init in browser
// =========================================================================
Sx.VERSION = "2026-03-01b-debug";
Sx.VERSION = "2026-03-01c-cssx";
// CSS class tracking for on-demand CSS delivery
var _sxCssKnown = {};
var _sxCssHash = ""; // 8-char hex hash from server
function _initCssTracking() {
var meta = document.querySelector('meta[name="sx-css-classes"]');
if (meta) {
var content = meta.getAttribute("content");
if (content) {
// If content is short (≤16 chars), it's a hash from the server
if (content.length <= 16) {
_sxCssHash = content;
} else {
content.split(",").forEach(function (c) {
if (c) _sxCssKnown[c] = true;
});
}
}
}
}
function _getSxCssHeader() {
// Prefer sending the hash (compact) over the full class list
if (_sxCssHash) return _sxCssHash;
var names = Object.keys(_sxCssKnown);
return names.length ? names.join(",") : "";
}
function _processCssResponse(text, resp) {
// Read SX-Css-Hash response header — replaces local hash
var hashHeader = resp.headers.get("SX-Css-Hash");
if (hashHeader) _sxCssHash = hashHeader;
// Merge SX-Css-Add header into known set (kept for debugging/fallback)
var addHeader = resp.headers.get("SX-Css-Add");
if (addHeader) {
addHeader.split(",").forEach(function (c) {
if (c) _sxCssKnown[c] = true;
});
}
// Extract <style data-sx-css>...</style> blocks and inject into <style id="sx-css">
var cssTarget = document.getElementById("sx-css");
if (cssTarget) {
text = text.replace(/<style[^>]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi,
function (_, css) {
cssTarget.textContent += css;
return "";
});
}
return text;
}
if (typeof document !== "undefined") {
var init = function () {
console.log("[sx.js] v" + Sx.VERSION + " init");
_initCssTracking();
Sx.processScripts();
Sx.hydrate();
SxEngine.process();