Skip to content

elcharitas/momenta

Repository files navigation

Momenta

Simple and performant reactivity for building user interfaces

A fine-grained reactive framework for Rust that makes it simple to build high-performance, reactive user interfaces using Rust's type system and ownership model.

Crates.io Documentation License Coverage Status

use momenta::prelude::*;

#[component]
fn Counter() -> Node {
    let count = create_signal(0);
    
    rsx! {
        <div class="counter">
            <h1>"Count: " {count}</h1>
            <button on:click={move |_| count += 1}>"Increment"</button>
        </div>
    }
}

Features

Element-Level Reactivity

Fine-grained reactivity that automatically tracks dependencies and updates only what has changed.

Type-Safe Components

Leverage Rust's type system for compile-time guarantees and better developer experience.

Lightweight & Fast

Small runtime with minimal overhead. Your apps stay fast and bundle sizes stay small.

Familiar API

Inspired by React with a Rust-first approach to reactive programming.

SSR Ready

Buffered and chunked server-side rendering are available through the momenta-ssr crate, with adapters for Axum, Actix, and Hyper.

Composable Primitives

Build complex UIs from simple, reusable reactive primitives:

  • Signals - Fine-grained reactive state
  • Computed Signals - Derived values with automatic memoization
  • Effects - Side effect management
  • Resources - Async data loading with built-in loading/error states

Quick Start

Add Momenta to your Cargo.toml:

[dependencies]
momenta = "0.2"

Create your first component:

use momenta::prelude::*;

#[component]
fn App() -> Node {
    let count = create_signal(0);
    
    rsx! {
        <div>
            <h1>"Counter: " {count}</h1>
            <button on:click={move |_| count += 1}>
                "Increment"
            </button>
            <button on:click={move |_| count -= 1}>
                "Decrement"
            </button>
        </div>
    }
}

fn main() {
    momenta::dom::render_root::<App>("#app");
}

Why Momenta?

I started this project while attempting to transit my portfolio from Next.js to Rust. I tried using dioxus, yew, and hypertext, but I found them to be too complex and verbose for my needs. I wanted a simple and intuitive way to write HTML-like templates in Rust, while still leveraging the full power of Rust's type system.

Momenta aims to provide:

  • Simplicity - Easy to learn, easy to use
  • Performance - Fine-grained reactivity means minimal updates
  • Type Safety - Leverage Rust's type system for compile-time guarantees
  • Familiarity - React-like API that's easy to pick up

Core Concepts

Signals - Reactive State

let count = create_signal(0);
count.set(5);           // Set value
let value = count.get(); // Get value
count += 1;             // Update with operators

Computed Signals - Derived Values

let count = create_signal(0);
let doubled = create_computed(move || count.get() * 2);
// or use the derive method
let tripled = count.derive(|&n| n * 3);

Effects - Side Effects

create_effect(|| {
    console::log!("Count changed to: {}", count.get());
});

Resources - Async Data

let user = create_resource(|| async {
    fetch_user_data().await
});

rsx! {
    <div>
        {when!(user.loading() => <p>"Loading..."</p>
        else when!(user.error().is_some() => <p>"Error loading user"</p>)
        else <p>"User: " {user.get().unwrap()}</p>)}
    </div>
}

Components - Reusable UI

pub struct ButtonProps {
    pub label: &'static str,
    pub on_click: Box<dyn Fn()>,
}

#[component]
fn Button(props: &ButtonProps) -> Node {
    rsx! {
        <button on:click={move |_| (props.on_click)()}>
            {props.label}
        </button>
    }
}

Control Flow - Conditionals & Lists

// Conditional rendering
{when!(show => <p>"Visible"</p> else <p>"Hidden"</p>)}

// List rendering
let items = create_signal(vec!["Apple", "Banana", "Cherry"]);
rsx! {
    <ul>
        {items.map(|item| <li>{item}</li>)}
    </ul>
}

Feature Flags

Enable optional features in your Cargo.toml:

[dependencies]
momenta = { version = "0.2", features = ["full-reactivity"] }

Available features:

  • dom - All HTML elements with DOM rendering (default)
  • wasm - WebAssembly support for browser rendering (default)
  • computed - Computed signals support
  • memoization - Memoization utilities
  • full-reactivity - All reactive features (includes computed + memoization, default)

For server-side rendering without DOM, use momenta-ssr:

[dependencies]
momenta-ssr = "0.2"

Basic SSR usage:

use momenta::prelude::*;
use momenta_ssr::render_to_string;

let html = render_to_string(|| {
    rsx!(<div><h1>"Hello from Momenta SSR"</h1></div>)
});

SSR and Hydration

Momenta's server API is intentionally small:

  • render_to_string for buffered HTML output
  • render_to_chunks for chunked or streamed HTML output
  • framework adapters in momenta-ssr for Axum, Actix, and Hyper
  • render_to_hydration_string when the client should resume from server markup

Hydratable rendering emits stable data-momenta-* markers and can embed serialized request data in an inline JSON script.

use momenta::prelude::*;
use momenta_ssr::{render_to_hydration_string, HydrationOptions};

let html = render_to_hydration_string(
    || rsx!(<div id="app-shell"><h1>"Hello"</h1></div>),
    HydrationOptions {
        state_json: Some(r#"{"user":"jon"}"#.into()),
        ..HydrationOptions::default()
    },
);

On the client, hydrate the existing DOM instead of replacing it:

use momenta::prelude::*;

#[component]
fn App() -> Node {
    rsx!(<div id="app-shell"><h1>"Hello"</h1></div>)
}

fn main() {
    let request_data = read_default_hydration_data();
    let _ = request_data;
    hydrate_root::<App>("#app");
}

If you do not need hydration, keep using render_to_string on the server and render_root in the browser.

Streaming is just as small:

use momenta::prelude::*;
use momenta_ssr::render_to_chunks;

let chunks = render_to_chunks(
    || rsx!(<main><h1>"Streamed"</h1></main>),
    1024,
);

For framework-specific response helpers, enable the corresponding momenta-ssr feature:

[dependencies]
momenta-ssr = { version = "0.2", features = ["axum"] }

Comparison with Other Frameworks

Feature Momenta Yew Dioxus Leptos
Reactivity Model Compile-time/Fine grained Component-scoped Component-scoped (VDOM) Fine-grained (Signals)
Runtime Overhead None Medium Medium Low
Bundle Size Smallest Large Medium Small
Memory Usage Minimal High Medium Low
Rendering Strategy Direct DOM Virtual DOM Virtual DOM Direct DOM
Developer Experience
Syntax RSX (React-like) html! macro RSX view! macro
Type Safety ✅ Excellent ✅ Excellent ✅ Excellent ✅ Excellent
Hot Reload ⚠️ Limited ✅ Yes ✅ Yes
Learning Curve Easy (if you know React) Medium Easy Medium
Documentation Growing Mature Good Good
Platform
Web (WASM)
Desktop
Mobile
SSR
no_std
Ecosystem & Community
Maturity New Mature Growing Growing
GitHub Stars <1k 30.5k+ 20k+ 18.5k+
Component Libraries Limited Good Growing Growing
Community Size Small Large Medium Medium

Contributing

We welcome contributions! Here's how you can help:

  1. Report bugs - Open an issue with a minimal reproduction
  2. Suggest features - Start a discussion about your idea
  3. Improve docs - Help us make the docs better
  4. Submit PRs - Fix bugs or implement features
  5. Star the repo - Show your support!

Please read our Contributing Guide before submitting PRs.

License

MIT License - see LICENSE for details

About

Simple and performant reactivity for building user interfaces in rust

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages