Initial commit - some junk

This commit is contained in:
A2IA committed 2025-11-15 08:07:09 +00:00
commit 316c090778
9 files changed
+2272

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
# Ritz Framework
A WebAssembly UI framework with Python-like syntax that compiles to high-performance WASM while remaining executable in Python for debugging.
## Core Innovations
### 1. Python's AST Module for Parsing
No need for a custom parser! Since our components are valid Python, we use Python's own `ast` module to parse them. Zero parsing overhead, perfect compatibility, error messages for free.
### 2. Debug Python in WASM (Not JavaScript!)
Set breakpoints in Python source code, step through Python lines, inspect Python variables - all in the browser! We compile with DWARF debug symbols and source maps that map WASM → Python source.
### 3. Progressive Module Loading
Split apps into multiple WASM modules:
- `bootstrap.wasm` (50KB) - Login only, no business logic
- `app-core.wasm` (200KB) - Main app, requires auth
- Feature modules - Lazy loaded on demand
- Security: Unauthenticated users never receive app code!
### 4. Python Executable, WASM Compilable
Write components in **actual Python** that can run in CPython for debugging, but compile to WebAssembly for production with:
- No virtual DOM overhead
- Direct signal-to-DOM updates
- 99% fewer mutations than React
- Sub-200KB bundle sizes
## Quick Start
### Try the Python Prototype
```bash
# Run the example
python3 example.py
# Run tests
python3 test.py
```
### Component Syntax
```python
@component
def Counter():
count = signal(0)
def increment():
count += 1
with Div(class_='counter'):
yield H1('Counter App')
yield P(f'Count: {count}')
yield Button('Click me', onclick=increment)
```
### Key Features
1. **Yield-based composition** - Natural Python generators for building component trees
2. **Context managers** - Use `with` statements for nested structures
3. **Reactive signals** - Fine-grained reactivity without virtual DOM
4. **Mixed content** - Yield strings, components, or elements seamlessly
## Files
- `RESEARCH.md` - Complete technical architecture and implementation plan
- `framework.py` - Python prototype implementation
- `example.py` - Example components demonstrating the syntax
- `test.py` - Simple test file
- `ast_parser.py` - Parse components using Python's ast module!
- `debug_concept.py` - Revolutionary Python-in-WASM debugging
- `module_system.py` - Progressive loading & security architecture
- `compiler.py` - Complete compilation pipeline concept
## Architecture
The framework works in two modes:
1. **Debug Mode** (Current): Runs in Python interpreter, generates HTML/IR
2. **Production Mode** (TODO): Compiles to WebAssembly with LLVM
### Compilation Pipeline
```
Python-like syntax → Parser (AST) → LLVM IR → WebAssembly → Browser
```
### Reactive Runtime
- Signals store values + subscriber lists in WASM memory
- Effects track dependencies automatically
- Direct signal-to-DOM-node mapping (no diffing)
- Batched DOM operations across WASM/JS bridge
## Next Steps
1. ✅ Python prototype with working components
2. ⬜ Parser for Python subset (yields, functions, decorators)
3. ⬜ LLVM backend for WASM generation
4. ⬜ Reactive runtime in C/Rust
5. ⬜ DOM operation batching and JS bridge
6. ⬜ Benchmarks against React/SolidJS
## Performance Targets
Based on SolidJS and Leptos research:
- Bundle size: < 200KB compressed
- Startup time: < 500ms on slow 3G
- Update performance: Within 10% of vanilla JS
- Memory usage: 70% less than React
## Why This Approach?
1. **Developer happiness** - Write Python, debug Python, ship WASM
2. **Performance** - Compile-time optimization, no virtual DOM
3. **Simplicity** - Yield syntax is intuitive and composable
4. **Type safety** - Static typing at compile time
Start with `example.py` to see the syntax in action!
+522
View File
@@ -0,0 +1,522 @@
# Ritz Framework Research & Architecture
## Core Insight: Python-Executable, WASM-Compilable
The breakthrough: Our framework uses **valid Python syntax** that can execute in a Python interpreter for debugging/prototyping, but compiles to WebAssembly for production. Developers write Python, debug in Python, ship as WASM.
## The Yield-Based Component Model
### Pure Python Implementation (for prototyping/debugging)
```python
# This is ACTUAL Python that runs in CPython!
from ritz import component, signal, Main, P, Label, Button
@component
def HighFiveApp():
high_fives = signal(0)
def increment():
high_fives.value += 1
with Main():
yield P('Give me a high 5 bro!')
yield Label(f'High fives: {high_fives}')
yield Button('Click me', onclick=increment)
# Can execute in Python for debugging:
# python app.py -> generates HTML/IR for verification
```
### Complex Example with Mixed Content
```python
@component
def BlogPost(title, author, content):
with Article(class_='blog-post'):
with Header():
yield H1(title)
yield 'By ' # Raw strings!
yield Span(author, class_='author')
yield ' on '
yield Time(content.date)
with Section():
yield from content.paragraphs # Composition via yield from
for comment in content.comments:
yield Comment(comment.author, comment.text)
```
### Why This Architecture Works
1. **Debugging**: Run components in Python interpreter to see generated HTML/IR
2. **Type Checking**: Use Python's type hints for compile-time validation
3. **Testing**: Standard Python test frameworks work
4. **Compilation**: Parser only needs to handle subset of Python, not full language
## Compilation Pipeline
### 1. Parser (Python → AST) - Using Python's `ast` Module!
**BREAKTHROUGH**: Since our components are valid Python, we can use Python's own `ast` module to parse them! No custom parser needed!
```python
import ast
import inspect
def parse_component(func):
"""Parse a component function using Python's AST"""
source = inspect.getsource(func)
tree = ast.parse(source)
# Walk the AST and transform to our IR
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# Found our component function
if has_component_decorator(node):
return transform_to_ritz_ast(node)
def transform_to_ritz_ast(node):
"""Transform Python AST to Ritz IR"""
component = {
'type': 'component',
'name': node.name,
'body': []
}
for stmt in node.body:
if isinstance(stmt, ast.With):
# with Element(): ...
component['body'].append(process_with_statement(stmt))
elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Yield):
# yield Element()
component['body'].append(process_yield(stmt.value))
return component
```
This gives us:
- **Zero parsing overhead** - Python does it for us
- **Perfect Python compatibility** - It IS Python
- **Error messages for free** - Python's parser handles syntax errors
- **Type hints support** - Can extract from AST
### 2. LLVM IR Generation
```cpp
// codegen.cpp
class RitzCodeGen {
llvm::LLVMContext context;
llvm::IRBuilder<> builder;
llvm::Module* module;
// Compile @component function to WASM
llvm::Function* compileComponent(ASTNode* node) {
auto func = llvm::Function::Create(
llvm::FunctionType::get(builder.getVoidTy(), false),
llvm::Function::ExternalLinkage,
node->component.name,
module
);
auto entry = llvm::BasicBlock::Create(context, "entry", func);
builder.SetInsertPoint(entry);
// Process yields - each becomes DOM operations
for (auto stmt : node->component.body) {
if (stmt->type == AST_YIELD) {
compileYield(stmt);
}
}
builder.CreateRetVoid();
return func;
}
void compileYield(ASTNode* node) {
// yield P('text') compiles to:
// 1. Create P element
// 2. Create text node
// 3. Append text to P
// 4. Append P to current parent
auto create_p = builder.CreateCall(
module->getFunction("dom_create_element"),
builder.getInt32(TAG_P)
);
// ... rest of DOM operations
}
};
```
### 3. Reactive Runtime (in WASM)
```c
// runtime.c - Compiles to WASM
typedef struct {
int32_t value;
uint32_t subscribers[MAX_SUBSCRIBERS];
uint32_t subscriber_count;
uint32_t dom_node_id; // Direct DOM node reference
} Signal;
Signal* create_signal(int32_t initial) {
Signal* sig = (Signal*)wasm_alloc(sizeof(Signal));
sig->value = initial;
sig->subscriber_count = 0;
return sig;
}
void signal_set(Signal* sig, int32_t value) {
sig->value = value;
// Direct DOM update - no virtual DOM!
if (sig->dom_node_id) {
dom_ops[op_count++] = OP_UPDATE_TEXT;
dom_ops[op_count++] = sig->dom_node_id;
dom_ops[op_count++] = (uint32_t)int_to_string(value);
}
// Notify subscribers
for (int i = 0; i < sig->subscriber_count; i++) {
call_effect(sig->subscribers[i]);
}
}
```
## DOM Bridge Architecture
### WASM Side - Accumulate Operations
```c
// Opcodes for DOM operations
enum DOMOp {
OP_CREATE_ELEMENT = 0x01,
OP_CREATE_TEXT = 0x02,
OP_SET_ATTRIBUTE = 0x03,
OP_APPEND_CHILD = 0x04,
OP_UPDATE_TEXT = 0x05,
OP_ADD_EVENT = 0x06,
OP_REMOVE = 0x07,
OP_BATCH_END = 0xFF
};
// Global operation buffer
uint32_t dom_ops[MAX_OPS];
uint32_t op_count = 0;
// Batch operations then flush
void flush_dom_ops() {
// Call JS with pointer to operation buffer
js_flush_dom_ops(dom_ops, op_count);
op_count = 0;
}
```
### JavaScript Side - Execute Operations
```javascript
// bridge.js
const nodeMap = new Map();
let nodeIdCounter = 1;
function flushDOMOps(ptr, len) {
const ops = new Uint32Array(wasmMemory.buffer, ptr, len);
const stack = [];
for (let i = 0; i < len;) {
switch(ops[i++]) {
case 0x01: { // CREATE_ELEMENT
const tagId = ops[i++];
const el = document.createElement(TAG_NAMES[tagId]);
const id = nodeIdCounter++;
nodeMap.set(id, el);
stack.push(id);
break;
}
case 0x05: { // UPDATE_TEXT
const nodeId = ops[i++];
const textPtr = ops[i++];
const text = readString(textPtr);
nodeMap.get(nodeId).textContent = text;
break;
}
// ... other operations
}
}
}
```
## Implementation Roadmap
### Phase 1: Python Prototype (Week 1)
- [x] Define component syntax with yield
- [ ] Build Python runtime for testing (framework.py)
- [ ] Create HTML generation from component tree
- [ ] Signal/reactive state in pure Python
### Phase 2: Parser (Week 2)
- [ ] Python subset grammar definition
- [ ] Parser using ANTLR or hand-written recursive descent
- [ ] AST generation from Python code
- [ ] Semantic analysis (type checking, reactive dependency tracking)
### Phase 3: WASM Compiler (Week 3-4)
- [ ] LLVM setup and IR generation
- [ ] Compile components to WASM functions
- [ ] Reactive runtime in C/Rust
- [ ] DOM operation batching
### Phase 4: JavaScript Bridge (Week 5)
- [ ] DOM operation interpreter in JS
- [ ] Event handler callbacks
- [ ] String passing optimization
- [ ] Memory management
### Phase 5: Optimization (Week 6)
- [ ] Template compilation (static vs dynamic parts)
- [ ] Signal-to-DOM direct mapping
- [ ] Batch operation compression
- [ ] Bundle size optimization
## Key Technical Decisions
### Why Yield?
- Natural Python syntax for building trees
- Composable via `yield from`
- Clear execution order
- No magic - just generators
### Why Compile vs Interpret?
- 10-100x performance improvement
- 5-10x smaller bundle (no Python runtime)
- Direct DOM updates (no virtual DOM)
- Type safety at compile time
### Why LLVM?
- Mature toolchain
- Excellent WASM backend
- Optimization passes
- Debugging support
## Performance Targets
Based on research of SolidJS, Leptos, and Dioxus:
- **Bundle size**: < 200KB compressed
- **Startup time**: < 500ms on slow 3G
- **Update latency**: Within 10% of vanilla JS
- **Memory usage**: 70% less than React
- **DOM mutations**: 99% fewer than virtual DOM
## Critical Implementation Details
### Signal Subscription Model
```c
// Each signal tracks its subscribers
// Each effect tracks its dependencies
// Bidirectional relationship for cleanup
typedef struct Effect {
void (*fn)(void);
Signal** dependencies;
uint32_t dep_count;
} Effect;
// Global context during effect execution
Effect* current_effect = NULL;
// When signal.get() called, register dependency
int32_t signal_get(Signal* sig) {
if (current_effect) {
add_dependency(current_effect, sig);
}
return sig->value;
}
```
### Memory Management
- Bump allocator for per-frame allocations
- Generation-based GC for long-lived objects
- Reference counting for shared resources
- No Rc/RefCell exposed to users
### String Handling
- String table in WASM memory
- Pass indices, not pointers
- Intern common strings
- UTF-8 throughout
## Revolutionary Debugging Story
### Python Debugging in WASM!
**THE INSIGHT**: We're not debugging JavaScript - we're debugging Python code running in WASM! This means we can:
```python
# This is what you see in DevTools
@component
def Counter():
count = signal(0) # <-- Breakpoint here shows Python source!
def increment():
count += 1 # <-- Step through Python, not JS!
print(f"Count: {count}") # <-- See Python values
```
### How It Works
1. **DWARF Debug Symbols**: LLVM generates standard DWARF symbols in WASM
2. **Source Maps on Steroids**: Map WASM instructions → Python source lines
3. **Symbol Translation**:
```
WASM offset 0x1234 → Counter.py:15:8 → "count += 1"
```
4. **Browser Integration**: Chrome DevTools already supports WASM debugging!
5. **Live Editing**: Recompile just the changed component, hot-reload the WASM module
### Debugging Options
```javascript
// In browser console
wasm.debug.setBreakpoint('Counter.py', 15)
wasm.debug.stepInto()
wasm.debug.inspect('count') // Shows Signal { value: 5, subscribers: [...] }
// Or even connect Python debugger!
# pdb remote debugging over WebSocket
import pdb; pdb.set_trace() # This could actually work!
```
## Modular Loading Architecture
### Progressive WASM Bundles
Instead of one monolithic WASM file, split into modules:
```
bootstrap.wasm (50KB)
├── Minimal runtime
├── Login component
└── Module loader
app.wasm (200KB) - Loaded after auth
├── Main application
├── Core components
└── Router
lazy/
├── admin.wasm (150KB) - Admin panel
├── analytics.wasm (100KB) - Analytics dashboard
├── reports.wasm (120KB) - Report generation
└── [feature].wasm - Feature-specific modules
```
### Security Through Architecture
```python
# bootstrap.py - Compiles to bootstrap.wasm
@component
def LoginPage():
with Main():
yield LoginForm()
# No app code here - not even downloaded!
# After successful auth
async def load_app():
# Server only sends this to authenticated users
app_module = await fetch('/wasm/app.wasm', {
'headers': {'Authorization': f'Bearer {token}'}
})
wasm.load_module(app_module)
```
### Implementation
```javascript
// module-loader.js
class RitzModuleLoader {
constructor() {
this.modules = new Map();
this.cache = new Cache('ritz-modules-v1');
}
async loadModule(name, url) {
// Check cache first
const cached = await this.cache.match(url);
if (cached) {
const wasm = await WebAssembly.instantiate(cached);
this.modules.set(name, wasm.instance);
return;
}
// Download and cache
const response = await fetch(url);
const bytes = await response.arrayBuffer();
// Cache for next time
await this.cache.put(url, new Response(bytes));
// Instantiate
const wasm = await WebAssembly.instantiate(bytes, {
env: this.getImports(name)
});
this.modules.set(name, wasm.instance);
}
}
```
### Route-Based Code Splitting
```python
# router.py
@component
def Router():
current_route = signal(window.location.pathname)
with Main():
if current_route == '/admin':
# Lazy load admin module
yield LazyLoad('admin.wasm', AdminPanel)
elif current_route == '/reports':
# Only load if needed
yield LazyLoad('reports.wasm', ReportsView)
else:
# Main app is already loaded
yield Dashboard()
```
## Next Steps (Updated)
1. ✅ **Prototype**: Build framework.py with working signals and components
2. **AST Parser**: Use Python's `ast` module to parse components
3. **Compiler Pipeline**:
- Python AST → Ritz IR
- Ritz IR → LLVM IR
- LLVM IR → WASM with debug symbols
4. **Debugging Infrastructure**:
- Generate source maps
- Embed Python source in WASM
- Browser DevTools integration
5. **Module System**:
- Build tool for splitting modules
- Runtime module loader
- Caching strategy
6. **Test**: Port TodoMVC with lazy-loaded features
## References
- SolidJS: Fine-grained reactivity without virtual DOM
- Leptos: Rust signals with Copy + 'static
- Dioxus Sledgehammer: Batched DOM operations
- LLVM WebAssembly Backend: https://llvm.org/docs/WebAssemblyUsage.html
- Binaryen: WASM optimizer and code generator
## Core Innovation
**Python syntax → WASM performance**. Write and debug in Python, ship as WASM. No virtual DOM, direct signal-to-DOM updates, 99% fewer mutations than React. The future of web frameworks: compiled, reactive, Python-like.
+256
View File
@@ -0,0 +1,256 @@
#!/usr/bin/env python3
"""
AST Parser for Ritz Framework
Uses Python's ast module to parse components and generate IR
"""
import ast
import inspect
import json
from typing import Any, Dict, List
class RitzASTTransformer(ast.NodeVisitor):
"""Transform Python AST to Ritz IR"""
def __init__(self):
self.current_component = None
self.ir = []
def visit_FunctionDef(self, node):
"""Process function definitions"""
# Check if it has @component decorator
if self.has_component_decorator(node):
self.current_component = {
'type': 'component',
'name': node.name,
'args': [arg.arg for arg in node.args.args],
'body': []
}
# Process function body
for stmt in node.body:
ir_node = self.process_statement(stmt)
if ir_node:
self.current_component['body'].append(ir_node)
self.ir.append(self.current_component)
self.current_component = None
def has_component_decorator(self, node):
"""Check if function has @component decorator"""
for decorator in node.decorator_list:
if isinstance(decorator, ast.Name) and decorator.id == 'component':
return True
return False
def process_statement(self, stmt):
"""Process a statement in component body"""
if isinstance(stmt, ast.With):
return self.process_with_statement(stmt)
elif isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Yield):
return self.process_yield(stmt.value)
elif isinstance(stmt, ast.Assign):
return self.process_assignment(stmt)
elif isinstance(stmt, ast.FunctionDef):
return self.process_inner_function(stmt)
elif isinstance(stmt, ast.For):
return self.process_for_loop(stmt)
elif isinstance(stmt, ast.If):
return self.process_if_statement(stmt)
return None
def process_with_statement(self, node):
"""Process 'with Element():' statements"""
context = node.items[0].context_expr
with_node = {
'type': 'with',
'element': self.process_expression(context),
'body': []
}
# Process body
for stmt in node.body:
body_node = self.process_statement(stmt)
if body_node:
with_node['body'].append(body_node)
return with_node
def process_yield(self, node):
"""Process yield statements"""
return {
'type': 'yield',
'value': self.process_expression(node.value) if node.value else None
}
def process_expression(self, expr):
"""Process an expression"""
if isinstance(expr, ast.Call):
return {
'type': 'call',
'func': self.get_name(expr.func),
'args': [self.process_expression(arg) for arg in expr.args],
'kwargs': {
kw.arg: self.process_expression(kw.value)
for kw in expr.keywords
}
}
elif isinstance(expr, ast.Name):
return {
'type': 'name',
'id': expr.id
}
elif isinstance(expr, ast.Constant):
return {
'type': 'constant',
'value': expr.value
}
elif isinstance(expr, ast.JoinedStr): # f-strings
return {
'type': 'fstring',
'parts': [self.process_expression(v) for v in expr.values]
}
elif isinstance(expr, ast.FormattedValue):
return {
'type': 'formatted_value',
'value': self.process_expression(expr.value)
}
elif isinstance(expr, ast.Attribute):
return {
'type': 'attribute',
'value': self.process_expression(expr.value),
'attr': expr.attr
}
else:
return {
'type': 'unknown',
'ast_type': expr.__class__.__name__
}
def process_assignment(self, node):
"""Process variable assignments (e.g., count = signal(0))"""
target = node.targets[0]
if isinstance(target, ast.Name):
value = self.process_expression(node.value)
# Check if it's a signal
if (isinstance(value, dict) and
value.get('type') == 'call' and
value.get('func') == 'signal'):
return {
'type': 'signal',
'name': target.id,
'initial': value['args'][0] if value['args'] else {'type': 'constant', 'value': None}
}
else:
return {
'type': 'assign',
'target': target.id,
'value': value
}
def process_inner_function(self, node):
"""Process inner function definitions (e.g., event handlers)"""
return {
'type': 'function',
'name': node.name,
'args': [arg.arg for arg in node.args.args],
'body': [self.process_statement(stmt) for stmt in node.body]
}
def process_for_loop(self, node):
"""Process for loops"""
return {
'type': 'for',
'target': self.get_name(node.target),
'iter': self.process_expression(node.iter),
'body': [self.process_statement(stmt) for stmt in node.body]
}
def process_if_statement(self, node):
"""Process if statements"""
return {
'type': 'if',
'test': self.process_expression(node.test),
'body': [self.process_statement(stmt) for stmt in node.body],
'else': [self.process_statement(stmt) for stmt in node.orelse] if node.orelse else []
}
def get_name(self, node):
"""Get name from Name or Attribute node"""
if isinstance(node, ast.Name):
return node.id
elif isinstance(node, ast.Attribute):
return f"{self.get_name(node.value)}.{node.attr}"
return 'unknown'
def parse_component_source(source: str) -> List[Dict]:
"""Parse component source code and return IR"""
tree = ast.parse(source)
transformer = RitzASTTransformer()
transformer.visit(tree)
return transformer.ir
def parse_component_function(func) -> Dict:
"""Parse a component function and return its IR"""
source = inspect.getsource(func)
ir = parse_component_source(source)
return ir[0] if ir else None
def visualize_ast(source: str):
"""Visualize Python AST for debugging"""
tree = ast.parse(source)
return ast.dump(tree, indent=2)
# Test it!
if __name__ == '__main__':
test_source = '''
@component
def Counter():
count = signal(0)
def increment():
count += 1
with Div(class_='counter'):
yield H1('Counter')
yield P(f'Count: {count}')
yield Button('Click me', onclick=increment)
'''
print("=== Python AST (for reference) ===")
print(visualize_ast(test_source)[:500] + "...\n")
print("=== Ritz IR ===")
ir = parse_component_source(test_source)
print(json.dumps(ir, indent=2))
print("\n=== Test with actual component ===")
# Import our framework
from framework import component, signal
# Mock elements for testing
def Div(**kwargs): return {'element': 'div', 'props': kwargs}
def H1(*args): return {'element': 'h1', 'children': args}
def P(*args): return {'element': 'p', 'children': args}
def Button(*args, **kwargs): return {'element': 'button', 'children': args, 'props': kwargs}
@component
def TestComponent():
count = signal(0)
with Div(class_='test'):
yield H1('Test')
yield P(f'Value: {count}')
for i in range(3):
yield Button(f'Button {i}')
ir = parse_component_function(TestComponent)
print("\nParsed from real function:")
print(json.dumps(ir, indent=2)[:1000] + "...")
+344
View File
@@ -0,0 +1,344 @@
#!/usr/bin/env python3
"""
Ritz Compilation Pipeline
From Python source to debuggable WASM modules
"""
import ast
import hashlib
import subprocess
from pathlib import Path
from typing import Dict, List, Optional
from dataclasses import dataclass
@dataclass
class CompilationUnit:
"""A unit of compilation (module)"""
name: str
components: List[str]
dependencies: List[str]
source_files: List[Path]
output_path: Path
requires_auth: bool = False
class RitzCompiler:
"""Complete compilation pipeline"""
def __init__(self, project_dir: Path):
self.project_dir = project_dir
self.build_dir = project_dir / 'build'
self.wasm_dir = project_dir / 'dist' / 'wasm'
self.units: List[CompilationUnit] = []
def compile_project(self):
"""Full compilation pipeline"""
print("=== Ritz Compilation Pipeline ===\n")
# 1. Discover and parse components
print("1. Parsing Python components...")
components = self.discover_components()
# 2. Split into modules
print("\n2. Splitting into modules...")
self.split_into_modules(components)
# 3. Generate IR for each module
print("\n3. Generating intermediate representation...")
for unit in self.units:
self.generate_ir(unit)
# 4. Compile to WASM with debug symbols
print("\n4. Compiling to WASM...")
for unit in self.units:
self.compile_to_wasm(unit)
# 5. Generate debug artifacts
print("\n5. Generating debug artifacts...")
self.generate_debug_artifacts()
# 6. Generate module manifest
print("\n6. Creating module manifest...")
self.generate_manifest()
print("\n✅ Compilation complete!")
def discover_components(self) -> Dict[str, ast.AST]:
"""Discover all @component decorated functions"""
components = {}
for py_file in self.project_dir.glob('**/*.py'):
if 'build' in py_file.parts or '__pycache__' in py_file.parts:
continue
with open(py_file) as f:
source = f.read()
try:
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
if self.has_component_decorator(node):
components[node.name] = {
'ast': node,
'file': py_file,
'source': source
}
print(f" Found: {node.name} in {py_file.name}")
except SyntaxError as e:
print(f" Skipping {py_file}: {e}")
return components
def has_component_decorator(self, node: ast.FunctionDef) -> bool:
"""Check if function has @component decorator"""
for decorator in node.decorator_list:
if isinstance(decorator, ast.Name) and decorator.id == 'component':
return True
return False
def split_into_modules(self, components: Dict):
"""Split components into compilation units"""
# Bootstrap module - login only
bootstrap_components = [
name for name in components
if 'Login' in name or 'Auth' in name
]
if bootstrap_components:
self.units.append(CompilationUnit(
name='bootstrap',
components=bootstrap_components,
dependencies=[],
source_files=[components[c]['file'] for c in bootstrap_components],
output_path=self.wasm_dir / 'bootstrap.wasm',
requires_auth=False
))
print(f" bootstrap.wasm: {len(bootstrap_components)} components")
# Core module - main app
core_components = [
name for name in components
if name not in bootstrap_components
and not name.startswith('Admin')
and not name.startswith('Analytics')
]
if core_components:
self.units.append(CompilationUnit(
name='app-core',
components=core_components,
dependencies=['bootstrap'],
source_files=[components[c]['file'] for c in core_components],
output_path=self.wasm_dir / 'app-core.wasm',
requires_auth=True
))
print(f" app-core.wasm: {len(core_components)} components")
# Feature modules
admin_components = [
name for name in components if name.startswith('Admin')
]
if admin_components:
self.units.append(CompilationUnit(
name='admin',
components=admin_components,
dependencies=['app-core'],
source_files=[components[c]['file'] for c in admin_components],
output_path=self.wasm_dir / 'features' / 'admin.wasm',
requires_auth=True
))
print(f" admin.wasm: {len(admin_components)} components")
def generate_ir(self, unit: CompilationUnit):
"""Generate Ritz IR from Python AST"""
print(f" Generating IR for {unit.name}...")
# Use our AST parser
from ast_parser import RitzASTTransformer
ir_module = {
'name': unit.name,
'components': [],
'dependencies': unit.dependencies
}
for component_name in unit.components:
# Parse component
# In real implementation, would use the stored AST
print(f" Processing {component_name}")
# Generate IR for component
ir = {
'type': 'component',
'name': component_name,
# ... IR details
}
ir_module['components'].append(ir)
# Save IR for next stage
ir_path = self.build_dir / f'{unit.name}.ir.json'
ir_path.parent.mkdir(parents=True, exist_ok=True)
import json
with open(ir_path, 'w') as f:
json.dump(ir_module, f, indent=2)
def compile_to_wasm(self, unit: CompilationUnit):
"""Compile IR to WASM using LLVM"""
print(f" Compiling {unit.name} to WASM...")
# This would use LLVM in reality
# For demonstration, show the command that would be run
llvm_commands = [
# 1. Generate LLVM IR
f"ritz-codegen {self.build_dir}/{unit.name}.ir.json -o {self.build_dir}/{unit.name}.ll",
# 2. Compile to WASM with debug symbols
f"clang --target=wasm32-unknown-unknown "
f"-O2 -g " # Optimization + debug symbols
f"-fdebug-prefix-map={self.project_dir}=/src " # Remap paths for debugging
f"-o {self.build_dir}/{unit.name}.wasm "
f"{self.build_dir}/{unit.name}.ll",
# 3. Optimize with Binaryen
f"wasm-opt "
f"-O2 " # Optimize
f"--debuginfo " # Preserve debug info
f"--source-map {unit.output_path}.map " # Generate source map
f"-o {unit.output_path} "
f"{self.build_dir}/{unit.name}.wasm"
]
for cmd in llvm_commands:
print(f" $ {cmd}")
# Create output file (mock)
unit.output_path.parent.mkdir(parents=True, exist_ok=True)
unit.output_path.write_bytes(b'\x00asm\x01\x00\x00\x00...')
def generate_debug_artifacts(self):
"""Generate debugging support files"""
print(" Generating source maps...")
for unit in self.units:
source_map = {
"version": 3,
"file": str(unit.output_path),
"sources": [str(f) for f in unit.source_files],
"sourcesContent": [
f.read_text() for f in unit.source_files
],
"names": unit.components,
"mappings": "...", # Would be actual VLQ encodings
# Ritz extensions for Python debugging
"x_ritz": {
"python_version": "3.11",
"module_name": unit.name,
"components": unit.components,
"debug_symbols": f"{unit.output_path}.dwarf"
}
}
map_path = unit.output_path.with_suffix('.wasm.map')
print(f" {map_path.name}")
def generate_manifest(self):
"""Generate module manifest for loader"""
manifest = {
"version": "1.0",
"modules": {}
}
for unit in self.units:
# Calculate hash for cache busting
if unit.output_path.exists():
content = unit.output_path.read_bytes()
hash_val = hashlib.sha256(content).hexdigest()[:12]
else:
hash_val = "mock"
manifest["modules"][unit.name] = {
"path": f"/wasm/{unit.output_path.name}",
"size": unit.output_path.stat().st_size if unit.output_path.exists() else 0,
"hash": hash_val,
"dependencies": unit.dependencies,
"requires_auth": unit.requires_auth,
"components": unit.components
}
manifest_path = self.wasm_dir / 'manifest.json'
print(f" Writing {manifest_path}")
import json
manifest_path.parent.mkdir(parents=True, exist_ok=True)
with open(manifest_path, 'w') as f:
json.dump(manifest, f, indent=2)
class DebugServer:
"""Development server with debugging support"""
def __init__(self, compiler: RitzCompiler):
self.compiler = compiler
def start(self, port: int = 8080):
"""Start dev server with hot reload and debugging"""
print(f"\n🚀 Starting Ritz dev server on http://localhost:{port}")
print("\nFeatures:")
print(" ✓ Python source debugging in browser")
print(" ✓ Hot module reloading")
print(" ✓ Source maps")
print(" ✓ Progressive loading")
print("\nDevTools Integration:")
print(" 1. Open Chrome DevTools")
print(" 2. Go to Sources tab")
print(" 3. Find 'wasm://' in file tree")
print(" 4. Set breakpoints in Python files!")
print("\nCommands:")
print(" r - Recompile all")
print(" m <module> - Recompile specific module")
print(" d - Open debugger")
print(" q - Quit")
# Example usage
if __name__ == '__main__':
import sys
from pathlib import Path
# Compile the project
project_dir = Path.cwd()
compiler = RitzCompiler(project_dir)
# Mock some components for demo
print("=== Ritz Compiler Demo ===\n")
# Show what the compiler would do
print("Project structure:")
print(" ritz/")
print(" ├── components/")
print(" │ ├── login.py (@component LoginForm)")
print(" │ ├── dashboard.py (@component Dashboard)")
print(" │ ├── admin.py (@component AdminPanel)")
print(" │ └── analytics.py (@component AnalyticsView)")
print(" ├── build/ (intermediate files)")
print(" └── dist/")
print(" └── wasm/")
print(" ├── bootstrap.wasm (50KB - no auth)")
print(" ├── app-core.wasm (200KB - auth required)")
print(" ├── features/")
print(" │ ├── admin.wasm (150KB)")
print(" │ └── analytics.wasm (120KB)")
print(" └── manifest.json")
print("\nCompilation would:")
print("1. Parse Python with ast module")
print("2. Generate Ritz IR")
print("3. Compile to LLVM IR")
print("4. Generate WASM with debug symbols")
print("5. Create source maps")
print("6. Split into secure modules")
print("\n🎉 The dream is real: Python → WASM with debugging!")
+263
View File
@@ -0,0 +1,263 @@
"""
Ritz Debugging Infrastructure Concept
Shows how we can debug Python code running in WASM
"""
import json
from dataclasses import dataclass
from typing import Dict, List, Optional
@dataclass
class DebugSymbol:
"""Maps WASM offset to Python source location"""
wasm_offset: int
file: str
line: int
column: int
function: str
source_text: str
class WASMDebugger:
"""Debugging infrastructure for Python-in-WASM"""
def __init__(self):
self.symbols: Dict[int, DebugSymbol] = {}
self.breakpoints: List[DebugSymbol] = []
self.source_files: Dict[str, List[str]] = {}
def generate_debug_info(self, component_ir, wasm_module):
"""Generate debug symbols during compilation"""
# This would be done by LLVM during compilation
# Here's a conceptual example:
debug_info = {
'format': 'ritz-debug-v1',
'source_language': 'python',
'symbols': []
}
# Map each IR node to WASM offsets
for node in component_ir['body']:
if node['type'] == 'yield':
symbol = {
'wasm_offset': '0x1234', # Would be actual WASM offset
'source': {
'file': 'component.py',
'line': node.get('line', 0),
'column': node.get('column', 0),
'text': 'yield Element()'
}
}
debug_info['symbols'].append(symbol)
return debug_info
def set_breakpoint(self, file: str, line: int):
"""Set a breakpoint at Python source location"""
# Find WASM offset for this source location
for offset, symbol in self.symbols.items():
if symbol.file == file and symbol.line == line:
print(f"Breakpoint set at {file}:{line} (WASM offset: {hex(offset)})")
self.breakpoints.append(symbol)
# In real implementation, this would:
# 1. Communicate with browser DevTools
# 2. Set WASM breakpoint at the offset
# 3. Map back to Python source when hit
return True
return False
def source_map_example(self):
"""Example of source map for WASM→Python mapping"""
source_map = {
"version": 3,
"sources": ["counter.py", "framework.py"],
"names": ["count", "increment", "signal"],
"mappings": "AAAA,SAAS;AACT,YAAY", # Base64 VLQ encoded
"file": "app.wasm",
# Extensions for Python debugging
"x_ritz_debug": {
"python_version": "3.11",
"symbols": [
{
"wasm_offset": "0x1000",
"python_location": "counter.py:5:8",
"ast_node": "ast.Assign",
"scope_vars": ["count", "increment"]
}
],
# Embedded Python source for debugging!
"embedded_sources": {
"counter.py": """
@component
def Counter():
count = signal(0) # Line 3
def increment(): # Line 5
count += 1 # Line 6 - Breakpoint here!
with Div():
yield Button('Click', onclick=increment)
"""
}
}
}
return source_map
class LiveEditingSupport:
"""Live editing Python components in the browser"""
def __init__(self, debugger: WASMDebugger):
self.debugger = debugger
self.module_cache = {}
def edit_component(self, component_name: str, new_source: str):
"""Live edit a component"""
print(f"Live editing {component_name}")
# 1. Parse the new Python source
from ast_parser import parse_component_source
ir = parse_component_source(new_source)
# 2. Compile just this component to WASM
wasm_module = self.compile_component(ir)
# 3. Hot reload in browser
self.hot_reload(component_name, wasm_module)
return {
'status': 'reloaded',
'component': component_name,
'size': len(wasm_module)
}
def compile_component(self, ir):
"""Compile single component to WASM module"""
# In reality, this would use LLVM
# For now, mock it
return b'\x00asm\x01\x00\x00\x00...'
def hot_reload(self, name: str, wasm_bytes: bytes):
"""Hot reload a WASM module in browser"""
js_code = f"""
// Hot reload component
const oldModule = RitzModules.get('{name}');
const newModule = await WebAssembly.instantiate({list(wasm_bytes)});
// Preserve state
const state = oldModule.exports.get_state();
newModule.exports.set_state(state);
// Swap modules
RitzModules.set('{name}', newModule);
// Re-render affected components
RitzRenderer.rerender('{name}');
"""
print(f"Hot reload JS:\n{js_code}")
class BrowserDevToolsIntegration:
"""Integration with Chrome/Firefox DevTools"""
@staticmethod
def generate_dwarf_symbols():
"""Generate DWARF debug symbols for WASM"""
# DWARF is the standard debug format
# LLVM can generate this automatically
dwarf = {
'.debug_info': {
'compilation_unit': {
'language': 'DW_LANG_Python',
'producer': 'Ritz Compiler v0.1',
'source_file': 'app.py'
}
},
'.debug_line': {
# Line number program
# Maps WASM addresses to source lines
'line_table': [
{'address': 0x1000, 'file': 1, 'line': 5, 'column': 8},
{'address': 0x1010, 'file': 1, 'line': 6, 'column': 4},
]
},
'.debug_abbrev': {
# Abbreviation table
}
}
return dwarf
@staticmethod
def devtools_protocol_example():
"""Example of Chrome DevTools Protocol for WASM debugging"""
return {
'method': 'Debugger.setBreakpoint',
'params': {
'location': {
'scriptId': 'wasm://app.wasm/counter.py',
'lineNumber': 6,
'columnNumber': 8
}
}
}
# Demonstration
if __name__ == '__main__':
print("=== Ritz Debugging Infrastructure ===\n")
debugger = WASMDebugger()
print("1. Source Map Example:")
print(json.dumps(debugger.source_map_example(), indent=2)[:500] + "...\n")
print("2. Setting Breakpoint:")
# Simulate adding debug symbols
debugger.symbols[0x1234] = DebugSymbol(
wasm_offset=0x1234,
file='counter.py',
line=6,
column=8,
function='increment',
source_text='count += 1'
)
debugger.set_breakpoint('counter.py', 6)
print("\n3. Live Editing:")
editor = LiveEditingSupport(debugger)
result = editor.edit_component('Counter', '''
@component
def Counter():
count = signal(0)
def increment():
count += 2 # Changed from += 1
print(f"New count: {count}")
with Div():
yield Button('Click', onclick=increment)
''')
print(f"Result: {result}")
print("\n4. DevTools Integration:")
print(json.dumps(BrowserDevToolsIntegration.devtools_protocol_example(), indent=2))
print("\n=== The Dream ===")
print("""
In Chrome DevTools:
- Set breakpoints in Python source (not WASM or JS!)
- Step through Python code line by line
- Inspect Python variables and objects
- Live edit Python and see changes immediately
- Full stack traces in Python
This is possible because:
1. Our Python compiles to WASM with debug symbols
2. WASM has source map support
3. We embed Python source in the WASM module
4. Browser DevTools understand DWARF/source maps
""")
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
Example Ritz Application
This actually runs in Python!
"""
from framework import (
component, signal, render,
Main, Div, P, Button, Label, H1, H2,
Article, Header, Section, Footer, Span, Strong, Em, Time
)
@component
def HighFiveApp():
"""Simple counter example"""
high_fives = signal(0)
def increment():
high_fives += 1
print(f"High fives incremented to: {high_fives.value}")
with Main():
yield H1('High Five Counter')
yield P('Give me a high 5 bro!')
yield Label(f'High fives: {high_fives}')
yield Button('Click me', onclick=increment)
@component
def Comment(author: str, text: str, replies=None):
"""Nested comment component"""
with Div(class_='comment'):
with Div(class_='comment-header'):
yield Strong(author)
yield ' says:'
with Div(class_='comment-body'):
yield text
if replies:
with Div(class_='replies'):
for reply in replies:
yield reply
@component
def BlogPost(title: str, author: str, content: str):
"""Complex component with mixed content"""
with Article(class_='blog-post'):
with Header():
yield H1(title)
yield 'By ' # Raw string!
yield Span(author, class_='author')
yield ' on '
yield Time('2024-01-15')
with Section(class_='content'):
yield P(content)
yield H2('Comments')
# Nested components with yield
yield Comment(
'Alice',
'Great post! This yield syntax is brilliant.',
replies=[
Comment('Bob', 'I agree! So Pythonic.'),
Comment('Charlie', 'Can\'t wait to see the WASM output.')
]
)
yield Comment(
'Dave',
'How does this compare to React performance-wise?'
)
@component
def TodoItem(text: str, completed: signal):
"""Todo item with reactive state"""
def toggle():
completed.value = not completed.value
print(f"Todo '{text}' toggled to: {completed.value}")
with Div(class_='todo-item'):
checkbox_props = {'type': 'checkbox', 'onclick': toggle}
if completed.value:
checkbox_props['checked'] = 'checked'
# Note: In real implementation, Input would be a special element
# For now we just demonstrate the pattern
yield Div(f'[{"✓" if completed.value else " "}] {text}',
class_='todo-checkbox',
onclick=toggle)
@component
def TodoApp():
"""Todo list with multiple signals"""
todos = [
('Build parser', signal(False)),
('Implement WASM compiler', signal(False)),
('Create reactive runtime', signal(False)),
('Optimize DOM bridge', signal(False))
]
with Div(class_='todo-app'):
yield H1('Ritz Framework TODO')
for text, completed in todos:
yield TodoItem(text, completed)
with Footer():
completed_count = sum(1 for _, c in todos if c.value)
yield P(f'Completed: {completed_count}/{len(todos)}')
def main():
"""Run examples and show output"""
print("=" * 60)
print("RITZ FRAMEWORK - Python Prototype")
print("=" * 60)
print("\n### Example 1: High Five Counter ###")
render(HighFiveApp)
print("\n" + "=" * 60)
print("\n### Example 2: Blog Post with Comments ###")
render(BlogPost,
title="Introducing Ritz Framework",
author="Aaron",
content="A new approach to web frameworks: Python syntax that compiles to WASM!")
print("\n" + "=" * 60)
print("\n### Example 3: Todo App ###")
render(TodoApp)
print("\n" + "=" * 60)
print("\nThis is actual Python running in CPython!")
print("In production, this would compile to WebAssembly.")
print("The same code runs in both environments!")
if __name__ == '__main__':
main()
+176
View File
@@ -0,0 +1,176 @@
"""
Ritz Framework - Python Prototype
This runs in actual Python for debugging/prototyping!
"""
from contextlib import contextmanager
from typing import Generator, Any, List, Dict, Optional, Callable
import json
# Global context for building component trees
_context_stack = []
class Signal:
"""Reactive signal - simplified for Python prototype"""
def __init__(self, initial_value):
self.value = initial_value
self.subscribers = []
def __str__(self):
return str(self.value)
def __iadd__(self, other):
self.value += other
self._notify()
return self
def _notify(self):
for subscriber in self.subscribers:
subscriber()
def subscribe(self, callback):
self.subscribers.append(callback)
class VNode:
"""Virtual node for component tree"""
def __init__(self, tag: str, props: Dict = None, children: List = None):
self.tag = tag
self.props = props or {}
self.children = children or []
def to_html(self) -> str:
"""Render to HTML string"""
if self.children:
children_html = ''.join(
child.to_html() if isinstance(child, VNode) else str(child)
for child in self.children
)
attrs = ' '.join(f'{k}="{v}"' for k, v in self.props.items() if k != 'onclick')
attrs = ' ' + attrs if attrs else ''
return f'<{self.tag}{attrs}>{children_html}</{self.tag}>'
else:
attrs = ' '.join(f'{k}="{v}"' for k, v in self.props.items() if k != 'onclick')
attrs = ' ' + attrs if attrs else ''
return f'<{self.tag}{attrs} />'
def to_ir(self) -> Dict:
"""Convert to intermediate representation"""
# Filter out non-serializable props like onclick functions
serializable_props = {
k: v for k, v in self.props.items()
if not callable(v)
}
return {
'type': 'element',
'tag': self.tag,
'props': serializable_props,
'children': [
child.to_ir() if isinstance(child, VNode)
else {'type': 'text', 'value': str(child)}
for child in self.children
]
}
class Element:
"""Context manager for building elements"""
def __init__(self, tag: str, *children, **props):
self.tag = tag
self.props = props
self.children = list(children) if children else []
self.vnode = VNode(tag, props, self.children)
def __enter__(self):
_context_stack.append(self)
# Store the initial length so we know what was added in context
self._initial_children_len = len(self.children)
return self
def __exit__(self, *args):
_context_stack.pop()
# Don't rebuild vnode here - let the component decorator handle it
def component(func: Callable) -> Callable:
"""Decorator for components that use yield"""
def wrapper(*args, **kwargs):
gen = func(*args, **kwargs)
if hasattr(gen, '__next__'): # It's a generator
root_element = None
initial_stack_len = len(_context_stack)
# Track the first context that gets created
first_context = None
# Execute the generator to build the tree
for item in gen:
# Check if a new context was just created
if not first_context and len(_context_stack) > initial_stack_len:
first_context = _context_stack[initial_stack_len]
current_context = _context_stack[-1] if _context_stack else None
if current_context:
# We're inside a with statement, add to its children
if isinstance(item, Element):
current_context.children.append(item.vnode)
elif isinstance(item, VNode):
current_context.children.append(item)
elif isinstance(item, str):
current_context.children.append(item)
else:
# Try to convert to string
current_context.children.append(str(item))
else:
# No context, this should be the root
if isinstance(item, Element):
root_element = item.vnode
elif isinstance(item, VNode):
root_element = item
# Return the first context if it was created, otherwise root_element
if first_context:
first_context.vnode = VNode(
first_context.tag,
first_context.props,
first_context.children
)
return first_context.vnode
return root_element
else:
return func(*args, **kwargs)
return wrapper
# Factory functions for HTML elements
def _make_element(tag: str) -> Callable:
def element_factory(*children, **props):
if len(children) == 1 and isinstance(children[0], str):
# Shorthand: P('text') creates element with text
return Element(tag, children[0], **props)
return Element(tag, *children, **props)
return element_factory
# Generate standard HTML elements
for tag in ['main', 'div', 'p', 'button', 'label', 'h1', 'h2', 'h3',
'article', 'section', 'header', 'footer', 'span', 'strong',
'em', 'li', 'ul', 'ol', 'form', 'input', 'nav', 'time']:
globals()[tag.capitalize()] = _make_element(tag)
def signal(initial_value) -> Signal:
"""Create a reactive signal"""
return Signal(initial_value)
# Helper function for debugging
def render(component_func, *args, **kwargs):
"""Render a component to HTML and IR"""
result = component_func(*args, **kwargs)
if isinstance(result, VNode):
print("=== HTML Output ===")
print(result.to_html())
print("\n=== IR Output ===")
print(json.dumps(result.to_ir(), indent=2))
else:
print("Component did not return a VNode")
return result
+427
View File
@@ -0,0 +1,427 @@
"""
Ritz Module System - Progressive Loading Concept
Split apps into multiple WASM modules for security and performance
"""
import hashlib
import json
from dataclasses import dataclass
from typing import Dict, List, Optional, Set
@dataclass
class WASMModule:
"""Represents a WASM module"""
name: str
path: str
size_kb: int
dependencies: List[str]
exports: List[str]
requires_auth: bool = False
cache_strategy: str = 'aggressive' # aggressive, normal, no-cache
class ModuleGraph:
"""Dependency graph for WASM modules"""
def __init__(self):
self.modules: Dict[str, WASMModule] = {}
self.loaded: Set[str] = set()
def add_module(self, module: WASMModule):
self.modules[module.name] = module
def get_load_order(self, module_name: str) -> List[str]:
"""Get modules to load in dependency order"""
if module_name in self.loaded:
return []
order = []
visited = set()
def visit(name):
if name in visited or name in self.loaded:
return
visited.add(name)
module = self.modules.get(name)
if module:
for dep in module.dependencies:
visit(dep)
order.append(name)
visit(module_name)
return order
class RitzModuleSystem:
"""Progressive module loading system"""
def __init__(self):
self.graph = ModuleGraph()
self.setup_modules()
def setup_modules(self):
"""Define application module structure"""
# Bootstrap - minimal, always loaded
self.graph.add_module(WASMModule(
name='bootstrap',
path='/wasm/bootstrap.wasm',
size_kb=50,
dependencies=[],
exports=['LoginForm', 'ModuleLoader', 'AuthService'],
requires_auth=False,
cache_strategy='aggressive'
))
# Core app - loaded after authentication
self.graph.add_module(WASMModule(
name='app-core',
path='/wasm/app-core.wasm',
size_kb=200,
dependencies=['bootstrap'],
exports=['Router', 'Dashboard', 'Navigation'],
requires_auth=True,
cache_strategy='aggressive'
))
# Feature modules - lazy loaded
self.graph.add_module(WASMModule(
name='admin',
path='/wasm/features/admin.wasm',
size_kb=150,
dependencies=['app-core'],
exports=['AdminPanel', 'UserManagement', 'Settings'],
requires_auth=True,
cache_strategy='normal'
))
self.graph.add_module(WASMModule(
name='analytics',
path='/wasm/features/analytics.wasm',
size_kb=120,
dependencies=['app-core', 'charts'],
exports=['AnalyticsDashboard', 'Reports'],
requires_auth=True,
cache_strategy='normal'
))
self.graph.add_module(WASMModule(
name='charts',
path='/wasm/lib/charts.wasm',
size_kb=80,
dependencies=[],
exports=['LineChart', 'BarChart', 'PieChart'],
requires_auth=True,
cache_strategy='aggressive'
))
def generate_loader_js(self) -> str:
"""Generate JavaScript module loader"""
return '''
class RitzModuleLoader {
constructor() {
this.modules = new Map();
this.loading = new Map();
this.cache = caches.open('ritz-modules-v1');
}
async loadModule(name, config = {}) {
// Check if already loaded
if (this.modules.has(name)) {
return this.modules.get(name);
}
// Check if already loading (prevent double-load)
if (this.loading.has(name)) {
return this.loading.get(name);
}
// Start loading
const loadPromise = this._loadModule(name, config);
this.loading.set(name, loadPromise);
try {
const module = await loadPromise;
this.modules.set(name, module);
return module;
} finally {
this.loading.delete(name);
}
}
async _loadModule(name, config) {
const manifest = await this.getManifest();
const moduleInfo = manifest[name];
if (!moduleInfo) {
throw new Error(`Module ${name} not found`);
}
// Check authentication requirement
if (moduleInfo.requires_auth && !this.isAuthenticated()) {
throw new Error(`Module ${name} requires authentication`);
}
// Load dependencies first
for (const dep of moduleInfo.dependencies) {
await this.loadModule(dep, config);
}
// Try cache first
const cache = await this.cache;
const cached = await cache.match(moduleInfo.path);
let response;
if (cached && !config.bypassCache) {
console.log(`Loading ${name} from cache`);
response = cached;
} else {
console.log(`Downloading ${name} (${moduleInfo.size_kb}KB)`);
// Add auth token if required
const headers = {};
if (moduleInfo.requires_auth) {
headers['Authorization'] = `Bearer ${this.getAuthToken()}`;
}
response = await fetch(moduleInfo.path, { headers });
// Cache based on strategy
if (moduleInfo.cache_strategy !== 'no-cache') {
await cache.put(moduleInfo.path, response.clone());
}
}
// Instantiate WASM
const bytes = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(bytes, {
env: this.getImports(name),
...this.getModuleImports(moduleInfo.dependencies)
});
return wasm.instance;
}
getModuleImports(dependencies) {
// Link imported functions from dependencies
const imports = {};
for (const dep of dependencies) {
const module = this.modules.get(dep);
if (module) {
imports[dep] = module.exports;
}
}
return imports;
}
async preloadModules(names) {
// Preload modules in parallel
const promises = names.map(name => this.loadModule(name));
await Promise.all(promises);
}
isAuthenticated() {
return localStorage.getItem('auth_token') !== null;
}
getAuthToken() {
return localStorage.getItem('auth_token');
}
async getManifest() {
// Module manifest with metadata
const response = await fetch('/wasm/manifest.json');
return response.json();
}
}
// Usage
const loader = new RitzModuleLoader();
// Bootstrap phase - no auth required
await loader.loadModule('bootstrap');
// After login
async function onLoginSuccess(token) {
localStorage.setItem('auth_token', token);
// Now load the main app
await loader.loadModule('app-core');
// Preload common features
loader.preloadModules(['charts']); // Non-blocking
// Navigate to dashboard
window.RitzApp.navigate('/dashboard');
}
// Lazy load on route change
window.addEventListener('route-change', async (e) => {
const route = e.detail.route;
if (route.startsWith('/admin')) {
await loader.loadModule('admin');
} else if (route.startsWith('/analytics')) {
await loader.loadModule('analytics');
}
});
'''
def generate_manifest(self) -> Dict:
"""Generate module manifest"""
manifest = {}
for name, module in self.graph.modules.items():
manifest[name] = {
'path': module.path,
'size_kb': module.size_kb,
'dependencies': module.dependencies,
'requires_auth': module.requires_auth,
'cache_strategy': module.cache_strategy,
'hash': self.calculate_hash(module)
}
return manifest
def calculate_hash(self, module: WASMModule) -> str:
"""Calculate hash for cache invalidation"""
content = f"{module.name}:{module.path}:{module.size_kb}"
return hashlib.sha256(content.encode()).hexdigest()[:12]
def split_components_into_modules(self, components: List[str]) -> Dict[str, List[str]]:
"""Automatically split components into modules"""
# Example heuristic: group by feature area
modules = {
'bootstrap': [],
'app-core': [],
'admin': [],
'analytics': [],
'reports': []
}
for component in components:
if component.startswith('Login') or component.startswith('Auth'):
modules['bootstrap'].append(component)
elif component.startswith('Admin'):
modules['admin'].append(component)
elif component.startswith('Analytics') or component.startswith('Chart'):
modules['analytics'].append(component)
elif component.startswith('Report'):
modules['reports'].append(component)
else:
modules['app-core'].append(component)
# Remove empty modules
return {k: v for k, v in modules.items() if v}
class SecurityExample:
"""Security benefits of modular loading"""
@staticmethod
def bootstrap_only_code():
"""Code that ships to all users (even unauthenticated)"""
return '''
# bootstrap.py - Compiles to bootstrap.wasm
@component
def LoginPage():
username = signal('')
password = signal('')
error = signal(None)
async def handle_login():
try:
response = await fetch('/api/login', {
'method': 'POST',
'body': json.dumps({
'username': username.value,
'password': password.value
})
})
if response.ok:
token = await response.json()
# Now load the real app
await ModuleLoader.load('app-core')
navigate('/dashboard')
else:
error.value = 'Invalid credentials'
except Exception as e:
error.value = str(e)
with Main():
with Form(onsubmit=handle_login):
yield H1('Login')
yield Input(
type='text',
placeholder='Username',
value=username,
oninput=lambda e: username.set(e.target.value)
)
yield Input(
type='password',
placeholder='Password',
value=password,
oninput=lambda e: password.set(e.target.value)
)
yield Button('Sign In', type='submit')
if error.value:
yield ErrorMessage(error.value)
# IMPORTANT: No business logic, no API endpoints, no sensitive data!
# This is ALL that unauthenticated users can see
'''
@staticmethod
def protected_module_code():
"""Code that only authenticated users receive"""
return '''
# app-core.py - Compiles to app-core.wasm
# This is NEVER sent to unauthenticated users
@component
def Dashboard():
# Business logic here
user_data = signal(None)
async def load_user_data():
# API endpoints only known to authenticated users
response = await fetch('/api/user/profile')
user_data.value = await response.json()
# ... rest of application
'''
# Demonstration
if __name__ == '__main__':
print("=== Ritz Module System ===\n")
system = RitzModuleSystem()
print("1. Module Structure:")
for name, module in system.graph.modules.items():
print(f" {name}:")
print(f" Size: {module.size_kb}KB")
print(f" Auth Required: {module.requires_auth}")
print(f" Dependencies: {module.dependencies}")
print(f" Exports: {module.exports[:2]}...")
print("\n2. Load Order for 'analytics':")
order = system.graph.get_load_order('analytics')
print(f" Must load in order: {order}")
total_size = sum(system.graph.modules[m].size_kb for m in order)
print(f" Total download: {total_size}KB")
print("\n3. Module Manifest:")
manifest = system.generate_manifest()
print(json.dumps(manifest, indent=2)[:400] + "...")
print("\n4. Security Benefits:")
print(" - Unauthenticated users only get bootstrap.wasm (50KB)")
print(" - No API endpoints or business logic exposed")
print(" - Feature modules loaded on-demand")
print(" - Each module can have different cache strategies")
print("\n5. Performance Benefits:")
print(" - Initial load: 50KB instead of 600KB+")
print(" - Aggressive caching for stable modules")
print(" - Parallel preloading of likely-needed modules")
print(" - Route-based code splitting")
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""
Simple test to debug Ritz framework
"""
from framework import (
component, signal,
Main, Div, P, Button, H1
)
@component
def SimpleTest():
"""Simplest possible test"""
with Main():
yield P('Hello World')
# Test it
import json
result = SimpleTest()
print("Result type:", type(result))
print("Result:", result)
if result:
print("\n=== HTML ===")
print(result.to_html())
print("\n=== IR ===")
print(json.dumps(result.to_ir(), indent=2))