@hypen-space/core 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/wasm-browser/README.md +197 -38
- package/wasm-browser/hypen_engine.d.ts +1 -1
- package/wasm-browser/hypen_engine.js +1 -1
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/package.json +10 -1
- package/wasm-node/README.md +197 -38
- package/wasm-node/hypen_engine.d.ts +1 -1
- package/wasm-node/hypen_engine.js +1 -1
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/package.json +10 -1
package/package.json
CHANGED
package/wasm-browser/README.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
The core reactive rendering engine for Hypen, written in Rust. Compiles to WASM for web/desktop or native binaries with UniFFI for mobile platforms.
|
|
4
4
|
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Rust
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
use hypen_engine::{Engine, ast_to_ir};
|
|
11
|
+
use hypen_parser::parse_component;
|
|
12
|
+
use serde_json::json;
|
|
13
|
+
|
|
14
|
+
let mut engine = Engine::new();
|
|
15
|
+
engine.set_render_callback(|patches| {
|
|
16
|
+
// Apply patches to your renderer
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
let ast = parse_component(r#"Column { Text("Hello") }"#)?;
|
|
20
|
+
engine.render(&ast_to_ir(&ast));
|
|
21
|
+
engine.update_state(json!({ "count": 42 }));
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### JavaScript/TypeScript (WASM)
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import init, { WasmEngine } from './wasm/hypen_engine.js';
|
|
28
|
+
await init();
|
|
29
|
+
|
|
30
|
+
const engine = new WasmEngine();
|
|
31
|
+
engine.setRenderCallback((patches) => applyPatches(patches));
|
|
32
|
+
engine.setModule('App', ['increment'], ['count'], { count: 0 });
|
|
33
|
+
engine.renderSource(`Column { Text("\${state.count}") }`);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
See [BUILD_WASM.md](./BUILD_WASM.md) for detailed WASM build instructions.
|
|
37
|
+
|
|
5
38
|
## Overview
|
|
6
39
|
|
|
7
40
|
Hypen Engine is a platform-agnostic UI engine that:
|
|
@@ -51,7 +84,7 @@ Hypen Engine is a platform-agnostic UI engine that:
|
|
|
51
84
|
- `Insert(parent, id, before?)` - Insert into tree
|
|
52
85
|
- `Move(parent, id, before?)` - Reorder node
|
|
53
86
|
- `Remove(id)` - Remove from tree
|
|
54
|
-
-
|
|
87
|
+
- Event handling is managed at the renderer level
|
|
55
88
|
|
|
56
89
|
5. **Action/Event Routing** (`src/dispatch/`)
|
|
57
90
|
- Map `@actions.*` to module handlers
|
|
@@ -67,7 +100,7 @@ Hypen Engine is a platform-agnostic UI engine that:
|
|
|
67
100
|
- Initial tree serialization
|
|
68
101
|
- Incremental patch streaming
|
|
69
102
|
- Revision tracking and optional integrity hashes
|
|
70
|
-
- JSON
|
|
103
|
+
- JSON format support
|
|
71
104
|
|
|
72
105
|
## Usage
|
|
73
106
|
|
|
@@ -75,15 +108,20 @@ Hypen Engine is a platform-agnostic UI engine that:
|
|
|
75
108
|
|
|
76
109
|
```rust
|
|
77
110
|
use hypen_engine::{Engine, ir::{Element, Value, Component}};
|
|
111
|
+
use hypen_engine::reactive::parse_binding;
|
|
112
|
+
use indexmap::IndexMap;
|
|
78
113
|
use serde_json::json;
|
|
79
114
|
|
|
80
115
|
// Create engine
|
|
81
116
|
let mut engine = Engine::new();
|
|
82
117
|
|
|
83
118
|
// Register a custom component
|
|
84
|
-
|
|
119
|
+
// Note: In practice, you'd typically parse Hypen DSL with ast_to_ir
|
|
120
|
+
engine.register_component(Component::new("Greeting", |props: IndexMap<String, serde_json::Value>| {
|
|
85
121
|
Element::new("Text")
|
|
86
|
-
.with_prop("text", Value::Binding(
|
|
122
|
+
.with_prop("text", Value::Binding(
|
|
123
|
+
parse_binding("${state.name}").expect("valid binding")
|
|
124
|
+
))
|
|
87
125
|
}));
|
|
88
126
|
|
|
89
127
|
// Set render callback
|
|
@@ -141,6 +179,10 @@ cargo test
|
|
|
141
179
|
|
|
142
180
|
### WASM (Web/Desktop)
|
|
143
181
|
|
|
182
|
+
The WASM build is fully functional and tested. See [BUILD_WASM.md](./BUILD_WASM.md) for detailed build instructions.
|
|
183
|
+
|
|
184
|
+
**Quick Start:**
|
|
185
|
+
|
|
144
186
|
```bash
|
|
145
187
|
# Install wasm-pack (one time)
|
|
146
188
|
cargo install wasm-pack
|
|
@@ -149,47 +191,60 @@ cargo install wasm-pack
|
|
|
149
191
|
./build-wasm.sh
|
|
150
192
|
|
|
151
193
|
# Or build manually for specific targets:
|
|
152
|
-
wasm-pack build --target bundler # For webpack/vite
|
|
194
|
+
wasm-pack build --target bundler # For webpack/vite/rollup
|
|
153
195
|
wasm-pack build --target nodejs # For Node.js
|
|
154
196
|
wasm-pack build --target web # For vanilla JS
|
|
155
197
|
```
|
|
156
198
|
|
|
157
|
-
Output directories
|
|
199
|
+
**Output directories:**
|
|
158
200
|
- `pkg/bundler/` - For use with bundlers (webpack, vite, rollup)
|
|
159
201
|
- `pkg/nodejs/` - For Node.js
|
|
160
202
|
- `pkg/web/` - For vanilla HTML/JS (see `example.html`)
|
|
161
203
|
|
|
204
|
+
**Build to custom directory:**
|
|
205
|
+
```bash
|
|
206
|
+
# Build directly to your renderer project
|
|
207
|
+
wasm-pack build --target bundler --out-dir ../hypen-render-bun/wasm
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The WASM binary is optimized for size (~300KB) with LTO and size optimizations enabled.
|
|
211
|
+
|
|
162
212
|
### JavaScript/TypeScript API
|
|
163
213
|
|
|
214
|
+
The WASM build provides a `WasmEngine` class with a complete API:
|
|
215
|
+
|
|
164
216
|
```typescript
|
|
165
217
|
import init, { WasmEngine } from './pkg/web/hypen_engine.js';
|
|
166
218
|
|
|
167
|
-
// Initialize WASM
|
|
219
|
+
// Initialize WASM (required before creating engine)
|
|
168
220
|
await init();
|
|
169
221
|
|
|
170
|
-
// Create engine
|
|
222
|
+
// Create engine instance
|
|
171
223
|
const engine = new WasmEngine();
|
|
172
224
|
|
|
173
|
-
// Set render callback
|
|
225
|
+
// Set render callback to receive patches
|
|
174
226
|
engine.setRenderCallback((patches) => {
|
|
175
227
|
console.log('Patches:', patches);
|
|
176
|
-
// Apply patches to your renderer
|
|
228
|
+
// Apply patches to your platform renderer
|
|
229
|
+
applyPatchesToDOM(patches);
|
|
177
230
|
});
|
|
178
231
|
|
|
179
232
|
// Register action handlers
|
|
180
233
|
engine.onAction('increment', (action) => {
|
|
181
|
-
console.log('
|
|
234
|
+
console.log('Action received:', action.name, action.payload);
|
|
235
|
+
// Handle action (e.g., update state)
|
|
236
|
+
engine.updateState({ count: action.payload.count + 1 });
|
|
182
237
|
});
|
|
183
238
|
|
|
184
|
-
// Initialize module
|
|
239
|
+
// Initialize module with state and actions
|
|
185
240
|
engine.setModule(
|
|
186
|
-
'CounterModule',
|
|
187
|
-
['increment', 'decrement'],
|
|
188
|
-
['count'],
|
|
189
|
-
{ count: 0 }
|
|
241
|
+
'CounterModule', // Module name
|
|
242
|
+
['increment', 'decrement'], // Available actions
|
|
243
|
+
['count'], // State keys
|
|
244
|
+
{ count: 0 } // Initial state
|
|
190
245
|
);
|
|
191
246
|
|
|
192
|
-
// Render Hypen DSL
|
|
247
|
+
// Render Hypen DSL source code
|
|
193
248
|
const source = `
|
|
194
249
|
Column {
|
|
195
250
|
Text("Count: \${state.count}")
|
|
@@ -198,13 +253,30 @@ const source = `
|
|
|
198
253
|
`;
|
|
199
254
|
engine.renderSource(source);
|
|
200
255
|
|
|
201
|
-
// Update state
|
|
256
|
+
// Update state (triggers reactive re-render)
|
|
202
257
|
engine.updateState({ count: 42 });
|
|
203
258
|
|
|
204
|
-
// Dispatch action
|
|
259
|
+
// Dispatch action programmatically
|
|
205
260
|
engine.dispatchAction('increment', { amount: 1 });
|
|
261
|
+
|
|
262
|
+
// Get current revision number (for remote UI)
|
|
263
|
+
const revision = engine.getRevision();
|
|
206
264
|
```
|
|
207
265
|
|
|
266
|
+
**WasmEngine API Reference:**
|
|
267
|
+
|
|
268
|
+
- `constructor()` - Create a new engine instance
|
|
269
|
+
- `renderSource(source: string)` - Render Hypen DSL source code
|
|
270
|
+
- `setRenderCallback(callback: (patches: Patch[]) => void)` - Set patch callback
|
|
271
|
+
- `setModule(name, actions, stateKeys, initialState)` - Initialize module
|
|
272
|
+
- `updateState(patch: object)` - Update state and trigger re-render
|
|
273
|
+
- `dispatchAction(name: string, payload?: any)` - Dispatch an action
|
|
274
|
+
- `onAction(name: string, handler: (action: Action) => void)` - Register action handler
|
|
275
|
+
- `getRevision(): number` - Get current revision number
|
|
276
|
+
- `setComponentResolver(resolver: (name: string, context?: string) => ResolvedComponent | null)` - Set dynamic component resolver
|
|
277
|
+
|
|
278
|
+
See [BUILD_WASM.md](./BUILD_WASM.md) for more details and examples.
|
|
279
|
+
|
|
208
280
|
### Testing WASM Build
|
|
209
281
|
|
|
210
282
|
Open `example.html` in a web server:
|
|
@@ -221,44 +293,62 @@ npx serve .
|
|
|
221
293
|
|
|
222
294
|
### Mobile (UniFFI)
|
|
223
295
|
|
|
296
|
+
UniFFI bindings for native mobile platforms are planned but not yet implemented.
|
|
297
|
+
|
|
224
298
|
```bash
|
|
225
|
-
# Generate Swift/Kotlin bindings
|
|
299
|
+
# Future: Generate Swift/Kotlin bindings
|
|
226
300
|
cargo install uniffi_bindgen
|
|
227
301
|
uniffi-bindgen generate src/hypen_engine.udl --language swift
|
|
228
302
|
uniffi-bindgen generate src/hypen_engine.udl --language kotlin
|
|
229
303
|
```
|
|
230
304
|
|
|
305
|
+
For now, mobile platforms can use the WASM build via WebView or native WASM runtimes.
|
|
306
|
+
|
|
231
307
|
## Project Structure
|
|
232
308
|
|
|
233
309
|
```
|
|
234
310
|
hypen-engine-rs/
|
|
235
311
|
├── src/
|
|
236
|
-
│ ├── lib.rs # Public API
|
|
237
|
-
│ ├── engine.rs # Main orchestrator
|
|
238
|
-
│ ├──
|
|
312
|
+
│ ├── lib.rs # Public API exports
|
|
313
|
+
│ ├── engine.rs # Main Engine orchestrator
|
|
314
|
+
│ ├── wasm.rs # WASM bindings (wasm-bindgen)
|
|
315
|
+
│ ├── state.rs # State change tracking
|
|
316
|
+
│ ├── render.rs # Dirty node rendering
|
|
317
|
+
│ ├── logger.rs # Logging utilities
|
|
239
318
|
│ ├── ir/ # IR & component expansion
|
|
319
|
+
│ │ ├── mod.rs # Module exports
|
|
240
320
|
│ │ ├── node.rs # NodeId, Element, Props, Value
|
|
241
|
-
│ │ ├── component.rs # Component registry
|
|
242
|
-
│ │
|
|
321
|
+
│ │ ├── component.rs # Component registry & resolution
|
|
322
|
+
│ │ ├── expand.rs # AST → IR lowering
|
|
323
|
+
│ │ └── children_slots_test.rs
|
|
243
324
|
│ ├── reactive/ # Reactive system
|
|
325
|
+
│ │ ├── mod.rs # Module exports
|
|
244
326
|
│ │ ├── binding.rs # ${state.*} parsing
|
|
245
327
|
│ │ ├── graph.rs # Dependency tracking
|
|
246
|
-
│ │ └── scheduler.rs # Dirty marking
|
|
328
|
+
│ │ └── scheduler.rs # Dirty marking & scheduling
|
|
247
329
|
│ ├── reconcile/ # Reconciliation
|
|
248
|
-
│ │ ├──
|
|
249
|
-
│ │ ├──
|
|
330
|
+
│ │ ├── mod.rs # Module exports
|
|
331
|
+
│ │ ├── tree.rs # Instance tree (virtual DOM)
|
|
332
|
+
│ │ ├── diff.rs # Keyed diffing algorithm
|
|
250
333
|
│ │ └── patch.rs # Patch types
|
|
251
334
|
│ ├── dispatch/ # Events & actions
|
|
335
|
+
│ │ ├── mod.rs # Module exports
|
|
252
336
|
│ │ ├── action.rs # Action dispatcher
|
|
253
337
|
│ │ └── event.rs # Event router
|
|
254
|
-
│ ├── lifecycle/ # Lifecycle
|
|
338
|
+
│ ├── lifecycle/ # Lifecycle management
|
|
339
|
+
│ │ ├── mod.rs # Module exports
|
|
255
340
|
│ │ ├── module.rs # Module lifecycle
|
|
256
341
|
│ │ ├── component.rs # Component lifecycle
|
|
257
342
|
│ │ └── resource.rs # Resource cache
|
|
258
343
|
│ └── serialize/ # Serialization
|
|
344
|
+
│ ├── mod.rs # Module exports
|
|
259
345
|
│ └── remote.rs # Remote UI protocol
|
|
260
|
-
├──
|
|
261
|
-
|
|
346
|
+
├── tests/ # Integration tests
|
|
347
|
+
├── Cargo.toml # Rust dependencies
|
|
348
|
+
├── build-wasm.sh # WASM build script
|
|
349
|
+
├── BUILD_WASM.md # Detailed WASM build docs
|
|
350
|
+
├── example.html # WASM demo page
|
|
351
|
+
└── README.md # This file
|
|
262
352
|
```
|
|
263
353
|
|
|
264
354
|
## Key Data Structures
|
|
@@ -270,7 +360,7 @@ pub struct Element {
|
|
|
270
360
|
pub props: IndexMap<String, Value>, // Properties
|
|
271
361
|
pub children: Vec<Element>, // Child elements
|
|
272
362
|
pub key: Option<String>, // For reconciliation
|
|
273
|
-
|
|
363
|
+
// Note: Event handling is done at the renderer level, not in IR
|
|
274
364
|
}
|
|
275
365
|
```
|
|
276
366
|
|
|
@@ -278,7 +368,11 @@ pub struct Element {
|
|
|
278
368
|
```rust
|
|
279
369
|
pub enum Value {
|
|
280
370
|
Static(serde_json::Value), // Literal values
|
|
281
|
-
Binding(
|
|
371
|
+
Binding(Binding), // Parsed ${state.user.name} binding
|
|
372
|
+
TemplateString { // Template with embedded bindings
|
|
373
|
+
template: String,
|
|
374
|
+
bindings: Vec<Binding>,
|
|
375
|
+
},
|
|
282
376
|
Action(String), // @actions.signIn
|
|
283
377
|
}
|
|
284
378
|
```
|
|
@@ -292,8 +386,7 @@ pub enum Patch {
|
|
|
292
386
|
Insert { parent_id, id, before_id? },
|
|
293
387
|
Move { parent_id, id, before_id? },
|
|
294
388
|
Remove { id },
|
|
295
|
-
|
|
296
|
-
DetachEvent { id, event_name },
|
|
389
|
+
// Note: Event handling is done at the renderer level
|
|
297
390
|
}
|
|
298
391
|
```
|
|
299
392
|
|
|
@@ -404,13 +497,26 @@ For client-server streaming:
|
|
|
404
497
|
# Run all tests
|
|
405
498
|
cargo test
|
|
406
499
|
|
|
407
|
-
# Run with output
|
|
500
|
+
# Run with output (useful for debugging)
|
|
408
501
|
cargo test -- --nocapture
|
|
409
502
|
|
|
410
503
|
# Test specific module
|
|
411
504
|
cargo test reactive::
|
|
505
|
+
|
|
506
|
+
# Test specific file
|
|
507
|
+
cargo test --test test_reactive_graph
|
|
508
|
+
|
|
509
|
+
# Run tests in parallel (default)
|
|
510
|
+
cargo test --jobs 4
|
|
412
511
|
```
|
|
413
512
|
|
|
513
|
+
The test suite includes:
|
|
514
|
+
- Unit tests for each module
|
|
515
|
+
- Integration tests for engine workflows
|
|
516
|
+
- WASM integration tests
|
|
517
|
+
- Reactive dependency tracking tests
|
|
518
|
+
- Reconciliation algorithm tests
|
|
519
|
+
|
|
414
520
|
## Contributing
|
|
415
521
|
|
|
416
522
|
This is part of the Hypen project. See the main repository for contribution guidelines.
|
|
@@ -419,7 +525,60 @@ This is part of the Hypen project. See the main repository for contribution guid
|
|
|
419
525
|
|
|
420
526
|
See main Hypen project for license information.
|
|
421
527
|
|
|
528
|
+
## API Reference
|
|
529
|
+
|
|
530
|
+
### Engine (Rust)
|
|
531
|
+
|
|
532
|
+
The main `Engine` struct provides the core functionality:
|
|
533
|
+
|
|
534
|
+
```rust
|
|
535
|
+
impl Engine {
|
|
536
|
+
pub fn new() -> Self;
|
|
537
|
+
pub fn register_component(&mut self, component: Component);
|
|
538
|
+
pub fn set_component_resolver<F>(&mut self, resolver: F);
|
|
539
|
+
pub fn set_module(&mut self, module: ModuleInstance);
|
|
540
|
+
pub fn set_render_callback<F>(&mut self, callback: F);
|
|
541
|
+
pub fn on_action<F>(&mut self, action_name: impl Into<String>, handler: F);
|
|
542
|
+
pub fn render(&mut self, element: &Element);
|
|
543
|
+
pub fn update_state(&mut self, state_patch: serde_json::Value);
|
|
544
|
+
pub fn notify_state_change(&mut self, change: &StateChange);
|
|
545
|
+
pub fn dispatch_action(&mut self, action: Action) -> Result<(), String>;
|
|
546
|
+
pub fn revision(&self) -> u64;
|
|
547
|
+
pub fn component_registry(&self) -> &ComponentRegistry;
|
|
548
|
+
pub fn resources(&self) -> &ResourceCache;
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Key Exports
|
|
553
|
+
|
|
554
|
+
```rust
|
|
555
|
+
pub use engine::Engine;
|
|
556
|
+
pub use ir::{ast_to_ir, Element, Value};
|
|
557
|
+
pub use lifecycle::{Module, ModuleInstance};
|
|
558
|
+
pub use reconcile::Patch;
|
|
559
|
+
pub use state::StateChange;
|
|
560
|
+
```
|
|
561
|
+
|
|
422
562
|
---
|
|
423
563
|
|
|
424
|
-
|
|
425
|
-
|
|
564
|
+
## Status
|
|
565
|
+
|
|
566
|
+
**✅ Implemented:**
|
|
567
|
+
- Core reactive rendering engine
|
|
568
|
+
- Component expansion and registry
|
|
569
|
+
- Dependency tracking and dirty marking
|
|
570
|
+
- Keyed reconciliation algorithm
|
|
571
|
+
- Patch generation
|
|
572
|
+
- Action/event dispatch system
|
|
573
|
+
- Module lifecycle management
|
|
574
|
+
- Resource caching
|
|
575
|
+
- WASM bindings (fully functional)
|
|
576
|
+
- Remote UI serialization
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Related Documentation
|
|
581
|
+
|
|
582
|
+
- [BUILD_WASM.md](./BUILD_WASM.md) - Detailed WASM build instructions
|
|
583
|
+
- [../parser/README.md](../parser/README.md) - Hypen parser documentation
|
|
584
|
+
- [../hypen-render-bun/README.md](../hypen-render-bun/README.md) - Bun renderer implementation
|
|
@@ -10,7 +10,7 @@ export function patchesToJson(patches: any): string;
|
|
|
10
10
|
export function parseToJson(source: string): string;
|
|
11
11
|
export function main(): void;
|
|
12
12
|
/**
|
|
13
|
-
* WASM-exported engine instance
|
|
13
|
+
* WASM-exported engine instance for JavaScript runtimes
|
|
14
14
|
* Uses Rc<RefCell<>> instead of Send+Sync for WASM single-threaded environment
|
|
15
15
|
*/
|
|
16
16
|
export class WasmEngine {
|
|
@@ -255,7 +255,7 @@ const WasmEngineFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
|
255
255
|
? { register: () => {}, unregister: () => {} }
|
|
256
256
|
: new FinalizationRegistry(ptr => wasm.__wbg_wasmengine_free(ptr >>> 0, 1));
|
|
257
257
|
/**
|
|
258
|
-
* WASM-exported engine instance
|
|
258
|
+
* WASM-exported engine instance for JavaScript runtimes
|
|
259
259
|
* Uses Rc<RefCell<>> instead of Send+Sync for WASM single-threaded environment
|
|
260
260
|
*/
|
|
261
261
|
export class WasmEngine {
|
|
Binary file
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypen-engine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"
|
|
4
|
+
"collaborators": [
|
|
5
|
+
"Hypen Contributors"
|
|
6
|
+
],
|
|
7
|
+
"description": "A Rust implementation of the Hypen engine",
|
|
8
|
+
"version": "0.1.2",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/hypen-lang/hypen-engine-rs"
|
|
13
|
+
},
|
|
5
14
|
"files": [
|
|
6
15
|
"hypen_engine_bg.wasm",
|
|
7
16
|
"hypen_engine.js",
|
package/wasm-node/README.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
The core reactive rendering engine for Hypen, written in Rust. Compiles to WASM for web/desktop or native binaries with UniFFI for mobile platforms.
|
|
4
4
|
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Rust
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
use hypen_engine::{Engine, ast_to_ir};
|
|
11
|
+
use hypen_parser::parse_component;
|
|
12
|
+
use serde_json::json;
|
|
13
|
+
|
|
14
|
+
let mut engine = Engine::new();
|
|
15
|
+
engine.set_render_callback(|patches| {
|
|
16
|
+
// Apply patches to your renderer
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
let ast = parse_component(r#"Column { Text("Hello") }"#)?;
|
|
20
|
+
engine.render(&ast_to_ir(&ast));
|
|
21
|
+
engine.update_state(json!({ "count": 42 }));
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### JavaScript/TypeScript (WASM)
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import init, { WasmEngine } from './wasm/hypen_engine.js';
|
|
28
|
+
await init();
|
|
29
|
+
|
|
30
|
+
const engine = new WasmEngine();
|
|
31
|
+
engine.setRenderCallback((patches) => applyPatches(patches));
|
|
32
|
+
engine.setModule('App', ['increment'], ['count'], { count: 0 });
|
|
33
|
+
engine.renderSource(`Column { Text("\${state.count}") }`);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
See [BUILD_WASM.md](./BUILD_WASM.md) for detailed WASM build instructions.
|
|
37
|
+
|
|
5
38
|
## Overview
|
|
6
39
|
|
|
7
40
|
Hypen Engine is a platform-agnostic UI engine that:
|
|
@@ -51,7 +84,7 @@ Hypen Engine is a platform-agnostic UI engine that:
|
|
|
51
84
|
- `Insert(parent, id, before?)` - Insert into tree
|
|
52
85
|
- `Move(parent, id, before?)` - Reorder node
|
|
53
86
|
- `Remove(id)` - Remove from tree
|
|
54
|
-
-
|
|
87
|
+
- Event handling is managed at the renderer level
|
|
55
88
|
|
|
56
89
|
5. **Action/Event Routing** (`src/dispatch/`)
|
|
57
90
|
- Map `@actions.*` to module handlers
|
|
@@ -67,7 +100,7 @@ Hypen Engine is a platform-agnostic UI engine that:
|
|
|
67
100
|
- Initial tree serialization
|
|
68
101
|
- Incremental patch streaming
|
|
69
102
|
- Revision tracking and optional integrity hashes
|
|
70
|
-
- JSON
|
|
103
|
+
- JSON format support
|
|
71
104
|
|
|
72
105
|
## Usage
|
|
73
106
|
|
|
@@ -75,15 +108,20 @@ Hypen Engine is a platform-agnostic UI engine that:
|
|
|
75
108
|
|
|
76
109
|
```rust
|
|
77
110
|
use hypen_engine::{Engine, ir::{Element, Value, Component}};
|
|
111
|
+
use hypen_engine::reactive::parse_binding;
|
|
112
|
+
use indexmap::IndexMap;
|
|
78
113
|
use serde_json::json;
|
|
79
114
|
|
|
80
115
|
// Create engine
|
|
81
116
|
let mut engine = Engine::new();
|
|
82
117
|
|
|
83
118
|
// Register a custom component
|
|
84
|
-
|
|
119
|
+
// Note: In practice, you'd typically parse Hypen DSL with ast_to_ir
|
|
120
|
+
engine.register_component(Component::new("Greeting", |props: IndexMap<String, serde_json::Value>| {
|
|
85
121
|
Element::new("Text")
|
|
86
|
-
.with_prop("text", Value::Binding(
|
|
122
|
+
.with_prop("text", Value::Binding(
|
|
123
|
+
parse_binding("${state.name}").expect("valid binding")
|
|
124
|
+
))
|
|
87
125
|
}));
|
|
88
126
|
|
|
89
127
|
// Set render callback
|
|
@@ -141,6 +179,10 @@ cargo test
|
|
|
141
179
|
|
|
142
180
|
### WASM (Web/Desktop)
|
|
143
181
|
|
|
182
|
+
The WASM build is fully functional and tested. See [BUILD_WASM.md](./BUILD_WASM.md) for detailed build instructions.
|
|
183
|
+
|
|
184
|
+
**Quick Start:**
|
|
185
|
+
|
|
144
186
|
```bash
|
|
145
187
|
# Install wasm-pack (one time)
|
|
146
188
|
cargo install wasm-pack
|
|
@@ -149,47 +191,60 @@ cargo install wasm-pack
|
|
|
149
191
|
./build-wasm.sh
|
|
150
192
|
|
|
151
193
|
# Or build manually for specific targets:
|
|
152
|
-
wasm-pack build --target bundler # For webpack/vite
|
|
194
|
+
wasm-pack build --target bundler # For webpack/vite/rollup
|
|
153
195
|
wasm-pack build --target nodejs # For Node.js
|
|
154
196
|
wasm-pack build --target web # For vanilla JS
|
|
155
197
|
```
|
|
156
198
|
|
|
157
|
-
Output directories
|
|
199
|
+
**Output directories:**
|
|
158
200
|
- `pkg/bundler/` - For use with bundlers (webpack, vite, rollup)
|
|
159
201
|
- `pkg/nodejs/` - For Node.js
|
|
160
202
|
- `pkg/web/` - For vanilla HTML/JS (see `example.html`)
|
|
161
203
|
|
|
204
|
+
**Build to custom directory:**
|
|
205
|
+
```bash
|
|
206
|
+
# Build directly to your renderer project
|
|
207
|
+
wasm-pack build --target bundler --out-dir ../hypen-render-bun/wasm
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The WASM binary is optimized for size (~300KB) with LTO and size optimizations enabled.
|
|
211
|
+
|
|
162
212
|
### JavaScript/TypeScript API
|
|
163
213
|
|
|
214
|
+
The WASM build provides a `WasmEngine` class with a complete API:
|
|
215
|
+
|
|
164
216
|
```typescript
|
|
165
217
|
import init, { WasmEngine } from './pkg/web/hypen_engine.js';
|
|
166
218
|
|
|
167
|
-
// Initialize WASM
|
|
219
|
+
// Initialize WASM (required before creating engine)
|
|
168
220
|
await init();
|
|
169
221
|
|
|
170
|
-
// Create engine
|
|
222
|
+
// Create engine instance
|
|
171
223
|
const engine = new WasmEngine();
|
|
172
224
|
|
|
173
|
-
// Set render callback
|
|
225
|
+
// Set render callback to receive patches
|
|
174
226
|
engine.setRenderCallback((patches) => {
|
|
175
227
|
console.log('Patches:', patches);
|
|
176
|
-
// Apply patches to your renderer
|
|
228
|
+
// Apply patches to your platform renderer
|
|
229
|
+
applyPatchesToDOM(patches);
|
|
177
230
|
});
|
|
178
231
|
|
|
179
232
|
// Register action handlers
|
|
180
233
|
engine.onAction('increment', (action) => {
|
|
181
|
-
console.log('
|
|
234
|
+
console.log('Action received:', action.name, action.payload);
|
|
235
|
+
// Handle action (e.g., update state)
|
|
236
|
+
engine.updateState({ count: action.payload.count + 1 });
|
|
182
237
|
});
|
|
183
238
|
|
|
184
|
-
// Initialize module
|
|
239
|
+
// Initialize module with state and actions
|
|
185
240
|
engine.setModule(
|
|
186
|
-
'CounterModule',
|
|
187
|
-
['increment', 'decrement'],
|
|
188
|
-
['count'],
|
|
189
|
-
{ count: 0 }
|
|
241
|
+
'CounterModule', // Module name
|
|
242
|
+
['increment', 'decrement'], // Available actions
|
|
243
|
+
['count'], // State keys
|
|
244
|
+
{ count: 0 } // Initial state
|
|
190
245
|
);
|
|
191
246
|
|
|
192
|
-
// Render Hypen DSL
|
|
247
|
+
// Render Hypen DSL source code
|
|
193
248
|
const source = `
|
|
194
249
|
Column {
|
|
195
250
|
Text("Count: \${state.count}")
|
|
@@ -198,13 +253,30 @@ const source = `
|
|
|
198
253
|
`;
|
|
199
254
|
engine.renderSource(source);
|
|
200
255
|
|
|
201
|
-
// Update state
|
|
256
|
+
// Update state (triggers reactive re-render)
|
|
202
257
|
engine.updateState({ count: 42 });
|
|
203
258
|
|
|
204
|
-
// Dispatch action
|
|
259
|
+
// Dispatch action programmatically
|
|
205
260
|
engine.dispatchAction('increment', { amount: 1 });
|
|
261
|
+
|
|
262
|
+
// Get current revision number (for remote UI)
|
|
263
|
+
const revision = engine.getRevision();
|
|
206
264
|
```
|
|
207
265
|
|
|
266
|
+
**WasmEngine API Reference:**
|
|
267
|
+
|
|
268
|
+
- `constructor()` - Create a new engine instance
|
|
269
|
+
- `renderSource(source: string)` - Render Hypen DSL source code
|
|
270
|
+
- `setRenderCallback(callback: (patches: Patch[]) => void)` - Set patch callback
|
|
271
|
+
- `setModule(name, actions, stateKeys, initialState)` - Initialize module
|
|
272
|
+
- `updateState(patch: object)` - Update state and trigger re-render
|
|
273
|
+
- `dispatchAction(name: string, payload?: any)` - Dispatch an action
|
|
274
|
+
- `onAction(name: string, handler: (action: Action) => void)` - Register action handler
|
|
275
|
+
- `getRevision(): number` - Get current revision number
|
|
276
|
+
- `setComponentResolver(resolver: (name: string, context?: string) => ResolvedComponent | null)` - Set dynamic component resolver
|
|
277
|
+
|
|
278
|
+
See [BUILD_WASM.md](./BUILD_WASM.md) for more details and examples.
|
|
279
|
+
|
|
208
280
|
### Testing WASM Build
|
|
209
281
|
|
|
210
282
|
Open `example.html` in a web server:
|
|
@@ -221,44 +293,62 @@ npx serve .
|
|
|
221
293
|
|
|
222
294
|
### Mobile (UniFFI)
|
|
223
295
|
|
|
296
|
+
UniFFI bindings for native mobile platforms are planned but not yet implemented.
|
|
297
|
+
|
|
224
298
|
```bash
|
|
225
|
-
# Generate Swift/Kotlin bindings
|
|
299
|
+
# Future: Generate Swift/Kotlin bindings
|
|
226
300
|
cargo install uniffi_bindgen
|
|
227
301
|
uniffi-bindgen generate src/hypen_engine.udl --language swift
|
|
228
302
|
uniffi-bindgen generate src/hypen_engine.udl --language kotlin
|
|
229
303
|
```
|
|
230
304
|
|
|
305
|
+
For now, mobile platforms can use the WASM build via WebView or native WASM runtimes.
|
|
306
|
+
|
|
231
307
|
## Project Structure
|
|
232
308
|
|
|
233
309
|
```
|
|
234
310
|
hypen-engine-rs/
|
|
235
311
|
├── src/
|
|
236
|
-
│ ├── lib.rs # Public API
|
|
237
|
-
│ ├── engine.rs # Main orchestrator
|
|
238
|
-
│ ├──
|
|
312
|
+
│ ├── lib.rs # Public API exports
|
|
313
|
+
│ ├── engine.rs # Main Engine orchestrator
|
|
314
|
+
│ ├── wasm.rs # WASM bindings (wasm-bindgen)
|
|
315
|
+
│ ├── state.rs # State change tracking
|
|
316
|
+
│ ├── render.rs # Dirty node rendering
|
|
317
|
+
│ ├── logger.rs # Logging utilities
|
|
239
318
|
│ ├── ir/ # IR & component expansion
|
|
319
|
+
│ │ ├── mod.rs # Module exports
|
|
240
320
|
│ │ ├── node.rs # NodeId, Element, Props, Value
|
|
241
|
-
│ │ ├── component.rs # Component registry
|
|
242
|
-
│ │
|
|
321
|
+
│ │ ├── component.rs # Component registry & resolution
|
|
322
|
+
│ │ ├── expand.rs # AST → IR lowering
|
|
323
|
+
│ │ └── children_slots_test.rs
|
|
243
324
|
│ ├── reactive/ # Reactive system
|
|
325
|
+
│ │ ├── mod.rs # Module exports
|
|
244
326
|
│ │ ├── binding.rs # ${state.*} parsing
|
|
245
327
|
│ │ ├── graph.rs # Dependency tracking
|
|
246
|
-
│ │ └── scheduler.rs # Dirty marking
|
|
328
|
+
│ │ └── scheduler.rs # Dirty marking & scheduling
|
|
247
329
|
│ ├── reconcile/ # Reconciliation
|
|
248
|
-
│ │ ├──
|
|
249
|
-
│ │ ├──
|
|
330
|
+
│ │ ├── mod.rs # Module exports
|
|
331
|
+
│ │ ├── tree.rs # Instance tree (virtual DOM)
|
|
332
|
+
│ │ ├── diff.rs # Keyed diffing algorithm
|
|
250
333
|
│ │ └── patch.rs # Patch types
|
|
251
334
|
│ ├── dispatch/ # Events & actions
|
|
335
|
+
│ │ ├── mod.rs # Module exports
|
|
252
336
|
│ │ ├── action.rs # Action dispatcher
|
|
253
337
|
│ │ └── event.rs # Event router
|
|
254
|
-
│ ├── lifecycle/ # Lifecycle
|
|
338
|
+
│ ├── lifecycle/ # Lifecycle management
|
|
339
|
+
│ │ ├── mod.rs # Module exports
|
|
255
340
|
│ │ ├── module.rs # Module lifecycle
|
|
256
341
|
│ │ ├── component.rs # Component lifecycle
|
|
257
342
|
│ │ └── resource.rs # Resource cache
|
|
258
343
|
│ └── serialize/ # Serialization
|
|
344
|
+
│ ├── mod.rs # Module exports
|
|
259
345
|
│ └── remote.rs # Remote UI protocol
|
|
260
|
-
├──
|
|
261
|
-
|
|
346
|
+
├── tests/ # Integration tests
|
|
347
|
+
├── Cargo.toml # Rust dependencies
|
|
348
|
+
├── build-wasm.sh # WASM build script
|
|
349
|
+
├── BUILD_WASM.md # Detailed WASM build docs
|
|
350
|
+
├── example.html # WASM demo page
|
|
351
|
+
└── README.md # This file
|
|
262
352
|
```
|
|
263
353
|
|
|
264
354
|
## Key Data Structures
|
|
@@ -270,7 +360,7 @@ pub struct Element {
|
|
|
270
360
|
pub props: IndexMap<String, Value>, // Properties
|
|
271
361
|
pub children: Vec<Element>, // Child elements
|
|
272
362
|
pub key: Option<String>, // For reconciliation
|
|
273
|
-
|
|
363
|
+
// Note: Event handling is done at the renderer level, not in IR
|
|
274
364
|
}
|
|
275
365
|
```
|
|
276
366
|
|
|
@@ -278,7 +368,11 @@ pub struct Element {
|
|
|
278
368
|
```rust
|
|
279
369
|
pub enum Value {
|
|
280
370
|
Static(serde_json::Value), // Literal values
|
|
281
|
-
Binding(
|
|
371
|
+
Binding(Binding), // Parsed ${state.user.name} binding
|
|
372
|
+
TemplateString { // Template with embedded bindings
|
|
373
|
+
template: String,
|
|
374
|
+
bindings: Vec<Binding>,
|
|
375
|
+
},
|
|
282
376
|
Action(String), // @actions.signIn
|
|
283
377
|
}
|
|
284
378
|
```
|
|
@@ -292,8 +386,7 @@ pub enum Patch {
|
|
|
292
386
|
Insert { parent_id, id, before_id? },
|
|
293
387
|
Move { parent_id, id, before_id? },
|
|
294
388
|
Remove { id },
|
|
295
|
-
|
|
296
|
-
DetachEvent { id, event_name },
|
|
389
|
+
// Note: Event handling is done at the renderer level
|
|
297
390
|
}
|
|
298
391
|
```
|
|
299
392
|
|
|
@@ -404,13 +497,26 @@ For client-server streaming:
|
|
|
404
497
|
# Run all tests
|
|
405
498
|
cargo test
|
|
406
499
|
|
|
407
|
-
# Run with output
|
|
500
|
+
# Run with output (useful for debugging)
|
|
408
501
|
cargo test -- --nocapture
|
|
409
502
|
|
|
410
503
|
# Test specific module
|
|
411
504
|
cargo test reactive::
|
|
505
|
+
|
|
506
|
+
# Test specific file
|
|
507
|
+
cargo test --test test_reactive_graph
|
|
508
|
+
|
|
509
|
+
# Run tests in parallel (default)
|
|
510
|
+
cargo test --jobs 4
|
|
412
511
|
```
|
|
413
512
|
|
|
513
|
+
The test suite includes:
|
|
514
|
+
- Unit tests for each module
|
|
515
|
+
- Integration tests for engine workflows
|
|
516
|
+
- WASM integration tests
|
|
517
|
+
- Reactive dependency tracking tests
|
|
518
|
+
- Reconciliation algorithm tests
|
|
519
|
+
|
|
414
520
|
## Contributing
|
|
415
521
|
|
|
416
522
|
This is part of the Hypen project. See the main repository for contribution guidelines.
|
|
@@ -419,7 +525,60 @@ This is part of the Hypen project. See the main repository for contribution guid
|
|
|
419
525
|
|
|
420
526
|
See main Hypen project for license information.
|
|
421
527
|
|
|
528
|
+
## API Reference
|
|
529
|
+
|
|
530
|
+
### Engine (Rust)
|
|
531
|
+
|
|
532
|
+
The main `Engine` struct provides the core functionality:
|
|
533
|
+
|
|
534
|
+
```rust
|
|
535
|
+
impl Engine {
|
|
536
|
+
pub fn new() -> Self;
|
|
537
|
+
pub fn register_component(&mut self, component: Component);
|
|
538
|
+
pub fn set_component_resolver<F>(&mut self, resolver: F);
|
|
539
|
+
pub fn set_module(&mut self, module: ModuleInstance);
|
|
540
|
+
pub fn set_render_callback<F>(&mut self, callback: F);
|
|
541
|
+
pub fn on_action<F>(&mut self, action_name: impl Into<String>, handler: F);
|
|
542
|
+
pub fn render(&mut self, element: &Element);
|
|
543
|
+
pub fn update_state(&mut self, state_patch: serde_json::Value);
|
|
544
|
+
pub fn notify_state_change(&mut self, change: &StateChange);
|
|
545
|
+
pub fn dispatch_action(&mut self, action: Action) -> Result<(), String>;
|
|
546
|
+
pub fn revision(&self) -> u64;
|
|
547
|
+
pub fn component_registry(&self) -> &ComponentRegistry;
|
|
548
|
+
pub fn resources(&self) -> &ResourceCache;
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Key Exports
|
|
553
|
+
|
|
554
|
+
```rust
|
|
555
|
+
pub use engine::Engine;
|
|
556
|
+
pub use ir::{ast_to_ir, Element, Value};
|
|
557
|
+
pub use lifecycle::{Module, ModuleInstance};
|
|
558
|
+
pub use reconcile::Patch;
|
|
559
|
+
pub use state::StateChange;
|
|
560
|
+
```
|
|
561
|
+
|
|
422
562
|
---
|
|
423
563
|
|
|
424
|
-
|
|
425
|
-
|
|
564
|
+
## Status
|
|
565
|
+
|
|
566
|
+
**✅ Implemented:**
|
|
567
|
+
- Core reactive rendering engine
|
|
568
|
+
- Component expansion and registry
|
|
569
|
+
- Dependency tracking and dirty marking
|
|
570
|
+
- Keyed reconciliation algorithm
|
|
571
|
+
- Patch generation
|
|
572
|
+
- Action/event dispatch system
|
|
573
|
+
- Module lifecycle management
|
|
574
|
+
- Resource caching
|
|
575
|
+
- WASM bindings (fully functional)
|
|
576
|
+
- Remote UI serialization
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Related Documentation
|
|
581
|
+
|
|
582
|
+
- [BUILD_WASM.md](./BUILD_WASM.md) - Detailed WASM build instructions
|
|
583
|
+
- [../parser/README.md](../parser/README.md) - Hypen parser documentation
|
|
584
|
+
- [../hypen-render-bun/README.md](../hypen-render-bun/README.md) - Bun renderer implementation
|
|
@@ -10,7 +10,7 @@ export function patchesToJson(patches: any): string;
|
|
|
10
10
|
export function parseToJson(source: string): string;
|
|
11
11
|
export function main(): void;
|
|
12
12
|
/**
|
|
13
|
-
* WASM-exported engine instance
|
|
13
|
+
* WASM-exported engine instance for JavaScript runtimes
|
|
14
14
|
* Uses Rc<RefCell<>> instead of Send+Sync for WASM single-threaded environment
|
|
15
15
|
*/
|
|
16
16
|
export class WasmEngine {
|
|
@@ -249,7 +249,7 @@ const WasmEngineFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
|
249
249
|
? { register: () => {}, unregister: () => {} }
|
|
250
250
|
: new FinalizationRegistry(ptr => wasm.__wbg_wasmengine_free(ptr >>> 0, 1));
|
|
251
251
|
/**
|
|
252
|
-
* WASM-exported engine instance
|
|
252
|
+
* WASM-exported engine instance for JavaScript runtimes
|
|
253
253
|
* Uses Rc<RefCell<>> instead of Send+Sync for WASM single-threaded environment
|
|
254
254
|
*/
|
|
255
255
|
class WasmEngine {
|
|
Binary file
|
package/wasm-node/package.json
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypen-engine",
|
|
3
|
-
"
|
|
3
|
+
"collaborators": [
|
|
4
|
+
"Hypen Contributors"
|
|
5
|
+
],
|
|
6
|
+
"description": "A Rust implementation of the Hypen engine",
|
|
7
|
+
"version": "0.1.2",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/hypen-lang/hypen-engine-rs"
|
|
12
|
+
},
|
|
4
13
|
"files": [
|
|
5
14
|
"hypen_engine_bg.wasm",
|
|
6
15
|
"hypen_engine.js",
|