2 unstable releases
0.2.0 | Jun 28, 2021 |
---|---|
0.1.0 | Jun 16, 2021 |
#1981 in Web programming
105KB
2.5K
SLoC
Pinwheel
Pinwheel is a library for writing web user interfaces with Rust.
Example
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();
Features
Fine-Grained Reactivity
Pinwheel uses the futures-signals crate to update exactly the right DOM nodes as your application's state changes. No virtual DOM required!
Isomorphic Rendering
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();
Partial Hydration
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");
Macro-free Builders
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);
Components
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()
}
}
Component Builders
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!")
Dependencies
~13MB
~248K SLoC