Rue is a reactive UI framework for building web applications, written in pure Rust and compiled to WebAssembly. It draws heavy inspiration from Vue 3's Composition API, offering a familiar developer experience:
- Signals — reactive state management (like
ref()) - Computed values — derived reactive state (like
computed()) - Effects — auto-tracking side effects (like
watchEffect()) - Virtual DOM — efficient diff/patch algorithm with keyed reconciliation
html!macro — declarative templates in Rust (like Vue SFC templates)- Component trait — lifecycle-managed components with
init(),render(),mounted()
Features
| Feature | Description |
|---|---|
| ⚡ Reactive System | Signal-based reactivity with automatic dependency tracking |
| 🖼️ Virtual DOM | Efficient diffing/patching with in-place text/attribute updates |
| 🔑 Keyed Reconciliation | Longest-Increasing-Subsequence algorithm (same as Vue 3) |
📦 html! Macro |
Compile-time HTML-to-VNode conversion with event support |
| 🧩 Component Trait | Lifecycle hooks: init(), render(), mounted(), should_update() |
| 🛠️ Builder API | Type-safe programmatic DOM construction |
| 🌐 WASM Target | Compiles to WebAssembly via wasm-bindgen |
| 🎨 Tailwind CSS Compatible | Works seamlessly with utility CSS frameworks |
Quick Start
Prerequisites
Clone & Build
# Build the landing page example
# Serve it
# Open http://localhost:8080
Your First App
use *;
use html;
// Define a component
// Mount it
Core Concepts
Reactivity (Vue 3 Composition API Pattern)
// Signal = ref()
let count = signal;
count.get; // Read value (auto-tracks in effects)
count.set; // Set value (triggers dependents)
count.update; // Mutate in place
// Computed = computed()
let doubled = computed;
// Effect = watchEffect()
effect;
Component Model
Components are structs implementing the Component trait (similar to Vue 3 Options API or <script setup>):
You can also use the functional component style (like <script setup>):
thread_local!
Virtual DOM
Re-renders go through a three-level diff/patch engine:
patch_node()— compare node types (same type → patch; different → replace)patch_element()— attribute diffing, event listener lifecyclepatch_children()— keyed (LIS algorithm) or un-keyed reconciliation
This ensures minimal DOM mutations — text nodes update in-place, attributes are diffed, event listeners are properly cleaned up, and scroll position / focus is preserved.
html! Macro Syntax
| Syntax | Description |
|---|---|
<div>...</div> |
Element with children |
<br /> |
Self-closing element |
<div class="foo"> |
Static attribute |
<div class={expr}> |
Dynamic attribute (auto .to_string()) |
<button on:click={handler}> |
Event listener |
{"text"} |
Text content (recommended) |
{vnode: expr} |
Embed a VNode directly |
<>...</> |
Fragment |
Note: Always wrap text in
{"..."}expressions — the proc-macro tokenizer handles Rust tokens, not general unicode text.
Builder API (Alternative to html!)
element
.class
.attr
.on
.child
.children
.build
Triggering Updates
After mutating component state, call update_app() to trigger the virtual-DOM patch cycle:
API Summary
| Function / Type | Description | Vue 3 Equivalent |
|---|---|---|
signal(value) |
Create a reactive Signal<T> |
ref() |
| `computed( | ...)` | |
| `effect( | ...)` | |
Signal<T> |
Cloneable reactive value with get(), set(), update() |
Ref<T> |
App::new(selector, render_fn) |
Create app with render closure | createApp() |
App::from_component(selector, component) |
Create app from Component trait |
defineComponent() |
app.mount() |
Initial render and DOM mounting | mount() |
app.update() |
Diff/patch re-render cycle | nextTick() + re-render |
VNode |
Virtual DOM node enum | VNode |
html! { ... } |
HTML template macro | Vue SFC <template> |
Project Structure
rue/
├── Cargo.toml # Workspace root
├── core/ # rue-core library
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # Public API re-exports
│ ├── component.rs # Component trait
│ ├── app.rs # App creation & mounting
│ ├── reactive/
│ │ ├── mod.rs
│ │ ├── context.rs # Global tracking context
│ │ ├── signal.rs # Signal<T> implementation
│ │ ├── computed.rs # Computed<T> implementation
│ │ └── effect.rs # Effect implementation
│ └── node/
│ ├── mod.rs # VNode, VElement, VElementBuilder
│ ├── mount.rs # DOM mounting
│ ├── patch.rs # Diff/patch algorithm
│ └── children.rs # Keyed reconciliation (LIS)
├── macros/ # rue-macros proc-macro crate
│ ├── Cargo.toml
│ └── src/lib.rs # html! macro implementation
└── examples/
└── landing/ # Full landing page example
├── Cargo.toml
├── index.html
└── src/
├── lib.rs # Entry point
└── components/
├── mod.rs
├── navbar.rs
├── hero.rs
├── features.rs
└── footer.rs
Data Flow
User clicks button
→ event handler fires
→ Signal.set(new_value)
→ triggers dependents
→ update_app()
→ app.update()
→ root_render() composes component functions
→ Each component reads its Signals
→ Returns new VNode tree via html! macro
→ patch_node(old_vnode, new_vnode)
→ patch_element (attributes, events, children)
→ patch_attributes (HashMap diff)
→ patch_event_listeners (Closure lifecycle)
→ patch_children (keyed via LIS)
→ mount_to_dom (new nodes)
→ patch_node recursively
→ Real DOM updated efficiently
Building & Running
# Build the core library (WASM)
# Build the landing example
# Serve locally
# → http://localhost:8080
# Or use any HTTP server
Development within a larger project
Add rue-core and rue-macros to your Cargo.toml:
[]
= { = "../rue/core" }
= { = "../rue/macros" }
= "0.2"
Dependencies
| Crate | Version | Purpose |
|---|---|---|
wasm-bindgen |
0.2.100+ | JS/WASM interop |
web-sys |
0.3 | DOM API bindings |
js-sys |
0.3 | JavaScript types |
console_error_panic_hook |
0.1 | Better WASM panic messages |
proc-macro2 / quote / syn |
2.x | Procedural macro infrastructure |
Comparison with Vue 3
| Concept | Vue 3 | Rue |
|---|---|---|
| Reactive value | ref() |
Signal::new() |
| Derived value | computed() |
Computed::new() |
| Side effect | watchEffect() |
Effect::new() |
| Template | SFC <template> |
html! macro |
| Component state | setup() with ref |
thread_local! or struct fields |
| State mutation | value++ |
s.set(new_value) |
| Trigger update | Automatic | Manual update_app() |
| Virtual DOM | Automatic | patch_node() on app.update() |
| Lifecycle | onMounted etc. |
mounted() trait method |
Example
The landing example is a port of a Vue 3 landing page, showcasing:
- NavBar — responsive navigation with mobile menu toggle (local state via
Signal<bool>) - HeroSection — stateless hero banner with gradient background
- FeaturesSection — feature cards with icons and descriptions
- FooterSection — footer with links and social icons
Built With Code Architect
This project was built using Code Architect — an AI-powered development assistant that helps design, implement, and evolve software projects.
The project's architecture, conventions, and design decisions are documented in the integrated knowledge base at .coder/docs/. This documentation serves as the source of truth for the project and guides all development work.
License
This project is licensed under the MIT License. See the LICENSE file for details.