# Spair [![Crates.io](https://img.shields.io/crates/v/spair)](https://crates.io/crates/spair) [![docs.rs](https://img.shields.io/docsrs/spair)](https://docs.rs/spair) ![Build](https://github.com/aclueless/spair/workflows/Rust/badge.svg) An incremental and fine-grained render frontend framework for **S**ingle **P**age **A**pplication **i**n **R**ust. This project is in its *early stage*, things are still missing and *frequent breaking changes are expected*. Spair is both small and fast. Spair's performance is comparable to Leptos, Dominator, and Sycamore. [See the benchmark here](https://github.com/krausest/js-framework-benchmark). Spair's app size is smaller than some others frameworks. [See the comparision here](https://github.com/aclueless/rust-frontend-framework-comparision/tree/main/todomvc). ## Features * Incremtental render * The render method creates the DOM tree on the first run, then updates it on subsequence runs. * Queue render for fine-grained render. * Must be enabled in Cargo.toml, like: `spair = { version="x.y.z", features = ["queue-render"] }` * (It is just a queue render, not using any kind of signals) * You have to wrap your values in `spair::QrVal` or `spair::QrVec` * Current version of queue render may not very efficient because each fine-grained-render need to borrow the component state separately by it own. * Component state can be accessed in every part of render code. * Spair's components tend to be big, [`Render`] is used for code splitting. * You can always access the component state in every [`Render`] without having to pass it around. * Component state can also be accessed from queue render, too. * (Almost) no macro is required for constructing DOM. * But Spair is quite verbose because of this. * (But a macro can be built on top of Spair). * `spair::set_arm!()` is the only macro you have to use, it saves your days, (without it, `match_if` is a nightmare to maintainers) * Routing * Just basic support, currently, you have to implement the routing logic by yourself. * async command * svg * Missing things here and there... * Errr, this is not a feature, obviously. I just put this here to remind potential users not to surprise about missing things :D. * For example, Spair currently just implements a handful number of events. ### Not support (yet), do you want to contribute? These features are extremely low (or even not) on my todo list. * `#[derive(spair::Routes)]` * Event delegation, under `features = ["event-delegation"]` if it is ever implemented. * But you can always do it manually * SSR (under a feature flag, too) * RSX macro (under a feature flag, too) ## Cargo features You can enabled a feature in your Cargo.toml like this: `spair = { version="x.y.z", features = ["feature-name"] }` | feature-name | desciption | | ------------------ | ---------------------------- | |`keyed-list` | Support `keyed-list` for incremental mode | |`svg` | Support svg element | |`queue-render` | Support fined-grained render (*)| (*) Lists render by queue-render are always keyed. ## Run examples Prerequisites: * [Rust] with `wasm32-unknown-unknown` target. * [Trunk] In an example folder: trunk serve or, if it's slow, use: (especialy `examples/boids` or `examples/game_of_life`) trunk serve --release Open your browser at http://localhost:8080 ## Documentation Not yet. `/examples/*` is the best place to start now. Sections below provide first looks into Spair. ## Static-mode and update-mode Spair works by iterating through every elements and attributes/properties in the current DOM, which is empty before the first render, creating new items or modifying existing items, it's the update-mode. But there are elements or attributes that will never change. You can tell Spair to just create them but ignore them when iterating over them later by turn on the static-mode. | items | update-mode | static-mode | notes | | ------------------------ | ---------------------------- | ---------------------- | -------------------------------------------------------------------------- | | attributes / properties | *default* | `.static_attributes()` | call `.static_attributes()` after you are done with update-mode-attributes | | elements | *default*, `.update_nodes()` | `.static_nodes()` | only apply to elements (include `.relement()`), *not* apply to texts/renderable-items| | texts / renderable-items | `.rupdate(value)` | `.rstatic(value)` | not affected by mode introduced by `.update_nodes()` or `.static_nodes()` | * `.update_nodes()` and `.static_nodes()` can be switched back and forth as many times as you want. * Again, please rememeber that `.relement()` is affected by `.update_nodes()` and `.static_nodes()`. ```rust element // default to update-mode attributes .value(&some_value) // will be checked and updated if changed .class_if("class-name", bool_value) .static_attributes() // we are done with update-mode attributes! .class("class-name") // class="class-name" is added on creation, but ignored on subsequence renders // just add child-elements, default to update mode. .p(|p| {}) // create and update a
.rupdate(value) // create and update a text .rstatic(value) // a create-only text - not affected by update-mode (default). .static_nodes() .div(|d| {}) // a create-only
. // In the future update, this closure will be IGNORED, // therefore, all child-nodes of
will NOT be updated despite
// being created in update-mode.
p.span(|s| {})
.rupdate(value); // NEW VALUE OF `value` WILL NEVER BE RENDERED.
});
```
## Example
*Look in `/examples` for full examples*
This is the `render` method of `examples/counter`:
```rust
impl spair::Component for State {
type Routes = ();
fn render(&self, element: spair::Element