Phase 2: IO detection & selective expansion in deps.sx
Extend the spec with IO scanning functions (scan-io-refs, transitive-io-refs, compute-all-io-refs, component-pure?) that detect IO primitive references in component ASTs. Components are classified as pure (no IO deps, safe for client rendering) or IO-dependent (must expand server-side). The partial evaluator (_aser) now uses per-component IO metadata instead of the global _expand_components toggle: IO-dependent components expand server- side, pure components serialize for client. Layout slot context still expands all components for backwards compat. Spec: 5 new functions + 2 platform interface additions in deps.sx Host: io_refs field + is_pure property on Component, compute_all_io_refs() Bootstrap: both sx_ref.py and sx-ref.js updated with IO functions Bundle analyzer: shows pure/IO classification per page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,62 @@ def _compute_all_deps_fallback(env: dict[str, Any]) -> None:
|
||||
val.deps = _transitive_deps_fallback(key, env)
|
||||
|
||||
|
||||
def _scan_io_refs_fallback(node: Any, io_names: set[str]) -> set[str]:
|
||||
"""Scan an AST node for references to IO primitive names."""
|
||||
refs: set[str] = set()
|
||||
_walk_io(node, io_names, refs)
|
||||
return refs
|
||||
|
||||
|
||||
def _walk_io(node: Any, io_names: set[str], refs: set[str]) -> None:
|
||||
if isinstance(node, Symbol):
|
||||
if node.name in io_names:
|
||||
refs.add(node.name)
|
||||
return
|
||||
if isinstance(node, list):
|
||||
for item in node:
|
||||
_walk_io(item, io_names, refs)
|
||||
return
|
||||
if isinstance(node, dict):
|
||||
for v in node.values():
|
||||
_walk_io(v, io_names, refs)
|
||||
return
|
||||
|
||||
|
||||
def _transitive_io_refs_fallback(
|
||||
name: str, env: dict[str, Any], io_names: set[str]
|
||||
) -> set[str]:
|
||||
"""Compute transitive IO primitive references for a component."""
|
||||
all_refs: set[str] = set()
|
||||
seen: set[str] = set()
|
||||
|
||||
def walk(n: str) -> None:
|
||||
if n in seen:
|
||||
return
|
||||
seen.add(n)
|
||||
val = env.get(n)
|
||||
if isinstance(val, Component):
|
||||
all_refs.update(_scan_io_refs_fallback(val.body, io_names))
|
||||
for dep in _scan_ast(val.body):
|
||||
walk(dep)
|
||||
elif isinstance(val, Macro):
|
||||
all_refs.update(_scan_io_refs_fallback(val.body, io_names))
|
||||
for dep in _scan_ast(val.body):
|
||||
walk(dep)
|
||||
|
||||
key = name if name.startswith("~") else f"~{name}"
|
||||
walk(key)
|
||||
return all_refs
|
||||
|
||||
|
||||
def _compute_all_io_refs_fallback(
|
||||
env: dict[str, Any], io_names: set[str]
|
||||
) -> None:
|
||||
for key, val in env.items():
|
||||
if isinstance(val, Component):
|
||||
val.io_refs = _transitive_io_refs_fallback(key, env, io_names)
|
||||
|
||||
|
||||
def _scan_components_from_sx_fallback(source: str) -> set[str]:
|
||||
import re
|
||||
return {f"~{m}" for m in re.findall(r'\(~([a-zA-Z_][a-zA-Z0-9_\-]*)', source)}
|
||||
@@ -131,3 +187,27 @@ def components_needed(page_sx: str, env: dict[str, Any]) -> set[str]:
|
||||
from .ref.sx_ref import components_needed as _ref_cn
|
||||
return set(_ref_cn(page_sx, env))
|
||||
return _components_needed_fallback(page_sx, env)
|
||||
|
||||
|
||||
def compute_all_io_refs(env: dict[str, Any], io_names: set[str]) -> None:
|
||||
"""Compute and cache transitive IO refs for all Component entries in *env*."""
|
||||
if _use_ref():
|
||||
from .ref.sx_ref import compute_all_io_refs as _ref_cio
|
||||
_ref_cio(env, list(io_names))
|
||||
return
|
||||
_compute_all_io_refs_fallback(env, io_names)
|
||||
|
||||
|
||||
def get_all_io_names() -> set[str]:
|
||||
"""Build the complete set of IO primitive names from all boundary tiers.
|
||||
|
||||
Includes: core IO (primitives_io.py handlers), plus all page helper names
|
||||
from every service boundary.
|
||||
"""
|
||||
from .primitives_io import IO_PRIMITIVES
|
||||
from .boundary import declared_helpers
|
||||
|
||||
names = set(IO_PRIMITIVES)
|
||||
for _svc, helper_names in declared_helpers().items():
|
||||
names.update(helper_names)
|
||||
return names
|
||||
|
||||
Reference in New Issue
Block a user