# Hypen Engine
[](https://www.rust-lang.org/)
[](https://webassembly.org/)
[](../LICENSE)
The core reactive rendering engine for Hypen, written in Rust. Compiles to WASM for web/desktop or native binaries with UniFFI for mobile platforms.
## Quick Start
### Rust
```rust
use hypen_engine::{Engine, ast_to_ir};
use hypen_parser::parse_component;
use serde_json::json;
let mut engine = Engine::new();
});
let ast = parse_component(r#"Column { Text("Hello") }"#)?;
engine.render(&ast_to_ir(&ast));
engine.update_state(json!({ "count": 42 }));
```
### JavaScript/TypeScript (WASM)
```typescript
import init, { WasmEngine } from './wasm/hypen_engine.js';
await init();
const engine = new WasmEngine();
engine.setRenderCallback((patches) => applyPatches(patches));
engine.setModule('App', ['increment'], ['count'], { count: 0 });
engine.renderSource(`Column { Text("\${state.count}") }`);
```
See [BUILD_WASM.md](./BUILD_WASM.md) for detailed WASM build instructions.
## Overview
Hypen Engine is a platform-agnostic UI engine that:
- **Expands** Hypen DSL components into an intermediate representation (IR)
- **Tracks** reactive dependencies between state and UI nodes
- **Reconciles** UI trees efficiently using keyed diffing
- **Generates** minimal platform-agnostic patches for renderers
- **Routes** actions and events between UI and application logic
- **Serializes** for Remote UI scenarios (client-server streaming)
## Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Hypen Engine │
├─────────────────────────────────────────────────────────┤
│ Parser → IR → Reactive Graph → Reconciler → Patches │
│ ↓ ↓ │
│ Component Registry Platform Renderer│
│ Dependency Tracking (Web/iOS/Android)│
│ State Management │
└─────────────────────────────────────────────────────────┘
```
### Core Systems
1. **IR & Component Expansion** (`src/ir/`)
- Canonical intermediate representation
- Component registry and resolution
- Props/slots expansion with defaults
- Stable NodeId generation
2. **Reactive System** (`src/reactive/`)
- Dependency graph tracking `${state.*}` bindings
- Dirty marking on state changes
- Scheduling for efficient updates
3. **Reconciliation** (`src/reconcile/`)
- Virtual instance tree (no platform objects)
- Keyed children diffing algorithm
- Minimal patch generation
4. **Patch Types** (Platform-agnostic):
- `Create(id, type, props)` - Create new node
- `SetProp(id, name, value)` - Update property
- `SetText(id, text)` - Update text content
- `Insert(parent, id, before?)` - Insert into tree
- `Move(parent, id, before?)` - Reorder node
- `Remove(id)` - Remove from tree
- Event handling is managed at the renderer level
5. **Action/Event Routing** (`src/dispatch/`)
- Map `@actions.*` to module handlers
- Forward UI events (click, input, etc.)
- Stable dispatch contract for SDKs
6. **Lifecycle Management** (`src/lifecycle/`)
- Module lifecycle (created/destroyed)
- Component lifecycle (mount/unmount)
- Resource cache (images/fonts) with pluggable fetcher
7. **Remote UI Serialization** (`src/serialize/`)
- Initial tree serialization
- Incremental patch streaming
- Revision tracking and optional integrity hashes
- JSON format support
## Usage
### Basic Example
```rust
use hypen_engine::{Engine, ir::{Element, Value, Component}};
use hypen_engine::reactive::parse_binding;
use indexmap::IndexMap;
use serde_json::json;
// Create engine
let mut engine = Engine::new();
// Register a custom component
// Note: In practice, you'd typically parse Hypen DSL with ast_to_ir
.with_prop("text", Value::Binding(
parse_binding("${state.name}").expect("valid binding")
))
}));
// Set render callback
println!("Patch: {:?}", patch);
}
});
// Register action handler
engine.on_action("greet", |action| {
println!("Hello from action: {:?}", action);
});
// Render UI
let ui = Element::new("Column")
.with_child(Element::new("Greeting"));
engine.render(&ui);
// Update state
engine.update_state(json!({
"name": "Alice"
}));
```
### With Module Host
```rust
use hypen_engine::lifecycle::{Module, ModuleInstance};
// Create module definition
let module = Module::new("ProfilePage")
.with_actions(vec!["signIn".to_string(), "signOut".to_string()])
.with_state_keys(vec!["user".to_string()])
.with_persist(true);
// Create module instance
let instance = ModuleInstance::new(
module,
json!({ "user": null })
);
engine.set_module(instance);
```
## Compilation Targets
### Native (Development)
```bash
cargo build
cargo test
```
### WASM (Web/Desktop)
The WASM build is fully functional and tested. See [BUILD_WASM.md](./BUILD_WASM.md) for detailed build instructions.
**Quick Start:**
```bash
# Install wasm-pack (one time)
cargo install wasm-pack
# Build for all WASM targets
./build-wasm.sh
# Or build manually for specific targets:
wasm-pack build --target bundler # For webpack/vite/rollup
wasm-pack build --target nodejs # For Node.js
wasm-pack build --target web # For vanilla JS
```
**Output directories:**
- `pkg/bundler/` - For use with bundlers (webpack, vite, rollup)
- `pkg/nodejs/` - For Node.js
- `pkg/web/` - For vanilla HTML/JS (see `example.html`)
**Build to custom directory:**
```bash
# Build directly to your renderer project
wasm-pack build --target bundler --out-dir ../hypen-render-bun/wasm
```
The WASM binary is optimized for size (~300KB) with LTO and size optimizations enabled.
### JavaScript/TypeScript API
The WASM build provides a `WasmEngine` class with a complete API:
```typescript
import init, { WasmEngine } from './pkg/web/hypen_engine.js';
// Initialize WASM (required before creating engine)
await init();
// Create engine instance
const engine = new WasmEngine();
// Set render callback to receive patches
engine.setRenderCallback((patches) => {
console.log('Patches:', patches);
// Apply patches to your platform renderer
applyPatchesToDOM(patches);
});
// Register action handlers
engine.onAction('increment', (action) => {
console.log('Action received:', action.name, action.payload);
// Handle action (e.g., update state)
engine.updateState({ count: action.payload.count + 1 });
});
// Initialize module with state and actions
engine.setModule(
'CounterModule', // Module name
['increment', 'decrement'], // Available actions
['count'], // State keys
{ count: 0 } // Initial state
);
// Render Hypen DSL source code
const source = `
Column {
Text("Count: \${state.count}")
Button("@actions.increment") { Text("+1") }
}
`;
engine.renderSource(source);
// Update state (triggers reactive re-render)
engine.updateState({ count: 42 });
// Dispatch action programmatically
engine.dispatchAction('increment', { amount: 1 });
// Get current revision number (for remote UI)
const revision = engine.getRevision();
```
**WasmEngine API Reference:**
- `constructor()` - Create a new engine instance
- `renderSource(source: string)` - Render Hypen DSL source code
- `setRenderCallback(callback: (patches: Patch[]) => void)` - Set patch callback
- `setModule(name, actions, stateKeys, initialState)` - Initialize module
- `updateState(patch: object)` - Update state and trigger re-render
- `dispatchAction(name: string, payload?: any)` - Dispatch an action
- `onAction(name: string, handler: (action: Action) => void)` - Register action handler
- `getRevision(): number` - Get current revision number
- `setComponentResolver(resolver: (name: string, context?: string) => ResolvedComponent | null)` - Set dynamic component resolver
See [BUILD_WASM.md](./BUILD_WASM.md) for more details and examples.
### Testing WASM Build
Open `example.html` in a web server:
```bash
# Using Python
python3 -m http.server 8000
# Using Node.js
npx serve .
# Then visit: http://localhost:8000/example.html
```
### Mobile (UniFFI)
UniFFI bindings for native mobile platforms are planned but not yet implemented.
```bash
# Future: Generate Swift/Kotlin bindings
cargo install uniffi_bindgen
uniffi-bindgen generate src/hypen_engine.udl --language swift
uniffi-bindgen generate src/hypen_engine.udl --language kotlin
```
For now, mobile platforms can use the WASM build via WebView or native WASM runtimes.
## Project Structure
```
hypen-engine-rs/
├── src/
│ ├── lib.rs # Public API exports
│ ├── engine.rs # Main Engine orchestrator
│ ├── wasm.rs # WASM bindings (wasm-bindgen)
│ ├── state.rs # State change tracking
│ ├── render.rs # Dirty node rendering
│ ├── logger.rs # Logging utilities
│ ├── ir/ # IR & component expansion
│ │ ├── mod.rs # Module exports
│ │ ├── node.rs # NodeId, Element, Props, Value
│ │ ├── component.rs # Component registry & resolution
│ │ ├── expand.rs # AST → IR lowering
│ │ └── children_slots_test.rs
│ ├── reactive/ # Reactive system
│ │ ├── mod.rs # Module exports
│ │ ├── binding.rs # ${state.*} parsing
│ │ ├── graph.rs # Dependency tracking
│ │ └── scheduler.rs # Dirty marking & scheduling
│ ├── reconcile/ # Reconciliation
│ │ ├── mod.rs # Module exports
│ │ ├── tree.rs # Instance tree (virtual DOM)
│ │ ├── diff.rs # Keyed diffing algorithm
│ │ └── patch.rs # Patch types
│ ├── dispatch/ # Events & actions
│ │ ├── mod.rs # Module exports
│ │ ├── action.rs # Action dispatcher
│ │ └── event.rs # Event router
│ ├── lifecycle/ # Lifecycle management
│ │ ├── mod.rs # Module exports
│ │ ├── module.rs # Module lifecycle
│ │ ├── component.rs # Component lifecycle
│ │ └── resource.rs # Resource cache
│ └── serialize/ # Serialization
│ ├── mod.rs # Module exports
│ └── remote.rs # Remote UI protocol
├── tests/ # Integration tests
├── Cargo.toml # Rust dependencies
├── build-wasm.sh # WASM build script
├── BUILD_WASM.md # Detailed WASM build docs
├── example.html # WASM demo page
└── README.md # This file
```
## Key Data Structures
### Element (IR Node)
```rust
pub struct Element {
pub element_type: String, // "Column", "Text", etc.
pub props: IndexMap<String, Value>, // Properties
pub children: Vec<Element>, // Child elements
pub key: Option<String>, // For reconciliation
// Note: Event handling is done at the renderer level, not in IR
}
```
### Value (Props)
```rust
pub enum Value {
Static(serde_json::Value), // Literal values
Binding(Binding), // Parsed ${state.user.name} binding
TemplateString { // Template with embedded bindings
template: String,
bindings: Vec<Binding>,
},
Action(String), // @actions.signIn
}
```
### Patch (Output)
```rust
pub enum Patch {
Create { id, element_type, props },
SetProp { id, name, value },
SetText { id, text },
Insert { parent_id, id, before_id? },
Move { parent_id, id, before_id? },
Remove { id },
// Note: Event handling is done at the renderer level
}
```
## Integration with Parser
The engine integrates with the Hypen parser from `../parser`:
```rust
use hypen_parser::parse_component;
use hypen_engine::ast_to_ir;
let source = r#"
Column {
Text("Hello, ${state.name}")
Button("@actions.greet") { Text("Greet") }
}
"#;
let ast = parse_component(source)?;
let element = ast_to_ir(&ast); // Convert AST → IR
engine.render(&element);
```
### Full Example with Parser
```rust
use hypen_engine::{Engine, ast_to_ir};
use hypen_parser::parse_component;
use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = Engine::new();
// Set render callback
engine.set_render_callback(|patches| {
println!("Patches: {:#?}", patches);
});
// Parse Hypen DSL
let source = r#"
Column {
Text("Count: ${state.count}")
Button("@actions.increment") { Text("+1") }
}
"#;
let ast = parse_component(source)?;
let element = ast_to_ir(&ast);
// Render
engine.render(&element);
// Update state
engine.update_state(json!({"count": 42}));
Ok(())
}
```
## Performance Considerations
- **Keyed reconciliation**: Use `key` props for list items to minimize DOM churn
- **Dependency tracking**: Only re-render nodes affected by state changes
- **Lazy evaluation**: Bindings are resolved on-demand during reconciliation
- **Resource caching**: Images/fonts are cached with configurable eviction
## Remote UI Protocol
For client-server streaming:
```json
// Initial tree (client connects)
{
"type": "initialTree",
"module": "ProfilePage",
"state": { "user": null },
"patches": [...],
"revision": 0
}
// State update (server → client)
{
"type": "stateUpdate",
"module": "ProfilePage",
"state": { "user": { "name": "Alice" } }
}
// Incremental patches (server → client)
{
"type": "patch",
"module": "ProfilePage",
"patches": [{ "type": "setProp", ... }],
"revision": 42
}
// Action dispatch (client → server)
{
"type": "dispatchAction",
"module": "ProfilePage",
"action": "signIn",
"payload": { "provider": "google" }
}
```
## Testing
```bash
# Run all tests
cargo test
# Run with output (useful for debugging)
cargo test -- --nocapture
# Test specific module
cargo test reactive::
# Test specific file
cargo test --test test_reactive_graph
# Run tests in parallel (default)
cargo test --jobs 4
```
The test suite includes:
- Unit tests for each module
- Integration tests for engine workflows
- WASM integration tests
- Reactive dependency tracking tests
- Reconciliation algorithm tests
## Contributing
This is part of the Hypen project. See the main repository for contribution guidelines.
## License
See main Hypen project for license information.
## API Reference
### Engine (Rust)
The main `Engine` struct provides the core functionality:
```rust
impl Engine {
pub fn new() -> Self;
pub fn register_component(&mut self, component: Component);
pub fn set_component_resolver<F>(&mut self, resolver: F);
pub fn set_module(&mut self, module: ModuleInstance);
pub fn set_render_callback<F>(&mut self, callback: F);
pub fn on_action<F>(&mut self, action_name: impl Into<String>, handler: F);
pub fn render(&mut self, element: &Element);
pub fn update_state(&mut self, state_patch: serde_json::Value);
pub fn notify_state_change(&mut self, change: &StateChange);
pub fn dispatch_action(&mut self, action: Action) -> Result<(), String>;
pub fn revision(&self) -> u64;
pub fn component_registry(&self) -> &ComponentRegistry;
pub fn resources(&self) -> &ResourceCache;
}
```
### Key Exports
```rust
pub use engine::Engine;
pub use ir::{ast_to_ir, Element, Value};
pub use lifecycle::{Module, ModuleInstance};
pub use reconcile::Patch;
pub use state::StateChange;
```
---
## Status
**✅ Implemented:**
- Core reactive rendering engine
- Component expansion and registry
- Dependency tracking and dirty marking
- Keyed reconciliation algorithm
- Patch generation
- Action/event dispatch system
- Module lifecycle management
- Resource caching
- WASM bindings (fully functional)
- Remote UI serialization
---
## Related Documentation
- [BUILD_WASM.md](./BUILD_WASM.md) - Detailed WASM build instructions
- [../parser/README.md](../parser/README.md) - Hypen parser documentation
- [../hypen-render-bun/README.md](../hypen-render-bun/README.md) - Bun renderer implementation