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:
@@ -740,8 +740,9 @@ def sx_page(ctx: dict, page_sx: str, *,
|
|||||||
from .jinja_bridge import components_for_page, css_classes_for_page
|
from .jinja_bridge import components_for_page, css_classes_for_page
|
||||||
from .css_registry import lookup_rules, get_preamble, registry_loaded, store_css_hash
|
from .css_registry import lookup_rules, get_preamble, registry_loaded, store_css_hash
|
||||||
|
|
||||||
# Per-page component bundle: only definitions this page needs
|
# Per-page component bundle: this page's deps + all :data page deps
|
||||||
component_defs, component_hash = components_for_page(page_sx)
|
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)
|
# Check if client already has this version cached (via cookie)
|
||||||
# In dev mode, always send full source so edits are visible immediately
|
# 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_classes = ""
|
||||||
sx_css_hash = ""
|
sx_css_hash = ""
|
||||||
if registry_loaded():
|
if registry_loaded():
|
||||||
classes = css_classes_for_page(page_sx)
|
classes = css_classes_for_page(page_sx, service=_ca.name)
|
||||||
# Always include body classes
|
# Always include body classes
|
||||||
classes.update(["bg-stone-50", "text-stone-900"])
|
classes.update(["bg-stone-50", "text-stone-900"])
|
||||||
rules = lookup_rules(classes)
|
rules = lookup_rules(classes)
|
||||||
|
|||||||
@@ -332,17 +332,32 @@ def client_components_tag(*names: str) -> str:
|
|||||||
return f'<script type="text/sx" data-components>{source}</script>'
|
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.
|
"""Return (component_defs_source, page_hash) for a page.
|
||||||
|
|
||||||
Scans *page_sx* for component references, computes the transitive
|
Scans *page_sx* for component references, computes the transitive
|
||||||
closure, and returns only the definitions needed for this page.
|
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.
|
The hash is computed from the page-specific bundle for caching.
|
||||||
"""
|
"""
|
||||||
from .deps import components_needed
|
from .deps import components_needed
|
||||||
from .parser import serialize
|
from .parser import serialize
|
||||||
|
|
||||||
needed = components_needed(page_sx, _COMPONENT_ENV)
|
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:
|
if not needed:
|
||||||
return "", ""
|
return "", ""
|
||||||
|
|
||||||
@@ -375,16 +390,24 @@ def components_for_page(page_sx: str) -> tuple[str, str]:
|
|||||||
return source, digest
|
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.
|
"""Return CSS classes needed for a page's component bundle + page source.
|
||||||
|
|
||||||
Instead of unioning ALL component CSS classes, only includes classes
|
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 .deps import components_needed
|
||||||
from .css_registry import scan_classes_from_sx
|
from .css_registry import scan_classes_from_sx
|
||||||
|
from .parser import serialize
|
||||||
|
|
||||||
needed = components_needed(page_sx, _COMPONENT_ENV)
|
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()
|
classes: set[str] = set()
|
||||||
|
|
||||||
for key, val in _COMPONENT_ENV.items():
|
for key, val in _COMPONENT_ENV.items():
|
||||||
|
|||||||
Reference in New Issue
Block a user