Include all :data page component deps in every page's client bundle

Per-page bundling now unions deps from all :data pages in the service,
so navigating between data pages uses client-side rendering + cache
instead of expensive server fetch + SX parse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 01:26:39 +00:00
parent fa295acfe3
commit 2c56d3e14b
2 changed files with 30 additions and 6 deletions

View File

@@ -740,8 +740,9 @@ def sx_page(ctx: dict, page_sx: str, *,
from .jinja_bridge import components_for_page, css_classes_for_page
from .css_registry import lookup_rules, get_preamble, registry_loaded, store_css_hash
# Per-page component bundle: only definitions this page needs
component_defs, component_hash = components_for_page(page_sx)
# Per-page component bundle: this page's deps + all :data page deps
from quart import current_app as _ca
component_defs, component_hash = components_for_page(page_sx, service=_ca.name)
# Check if client already has this version cached (via cookie)
# In dev mode, always send full source so edits are visible immediately
@@ -755,7 +756,7 @@ def sx_page(ctx: dict, page_sx: str, *,
sx_css_classes = ""
sx_css_hash = ""
if registry_loaded():
classes = css_classes_for_page(page_sx)
classes = css_classes_for_page(page_sx, service=_ca.name)
# Always include body classes
classes.update(["bg-stone-50", "text-stone-900"])
rules = lookup_rules(classes)

View File

@@ -332,17 +332,32 @@ def client_components_tag(*names: str) -> str:
return f'<script type="text/sx" data-components>{source}</script>'
def components_for_page(page_sx: str) -> tuple[str, str]:
def components_for_page(page_sx: str, service: str | None = None) -> tuple[str, str]:
"""Return (component_defs_source, page_hash) for a page.
Scans *page_sx* for component references, computes the transitive
closure, and returns only the definitions needed for this page.
When *service* is given, also includes deps for all :data pages
in that service so the client can render them without a server
roundtrip on navigation.
The hash is computed from the page-specific bundle for caching.
"""
from .deps import components_needed
from .parser import serialize
needed = components_needed(page_sx, _COMPONENT_ENV)
# Include deps for all :data pages so the client can render them
if service:
from .pages import get_all_pages
for page_def in get_all_pages(service).values():
if page_def.data_expr is not None and page_def.content_expr is not None:
content_src = serialize(page_def.content_expr)
data_deps = components_needed(content_src, _COMPONENT_ENV)
needed |= data_deps
if not needed:
return "", ""
@@ -375,16 +390,24 @@ def components_for_page(page_sx: str) -> tuple[str, str]:
return source, digest
def css_classes_for_page(page_sx: str) -> set[str]:
def css_classes_for_page(page_sx: str, service: str | None = None) -> set[str]:
"""Return CSS classes needed for a page's component bundle + page source.
Instead of unioning ALL component CSS classes, only includes classes
from components the page actually uses.
from components the page actually uses (plus all :data page deps).
"""
from .deps import components_needed
from .css_registry import scan_classes_from_sx
from .parser import serialize
needed = components_needed(page_sx, _COMPONENT_ENV)
if service:
from .pages import get_all_pages
for page_def in get_all_pages(service).values():
if page_def.data_expr is not None and page_def.content_expr is not None:
content_src = serialize(page_def.content_expr)
needed |= components_needed(content_src, _COMPONENT_ENV)
classes: set[str] = set()
for key, val in _COMPONENT_ENV.items():