Kobold logo

Kobold

Test Crates.io version shield Docs Crates.io license shield

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

The view! macro produces opaque impl View types that by default do no allocations. All static DOM elements compile to inline JavaScript code that constructs them. Expressions 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 (or pointer) 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 view! macro invocation contains DOM elements with no expressions, the constructed View type will be zero-sized, and its View::update method will be empty, making updates of static DOM literally zero-cost.

Hello World!

Components in Kobold are created by annotating a render function with a #[component] attribute.

```rust use kobold::prelude::*;

[component]

fn Hello(name: &str) -> impl View + '_ { view! {

"Hello "{ name }"!"

} }

fn main() { kobold::start(view! { }); } ```

The component function must return a type that implements the View trait. Since the view! macro produces transient locally defined types the best approach here is to always use the opaque impl View return type.

Everything here is statically typed and the macro doesn't delete any information when manipulating the token stream, so the Rust compiler can tell you when you've made a mistake:

text error[E0560]: struct `Hello` has no field named `nam` --> examples/hello_world/src/main.rs:12:16 | 12 | <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

The stateful function can be used to create views that own and manipulate their state:

```rust use kobold::prelude::*;

[component]

fn Counter(init: u32) -> impl View { stateful(init, |count| { bind! { count: // Create an event handler with access to &mut u32 let onclick = move |_event| *count += 1; }

    view! {
        <p>
            "You clicked the "
            // `{onclick}` here is shorthand for `onclick={onclick}`
            <button {onclick}>"Button"</button>
            " "{ count }" times."
        </p>
    }
})

}

fn main() { kobold::start(view! { }); } ```

The stateful function takes two parameters:

The Hook here is a smart pointer to the state itself that allows non-mutable access to the state. The bind! macro can be invoked for any Hook to create closures with &mut references to the underlying state.

For more details visit the stateful module documentation.

Optional parameters

Use #[component(<param>?)] syntax to set a component parameter as default:

``rust //codewill default to200` if omitted

[component(code?: 200)]

fn Status(code: u32) -> impl View { view! {

"Status code was "{ code } } }

view! { // Status code was 200 // Status code was 404 } ```

For more details visit the #[component] macro documentation.

Conditional Rendering

Because the view! macro produces unique transient types, if and match expressions that invoke the macro will naturally fail to compile.

Using the auto_branch flag on the #[component] attribute Kobold will scan the body of of your component render function, and make all view! macro invocations inside an if or match expression, and wrap them in an enum making them the same type:

```rust

[component(auto_branch)]

fn Conditional(illuminatus: bool) -> impl View { if illuminatus { view! {

"It was the year when they finally immanentized the Eschaton." } } else { view! {

"It was love at first sight." } } } ```

For more details visit the branching module documentation.

Lists and Iterators

To render an iterator use the for keyword:

``rust //ListIteratorExt` is included in the prelude use kobold::prelude::*;

[component]

fn IterateNumbers(count: u32) -> impl View { view! {