| Crates.io | reinhardt-pages |
| lib.rs | reinhardt-pages |
| version | 0.1.0-alpha.1 |
| created_at | 2026-01-23 09:20:26.821432+00 |
| updated_at | 2026-01-23 09:20:26.821432+00 |
| description | WASM-based frontend framework for Reinhardt with Django-like API |
| homepage | |
| repository | https://github.com/kent8192/reinhardt-rs |
| max_upload_size | |
| id | 2063902 |
| size | 1,305,131 |
WASM-based reactive frontend framework for Reinhardt with Django-like API.
cfg_aliases integration and automatic event handler handlingThe prelude provides all commonly used types with a single import:
// Instead of multiple scattered imports:
// use reinhardt_pages::{Signal, View, use_state, ...};
// use reinhardt_pages::component::{ElementView, IntoView};
// use reinhardt_pages::reactive::{Effect, Memo};
// Use the unified prelude:
use reinhardt_pages::prelude::*;
// or via reinhardt crate:
use reinhardt::pages::prelude::*;
The platform module provides unified types that work across both WASM and native:
use reinhardt_pages::platform::Event;
// Works on both WASM and native targets
fn handle_click(_event: Event) {
// Event handling logic
}
Configure cfg_aliases in your project's build.rs:
// build.rs
use cfg_aliases::cfg_aliases;
fn main() {
// Rust 2024 edition requires explicit check-cfg declarations
println!("cargo::rustc-check-cfg=cfg(wasm)");
println!("cargo::rustc-check-cfg=cfg(native)");
cfg_aliases! {
wasm: { target_arch = "wasm32" },
native: { not(target_arch = "wasm32") },
}
}
Add to Cargo.toml:
[build-dependencies]
cfg_aliases = "0.2"
Now you can use shorter cfg attributes:
// Before:
#[cfg(target_arch = "wasm32")]
// After:
#[cfg(wasm)]
// Before:
#[cfg(not(target_arch = "wasm32"))]
// After:
#[cfg(native)]
The page! macro automatically handles event handlers for server-side rendering. You no longer need to write duplicate conditional blocks:
use reinhardt_pages::prelude::*;
// This works on both WASM and native targets!
// On WASM: Event handlers are bound to DOM events
// On native: Event handlers are automatically ignored
fn my_button(on_click: Signal<bool>) -> View {
page!(|| {
button {
@click: move |_| { on_click.set(true); },
"Click me"
}
})
}
Before (manual conditional compilation):
#[cfg(target_arch = "wasm32")]
{
page!(|| {
button {
@click: move |_| { on_click.set(true); },
"Click me"
}
})
}
#[cfg(not(target_arch = "wasm32"))]
{
let _ = on_click; // suppress warning
page!(|| {
button { "Click me" }
})
}
After (automatic handling):
// Just write once - the macro handles everything!
page!(|| {
button {
@click: move |_| { on_click.set(true); },
"Click me"
}
})
watchThe watch { expr } syntax enables reactive re-rendering when Signal dependencies change. Unlike static if conditions that are evaluated only at render time, watch blocks automatically re-evaluate and update the DOM when their Signal dependencies change.
watch is NeededWhen you extract Signal values before the page! macro, they become static:
// Problem: Static values don't update when Signal changes
let has_error = error.get().is_some(); // Static bool captured at render time
page!(|has_error: bool| {
if has_error { // This never re-evaluates!
div { "Error occurred" }
}
})(has_error)
The watch syntax solves this by creating a reactive context:
// Solution: Pass Signal directly and use watch
page!(|error: Signal<Option<String>>| {
watch {
if error.get().is_some() { // Re-evaluates when error changes!
div { { error.get().unwrap_or_default() } }
}
}
})(error.clone())
For reactive UIs, pass Signals directly to the page! macro instead of extracting values:
use reinhardt_pages::prelude::*;
fn error_display() -> View {
let (error, set_error) = use_state(None::<String>);
// Pass the Signal directly (not the extracted value)
let error_signal = error.clone();
page!(|error_signal: Signal<Option<String>>| {
watch {
if error_signal.get().is_some() {
div {
class: "alert-danger",
{ error_signal.get().unwrap_or_default() }
}
}
}
})(error_signal)
}
watch vs Static if| Syntax | Use Case | Behavior |
|---|---|---|
if condition { ... } |
Static conditions, Copy types | Evaluated once at render time |
watch { if signal.get() { ... } } |
Signal-dependent conditions | Re-evaluates when Signal changes |
watch { match signal.get() { ... } } |
Multiple reactive branches | Re-evaluates when Signal changes |
watch with matchThe watch block also supports match expressions:
page!(|state: Signal<AppState>| {
watch {
match state.get() {
AppState::Loading => div { "Loading..." },
AppState::Ready(data) => div { { data } },
AppState::Error(msg) => div { class: "error", { msg } },
}
}
})(state.clone())
Signal<T> parameters instead of extracting valuesSignal::clone() is cheap (Rc-based), so clone freelywatch blocks must contain exactly one expressionwatch blocks (performance concern)This framework consists of several key modules:
reactive: Fine-grained reactivity system (Signal, Effect, Memo)dom: DOM abstraction layerbuilder: HTML element builder APIcomponent: Component system with IntoView traitform: Django Form integration (native only)csrf: CSRF protectionauth: Authentication integrationapi: API client with Django QuerySet-like interfaceserver_fn: Server Functions (RPC)ssr: Server-side renderinghydration: Client-side hydrationrouter: Client-side routing (reinhardt-urls compatible)platform: Platform abstraction typesprelude: Unified importsThe prelude includes:
Signal, Effect, Memo, Resource, ResourceStateContext, ContextGuard, create_context, get_context, provide_context, remove_contextuse_state, use_effect, use_memo, use_callback, use_contextuse_ref, use_reducer, use_transition, use_deferred_valueuse_id, use_layout_effect, use_effect_event, use_debug_valueuse_optimistic, use_action_state, use_shared_state, use_sync_external_storeComponent, ElementView, IntoView, View, Props, ViewEventHandlerCallback, IntoEventHandler, into_event_handlerEvent (platform-agnostic via platform module)Document, Element, EventHandle, EventType, documentLink, Router, Route, RouterOutlet, PathPatternApiModel, ApiQuerySet, Filter, FilterOpServerFn, ServerFnErrorAuthData, AuthError, AuthState, auth_stateCsrfManager, get_csrf_tokenHydrationContext, HydrationError, hydrateSsrOptions, SsrRenderer, SsrStateFormBinding, FormComponentWidget, FieldMetadata, FormMetadatapage!spawn_local (re-exported from wasm_bindgen_futures)create_resource, create_resource_with_depsuse reinhardt_pages::prelude::*;
fn counter() -> View {
let (count, set_count) = use_state(|| 0);
page!(|| {
div {
p { format!("Count: {}", count.get()) }
button {
@click: move |_| set_count.update(|n| *n + 1),
"Increment"
}
}
})
}
| Feature | Description |
|---|---|
msgpack |
MessagePack serialization support |
pages-full |
All features enabled |
static |
Static file serving |
urls |
URL routing integration |
Licensed under either of Apache License, Version 2.0 or MIT license at your option.