Kobold
Easy declarative web interfaces.
Kobold uses macros to deliver familiar HTML-esque syntax for building declarative web interfaces, while leveraging Rust's powerful type system for safety and performance.
Zero-Cost Static HTML
Like in React or Yew updates are done by repeating calls
to a render function whenever the state changes. However, unlike either, Kobold does not produce a
full blown virtual DOM. Instead the html! macro compiles
all static HTML elements to a single JavaScript function that constructs the exact
DOM for it.
All expressions, which must implement the Html trait, are injected into the constructed DOM on first
render. Kobold keeps track of the DOM node references for these expressions. Since the exact types the
expressions evaluate to are known to the Rust compiler, update calls can diff them by value and surgically
update the DOM should they change. Changing a string or an integer only updates the exact
Text node that string or integer was rendered to.
If the html! macro invocation contains HTML elements with no expressions, the constructed Html
type will be zero-sized, and its Html::update method will be empty, making updates of static
HTML quite literally zero-cost.
Hello World
Any struct that implements a render method can be used as a component:
use *;
The render method here will return a transient type that contains only the &'static str from
the { self.name } expression. Kobold will create a text node for that string, and then send it to
a compiled JavaScript function that will build the h1 element with the static text around it.
Everything is statically typed and the macro doesn't delete any information when manipulating the token stream, so the Rust compiler can tell you if you've made a mistake:
error[E0560]: struct `Hello` has no field named `nam`
--> examples/hello_world/src/main.rs:17:16
|
17 | <Hello nam="Kobold" />
| ^^^ help: a field with a similar name exists: `name`
You can even use rust-analyzer to refactor component or field names, and it will change the invocations inside the macros for you.
Stateful Components
The Stateful trait can be used to create components that own and manipulate their state:
use *;
// To derive `Stateful` the component must also implement `PartialEq`.
The stateful method above accepts a non-capturing anonymous render function
matching the signature:
fn
The State here is an associated type which for all components that
use derived Stateful implementation defaults to Self, so in the example above
it is the Counter itself.
The Context can be used to create event callbacks that take a &mut reference to the
state and a & reference to a DOM Event (ignored above). If the callback closure has no
return type (the return type is ()) each invocation of it will update the component. If you would
rather perform a "silent" update, or if the callback does not always modify the state, return the provided
ShouldRender enum instead.
For more details visit the stateful module documentation.
Conditional Rendering
Because the html! macro produces unique transient types, if and match expressions that invoke
the macro will naturally fail to compile. To fix this annotate a function with #[kobold::branching]:
For more details visit the branching module documentation.
Lists and Iterators
To render an iterator use the list method from the ListIteratorExtextension trait:
This wraps the iterator in a transparent List<_> type that implements Html.
On updates the iterator is consumed once and all items are diffed with previous version.
No allocations are made by Kobold unless the rendered list needs to grow past its original capacity.
Borrowed Values
Html types are truly transient and only need to live for the duration of the initial render,
or for the duration of the subsequent update. This means that you can easily and cheaply render borrowed
state without unnecessary clones:
// Need to mark the return type with an elided lifetime
// to tell the compiler that we borrow from `names` here
+ '_
Components with children
If you wish to capture children from parent html! invocation, simply implement
a render_with method on the component:
use *;
;
If you know or expect children to be of a specific type, you can do that too:
use *;
;
A component can have both render and render_with methods if you want to
support both styles of invocation.
More examples
To run Kobold you'll need to install trunk:
You might also need to add the Wasm target to Rust:
Then just run an example:
# Go to an example
# Run with trunk
Acknowledgements
- Pedrors for the Kobold logo.
License
Kobold is free software, and is released under the terms of the GNU Lesser General Public License version 3. See LICENSE.