Pinwheel is a library for writing web user interfaces with Rust.
The example below increments the value in a <p>
each time a <button>
is pressed.
let body = dom::window().unwrap().document().unwrap().body().unwrap().into();
let count = Mutable::new(0);
let on_click = {
let count = count.clone();
move |_| {
count.replace_with(|count| *count + 1);
}
};
let count_text = count.signal().map(|count| p().child(count.to_string()));
let increment_button = button().onclick(on_click).child("Increment");
let counter = div()
.style(style::DISPLAY, "grid")
.style(style::JUSTIFY_CONTENT, "start")
.child_signal(count_text)
.child(increment_button);
App::new(body, counter).forget();
Pinwheel uses the futures-signals crate to update exactly the right DOM nodes as your application's state changes. No virtual DOM required!
When compiled for the browser, Pinwheel renders by creating DOM nodes. On the server, it renders by writing HTML to a string.
let root = p().child("Hello, World!");
// On the server...
assert_eq!(root.to_string(), "<p>Hello, World!</p>");
// On the client...
App::new(dom_node, root).forget();
After server rendering, make a subset of your app interactive on the client. In the example below, dynamic_component
will render on the server and the client, but static_component
will render only on the server.
let root = p().child("Hello, World!");
// On the server...
let html = div()
.child(static_component)
.child(Dehydrate::new("hydration_id", dynamic_component))
.to_string();
// On the client...
hydrate("hydration_id");
Pinwheel provides statically typed builders for DOM elements with no macros, so you get all the benefits of rustfmt
formatting and rust-analyzer
autocomplete.
let count_p = count.signal().map(|count| p().child(count.to_string()));
let increment_button = button().onclick(on_click).child("Increment");
let root = div()
.style(style::DISPLAY, "grid")
.style(style::JUSTIFY_CONTENT, "start")
.child_signal(count_p)
.child(increment_button);
Organize your application into self-contained components.
use pinwheel::prelude::*;
struct Alert {
title: String,
color: Option<String>,
children: Vec<Node>,
}
impl Component for Alert {
fn into_node(self) -> Node {
div()
.style(style::BACKGROUND_COLOR, self.color)
.child(h1().child(self.title))
.children(self.children)
.into_node()
}
}
Components frequently have a few required fields and many optional fields. Pinwheel provides a derive macro to make using these components easy.
#[derive(ComponentBuilder)]
struct Alert {
// required field
title: String,
// optional field
#[optional]
color: Option<String>,
#[children]
children: Vec<Node>,
}
Now, you can make an alert with the builder pattern.
Alert::new("Alert!")
.color("green".to_owned())
.child("An alert occurred!")