| Crates.io | hypen-engine |
| lib.rs | hypen-engine |
| version | 0.1.4 |
| created_at | 2025-12-10 15:10:29.018963+00 |
| updated_at | 2025-12-19 15:30:52.379039+00 |
| description | A Rust implementation of the Hypen engine |
| homepage | |
| repository | https://github.com/hypen-lang/hypen-engine-rs |
| max_upload_size | |
| id | 1978227 |
| size | 728,403 |
The core reactive rendering engine for Hypen, written in Rust. Compiles to WASM for web/desktop or native binaries with UniFFI for mobile platforms.
use hypen_engine::{Engine, ast_to_ir};
use hypen_parser::parse_component;
use serde_json::json;
let mut engine = Engine::new();
engine.set_render_callback(|patches| {
// Apply patches to your renderer
});
let ast = parse_component(r#"Column { Text("Hello") }"#)?;
engine.render(&ast_to_ir(&ast));
engine.update_state(json!({ "count": 42 }));
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 for detailed WASM build instructions.
Hypen Engine is a platform-agnostic UI engine that:
┌─────────────────────────────────────────────────────────┐
│ Hypen Engine │
├─────────────────────────────────────────────────────────┤
│ Parser → IR → Reactive Graph → Reconciler → Patches │
│ ↓ ↓ │
│ Component Registry Platform Renderer│
│ Dependency Tracking (Web/iOS/Android)│
│ State Management │
└─────────────────────────────────────────────────────────┘
IR & Component Expansion (src/ir/)
Reactive System (src/reactive/)
${state.*} bindingsReconciliation (src/reconcile/)
Patch Types (Platform-agnostic):
Create(id, type, props) - Create new nodeSetProp(id, name, value) - Update propertySetText(id, text) - Update text contentInsert(parent, id, before?) - Insert into treeMove(parent, id, before?) - Reorder nodeRemove(id) - Remove from treeAction/Event Routing (src/dispatch/)
@actions.* to module handlersLifecycle Management (src/lifecycle/)
Remote UI Serialization (src/serialize/)
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
engine.register_component(Component::new("Greeting", |props: IndexMap<String, serde_json::Value>| {
Element::new("Text")
.with_prop("text", Value::Binding(
parse_binding("${state.name}").expect("valid binding")
))
}));
// Set render callback
engine.set_render_callback(|patches| {
for patch in patches {
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"
}));
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);
cargo build
cargo test
The WASM build is fully functional and tested. See BUILD_WASM.md for detailed build instructions.
Quick Start:
# 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.jspkg/web/ - For vanilla HTML/JS (see example.html)Build to custom directory:
# 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.
The WASM build provides a WasmEngine class with a complete API:
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 instancerenderSource(source: string) - Render Hypen DSL source codesetRenderCallback(callback: (patches: Patch[]) => void) - Set patch callbacksetModule(name, actions, stateKeys, initialState) - Initialize moduleupdateState(patch: object) - Update state and trigger re-renderdispatchAction(name: string, payload?: any) - Dispatch an actiononAction(name: string, handler: (action: Action) => void) - Register action handlergetRevision(): number - Get current revision numbersetComponentResolver(resolver: (name: string, context?: string) => ResolvedComponent | null) - Set dynamic component resolverSee BUILD_WASM.md for more details and examples.
Open example.html in a web server:
# Using Python
python3 -m http.server 8000
# Using Node.js
npx serve .
# Then visit: http://localhost:8000/example.html
UniFFI bindings for native mobile platforms are planned but not yet implemented.
# 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.
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
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
}
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
}
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
}
The engine integrates with the Hypen parser from ../parser:
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);
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(())
}
key props for list items to minimize DOM churnFor client-server streaming:
// 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" }
}
# 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:
This is part of the Hypen project. See the main repository for contribution guidelines.
See main Hypen project for license information.
The main Engine struct provides the core functionality:
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;
}
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;
✅ Implemented: