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.
Files changed (51) hide show
  1. tw_framework/__init__.py +13 -0
  2. tw_framework/advanced_diagnostics.py +25 -0
  3. tw_framework/asset_optimizer.py +30 -0
  4. tw_framework/ast_nodes.py +283 -0
  5. tw_framework/build.py +7 -0
  6. tw_framework/build_performance.py +26 -0
  7. tw_framework/build_report.py +84 -0
  8. tw_framework/cli.py +1258 -0
  9. tw_framework/code_splitting.py +76 -0
  10. tw_framework/common.py +22 -0
  11. tw_framework/compiler.py +4817 -0
  12. tw_framework/compiler_stats.py +51 -0
  13. tw_framework/dead_code.py +44 -0
  14. tw_framework/dependency_graph.py +91 -0
  15. tw_framework/deploy.py +7 -0
  16. tw_framework/dev.py +9 -0
  17. tw_framework/diagnostics.py +98 -0
  18. tw_framework/dynamic_imports.py +51 -0
  19. tw_framework/error_formatter.py +53 -0
  20. tw_framework/framework.py +3216 -0
  21. tw_framework/hydration.py +129 -0
  22. tw_framework/incremental_cache.py +53 -0
  23. tw_framework/interpreter.py +42 -0
  24. tw_framework/ir.py +171 -0
  25. tw_framework/lexer.py +52 -0
  26. tw_framework/lowering.py +68 -0
  27. tw_framework/parser.py +167 -0
  28. tw_framework/partial_rebuild.py +24 -0
  29. tw_framework/performance_analyzer.py +37 -0
  30. tw_framework/plugin_runtime.py +196 -0
  31. tw_framework/production_optimizer.py +82 -0
  32. tw_framework/reactivity.py +349 -0
  33. tw_framework/render_css.py +22 -0
  34. tw_framework/render_html.py +259 -0
  35. tw_framework/route_optimizer.py +40 -0
  36. tw_framework/router.py +7 -0
  37. tw_framework/runtime_model.py +23 -0
  38. tw_framework/runtime_values.py +31 -0
  39. tw_framework/semantic.py +162 -0
  40. tw_framework/server.py +532 -0
  41. tw_framework/signature.py +51 -0
  42. tw_framework/static_dynamic_auto.py +32 -0
  43. tw_framework/streaming.py +135 -0
  44. tw_framework/test_build_cli_pytest.py +1078 -0
  45. tw_framework/tree_shaking.py +48 -0
  46. tw_framework/twm_parser.py +274 -0
  47. tw_framework-0.2.3.dist-info/METADATA +8 -0
  48. tw_framework-0.2.3.dist-info/RECORD +51 -0
  49. tw_framework-0.2.3.dist-info/WHEEL +5 -0
  50. tw_framework-0.2.3.dist-info/entry_points.txt +2 -0
  51. 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"]