tw-framework 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tw_framework/__init__.py +13 -0
- tw_framework/advanced_diagnostics.py +25 -0
- tw_framework/asset_optimizer.py +30 -0
- tw_framework/ast_nodes.py +283 -0
- tw_framework/build.py +7 -0
- tw_framework/build_performance.py +26 -0
- tw_framework/build_report.py +84 -0
- tw_framework/cli.py +1258 -0
- tw_framework/code_splitting.py +76 -0
- tw_framework/common.py +22 -0
- tw_framework/compiler.py +4817 -0
- tw_framework/compiler_stats.py +51 -0
- tw_framework/dead_code.py +44 -0
- tw_framework/dependency_graph.py +91 -0
- tw_framework/deploy.py +7 -0
- tw_framework/dev.py +9 -0
- tw_framework/diagnostics.py +98 -0
- tw_framework/dynamic_imports.py +51 -0
- tw_framework/error_formatter.py +53 -0
- tw_framework/framework.py +3216 -0
- tw_framework/hydration.py +129 -0
- tw_framework/incremental_cache.py +53 -0
- tw_framework/interpreter.py +42 -0
- tw_framework/ir.py +171 -0
- tw_framework/lexer.py +52 -0
- tw_framework/lowering.py +68 -0
- tw_framework/parser.py +167 -0
- tw_framework/partial_rebuild.py +24 -0
- tw_framework/performance_analyzer.py +37 -0
- tw_framework/plugin_runtime.py +196 -0
- tw_framework/production_optimizer.py +82 -0
- tw_framework/reactivity.py +349 -0
- tw_framework/render_css.py +22 -0
- tw_framework/render_html.py +259 -0
- tw_framework/route_optimizer.py +40 -0
- tw_framework/router.py +7 -0
- tw_framework/runtime_model.py +23 -0
- tw_framework/runtime_values.py +31 -0
- tw_framework/semantic.py +162 -0
- tw_framework/server.py +532 -0
- tw_framework/signature.py +51 -0
- tw_framework/static_dynamic_auto.py +32 -0
- tw_framework/streaming.py +135 -0
- tw_framework/test_build_cli_pytest.py +1078 -0
- tw_framework/tree_shaking.py +48 -0
- tw_framework/twm_parser.py +274 -0
- tw_framework-0.2.3.dist-info/METADATA +8 -0
- tw_framework-0.2.3.dist-info/RECORD +51 -0
- tw_framework-0.2.3.dist-info/WHEEL +5 -0
- tw_framework-0.2.3.dist-info/entry_points.txt +2 -0
- tw_framework-0.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Streaming SSR support for TW Framework.
|
|
3
|
+
|
|
4
|
+
Provides a generator that yields HTML chunks as they become available,
|
|
5
|
+
enabling progressive rendering and faster Time-to-First-Byte.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import html
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Dict, Generator, List, Optional
|
|
11
|
+
|
|
12
|
+
from .ir import IRComponent, IRElement, IRFor, IRIf, IRLet, IRProgram, IRScript, IRText
|
|
13
|
+
from .runtime_values import RuntimeEnvironment
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _interpolate(value, context):
|
|
19
|
+
from . import compiler
|
|
20
|
+
if value is None:
|
|
21
|
+
return ""
|
|
22
|
+
if not isinstance(value, str):
|
|
23
|
+
return value
|
|
24
|
+
return compiler.interpolate(value, context)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _evaluate(expr: str, context):
|
|
28
|
+
from . import compiler
|
|
29
|
+
return compiler.evaluate_expression(expr, context)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def render_node_streaming(node, env: RuntimeEnvironment) -> Generator[str, None, None]:
|
|
33
|
+
"""Yield HTML chunks for a single IR node."""
|
|
34
|
+
context = env.to_context()
|
|
35
|
+
if isinstance(node, IRText):
|
|
36
|
+
yield html.escape(str(_interpolate(node.value, context)))
|
|
37
|
+
elif isinstance(node, IRLet):
|
|
38
|
+
env.set(node.name, _interpolate(node.value, context))
|
|
39
|
+
elif isinstance(node, IRIf):
|
|
40
|
+
branch = node.children if _evaluate(node.condition, context) else node.else_children
|
|
41
|
+
for child in branch:
|
|
42
|
+
yield from render_node_streaming(child, env.child())
|
|
43
|
+
elif isinstance(node, IRFor):
|
|
44
|
+
items = _evaluate(node.iterable, context)
|
|
45
|
+
for item in items or []:
|
|
46
|
+
loop_env = env.child({node.var_name: item})
|
|
47
|
+
for child in node.children:
|
|
48
|
+
yield from render_node_streaming(child, loop_env)
|
|
49
|
+
elif isinstance(node, IRScript):
|
|
50
|
+
yield f"<script>{node.raw_js}</script>"
|
|
51
|
+
elif isinstance(node, IRComponent):
|
|
52
|
+
props = {prop["name"]: _interpolate(prop["value"], context) for prop in node.props}
|
|
53
|
+
stack = context.get("__tw_component_stack__", ())
|
|
54
|
+
if node.name in stack:
|
|
55
|
+
chain = " -> ".join(stack + (node.name,))
|
|
56
|
+
logger.error("Circular component reference detected: %s", chain)
|
|
57
|
+
yield (
|
|
58
|
+
f'<div data-tw-component-error="circular" data-tw-component="{html.escape(node.name, quote=True)}">'
|
|
59
|
+
f"Circular component reference: {html.escape(chain)}"
|
|
60
|
+
f"</div>"
|
|
61
|
+
)
|
|
62
|
+
return
|
|
63
|
+
from .render_html import _load_component_ir
|
|
64
|
+
component_ir = _load_component_ir(node.name)
|
|
65
|
+
if component_ir is None:
|
|
66
|
+
children_html = "".join(render_node_streaming(child, env.child()) for child in node.children)
|
|
67
|
+
props_html = "".join(
|
|
68
|
+
f'<li><strong>{html.escape(str(key))}</strong>: {html.escape(str(value))}</li>'
|
|
69
|
+
for key, value in props.items()
|
|
70
|
+
)
|
|
71
|
+
logger.warning("Component `%s` could not be resolved; rendering placeholder.", node.name)
|
|
72
|
+
yield (
|
|
73
|
+
f'<div data-tw-component-error="not-found" data-tw-component="{html.escape(node.name, quote=True)}">'
|
|
74
|
+
f"<div>Component not found: {html.escape(node.name)}</div>"
|
|
75
|
+
f"<ul>{props_html}</ul>{children_html}</div>"
|
|
76
|
+
)
|
|
77
|
+
return
|
|
78
|
+
component_env = RuntimeEnvironment(
|
|
79
|
+
values={
|
|
80
|
+
**dict(component_ir.lets),
|
|
81
|
+
**dict(component_ir.state),
|
|
82
|
+
**props,
|
|
83
|
+
"__tw_component_stack__": stack + (node.name,),
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
for child in component_ir.body:
|
|
87
|
+
yield from render_node_streaming(child, component_env)
|
|
88
|
+
elif isinstance(node, IRElement):
|
|
89
|
+
attrs = _render_attrs(node.attrs, env)
|
|
90
|
+
style_attr = _render_style(node.styles, env)
|
|
91
|
+
event_attrs = _render_attrs(node.events, env)
|
|
92
|
+
text = html.escape(str(_interpolate(node.text, context))) if node.text is not None else ""
|
|
93
|
+
children = "".join(render_node_streaming(child, env.child()) for child in node.children)
|
|
94
|
+
if node.tag in {"br", "hr", "img", "input", "meta", "link"}:
|
|
95
|
+
yield f"<{node.tag}{attrs}{style_attr}{event_attrs}>"
|
|
96
|
+
else:
|
|
97
|
+
yield f"<{node.tag}{attrs}{style_attr}{event_attrs}>{text}{children}</{node.tag}>"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _render_attrs(attrs, env):
|
|
101
|
+
parts = []
|
|
102
|
+
context = env.to_context()
|
|
103
|
+
for attr in attrs:
|
|
104
|
+
value = _interpolate(attr["value"], context)
|
|
105
|
+
if value is True:
|
|
106
|
+
parts.append(attr["name"])
|
|
107
|
+
continue
|
|
108
|
+
if value in {False, None}:
|
|
109
|
+
continue
|
|
110
|
+
parts.append(f'{attr["name"]}="{html.escape(str(value), quote=True)}"')
|
|
111
|
+
return f" {' '.join(parts)}" if parts else ""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _render_style(styles, env):
|
|
115
|
+
resolved = []
|
|
116
|
+
context = env.to_context()
|
|
117
|
+
for item in styles:
|
|
118
|
+
value = _interpolate(item["value"], context)
|
|
119
|
+
resolved.append(f'{item["name"]}: {value}')
|
|
120
|
+
return f' style="{html.escape("; ".join(resolved), quote=True)}"' if resolved else ""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def render_program_streaming(program: IRProgram, context: Optional[Dict] = None) -> Generator[str, None, None]:
|
|
124
|
+
"""Yield HTML chunks for the entire program."""
|
|
125
|
+
env = RuntimeEnvironment(values={**dict(program.lets), **dict(program.state), **dict(context or {})})
|
|
126
|
+
yield "<!DOCTYPE html><html><head>"
|
|
127
|
+
title = html.escape(str(program.meta.get("title") or "TW Program"))
|
|
128
|
+
yield f"<title>{title}</title>"
|
|
129
|
+
yield "</head><body>"
|
|
130
|
+
for node in program.body:
|
|
131
|
+
yield from render_node_streaming(node, env)
|
|
132
|
+
yield "</body></html>"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
__all__ = ["render_program_streaming", "render_node_streaming"]
|