Send content expression component deps in SX responses for client routing
When a page has a content expression but no data dependency, compute its transitive component deps and pass them as extra_component_names to sx_response(). This ensures the client has all component definitions needed for future client-side route rendering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -456,7 +456,8 @@ def sx_call(component_name: str, **kwargs: Any) -> str:
|
||||
|
||||
|
||||
|
||||
def components_for_request(source: str = "") -> str:
|
||||
def components_for_request(source: str = "",
|
||||
extra_names: set[str] | None = None) -> str:
|
||||
"""Return defcomp/defmacro source for definitions the client doesn't have yet.
|
||||
|
||||
Reads the ``SX-Components`` header (comma-separated component names
|
||||
@@ -464,6 +465,10 @@ def components_for_request(source: str = "") -> str:
|
||||
is missing. If *source* is provided, only sends components needed
|
||||
for that source (plus transitive deps). If the header is absent,
|
||||
returns all needed defs.
|
||||
|
||||
*extra_names* — additional component names (``~foo``) to include
|
||||
beyond what *source* references. Used by ``execute_page`` to send
|
||||
components the page's content expression needs for client-side routing.
|
||||
"""
|
||||
from quart import request
|
||||
from .jinja_bridge import _COMPONENT_ENV
|
||||
@@ -477,6 +482,12 @@ def components_for_request(source: str = "") -> str:
|
||||
else:
|
||||
needed = None # all
|
||||
|
||||
# Merge in extra names (e.g. from page content expression deps)
|
||||
if extra_names and needed is not None:
|
||||
needed = needed | extra_names
|
||||
elif extra_names:
|
||||
needed = extra_names
|
||||
|
||||
loaded_raw = request.headers.get("SX-Components", "")
|
||||
loaded = set(loaded_raw.split(",")) if loaded_raw else set()
|
||||
|
||||
@@ -510,7 +521,8 @@ def components_for_request(source: str = "") -> str:
|
||||
|
||||
|
||||
def sx_response(source: str, status: int = 200,
|
||||
headers: dict | None = None):
|
||||
headers: dict | None = None,
|
||||
extra_component_names: set[str] | None = None):
|
||||
"""Return an s-expression wire-format response.
|
||||
|
||||
Takes a raw sx string::
|
||||
@@ -520,6 +532,10 @@ def sx_response(source: str, status: int = 200,
|
||||
For SX requests, missing component definitions are prepended as a
|
||||
``<script type="text/sx" data-components>`` block so the client
|
||||
can process them before rendering OOB content.
|
||||
|
||||
*extra_component_names* — additional component names to include beyond
|
||||
what *source* references. Used by defpage to send components the page's
|
||||
content expression needs for client-side routing.
|
||||
"""
|
||||
from quart import request, Response
|
||||
|
||||
@@ -535,7 +551,7 @@ def sx_response(source: str, status: int = 200,
|
||||
# For SX requests, prepend missing component definitions
|
||||
comp_defs = ""
|
||||
if request.headers.get("SX-Request"):
|
||||
comp_defs = components_for_request(source)
|
||||
comp_defs = components_for_request(source, extra_names=extra_component_names)
|
||||
if comp_defs:
|
||||
body = (f'<script type="text/sx" data-components>'
|
||||
f'{comp_defs}</script>\n{body}')
|
||||
|
||||
@@ -279,13 +279,25 @@ async def execute_page(
|
||||
is_htmx = is_htmx_request()
|
||||
|
||||
if is_htmx:
|
||||
# Compute content expression deps so the server sends component
|
||||
# definitions the client needs for future client-side routing
|
||||
extra_deps: set[str] | None = None
|
||||
if page_def.content_expr is not None and page_def.data_expr is None:
|
||||
from .deps import components_needed
|
||||
from .parser import serialize
|
||||
try:
|
||||
content_src = serialize(page_def.content_expr)
|
||||
extra_deps = components_needed(content_src, get_component_env())
|
||||
except Exception:
|
||||
pass # non-critical — client will just fall back to server
|
||||
|
||||
return sx_response(await oob_page_sx(
|
||||
oobs=oob_headers if oob_headers else "",
|
||||
filter=filter_sx,
|
||||
aside=aside_sx,
|
||||
content=content_sx,
|
||||
menu=menu_sx,
|
||||
))
|
||||
), extra_component_names=extra_deps)
|
||||
else:
|
||||
return await full_page_sx(
|
||||
tctx,
|
||||
|
||||
Reference in New Issue
Block a user