Initial commit - some junk
This commit is contained in:
commit
316c090778
9 files changed
+2272
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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!")
|
||||||
@@ -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
@@ -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
@@ -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
|
||||||
@@ -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")
|
||||||
@@ -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))
|
||||||
Reference in new issue
Block a user