Add on-demand CSS: registry, pre-computed component classes, header compression

- 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

@@ -24,11 +24,18 @@ rendered as HTML::
from __future__ import annotations
import contextvars
from typing import Any
from .types import Component, Keyword, Lambda, NIL, Symbol
from .evaluator import _eval, _call_component
# ContextVar for collecting CSS class names during render.
# Set to a set[str] to collect; None to skip.
css_class_collector: contextvars.ContextVar[set[str] | None] = contextvars.ContextVar(
"css_class_collector", default=None
)
class _RawHTML:
"""Marker for pre-rendered HTML that should not be escaped."""
@@ -455,6 +462,13 @@ def _render_element(tag: str, args: list, env: dict[str, Any]) -> str:
children.append(arg)
i += 1
# Collect CSS classes if collector is active
class_val = attrs.get("class")
if class_val is not None and class_val is not NIL and class_val is not False:
collector = css_class_collector.get(None)
if collector is not None:
collector.update(str(class_val).split())
# Build opening tag
parts = [f"<{tag}"]
for attr_name, attr_val in attrs.items():