Valerie: Rethinking Web Apps in Rust
Tags: valerie, webassembly, rust, webapps
Published: 2020-07-10 14:25:11

I have personally tried and seen various front-end frameworks for Web Apps in Rust. To be honest, they are pretty good and do their job well. But they do have some drawbacks, like a steep curve for understanding the API, and your code getting more complex as your app grows in size.

Now when I see the JS side of things, I see easy adoption and usage. But nah, I'm a Rust fan.

How to make things better? Find flaws and fix them. 😁

# Flaws

## A natural way of writing UI 🤔

I felt the lack of a natural way of writing UI. Writing UI should not be hindered by other factors like the language features itself. Like implementing a `Trait` and all functions along with it to display something simple as a **`Hello, World!`**, felt a bit cumbersome.

## Message passing is the way to go but not `match`ing them 🥺

Matching messages to functionality, using a `match`, might be an extra boilerplate that we can aim to reduce.

## Virtual DOM 🤨

Current computers are fast enough, that differences between using a Virtual DOM and not using it, are too far less to notice. But yes, if it reduces performance, we can try to get rid of it.

## Data centric and composable 😮

It is a lot easier to build composable UI. Why should the feature not be there?

# So, what's next?

I looked at all this and thought, let's write a hypothetical UI first which might use a hypothetical library and then try implementing the library in Rust.

```rust
fn ui() -> Something {
    div("Hello, World!")
}

fn run() {
    App::render(ui)
}
```

How should we manage variables and states? It should be something like this and it should somehow magically update.

```rust
fn ui() -> Something {
    let x = 0;

    div(
        x,
        button("Add 1").on_click(|| { x += 1; }),
    )
}
```

And how would we bind data to inputs?

```rust
fn ui() -> Something {
    let text = String::new();

    div(
        text,
        input("text").bind(text),
    )
}
```

Looking at these snippets, pretty much basic functionality is thought of. Our plan is to implement State variables, update them when changed and bind them to inputs. Now let's come back to implementing the library.

# Rethinking States

 - State update should be asynchronous. Period.
 - We are aiming for a message passing system, which passes messages to the receiver when the value is updated, and the receiver will be attached to elements and update their value as and when necessary.
 - This means we don't need diffing or any Virtual DOM.
 - It should also be bindable to elements and update when an input changes (and also be double way bindable).
 - It should also be derivable, i.e. a state can derive values from other states, i.e. if State A is deriving its value from State B, when State B updates its value, State A should also do the same.

# The solution I am trying to build: Valerie

Enter [Valerie](https://github.com/emmanuelantony2000/valerie). The idea behind Valerie was to enable people to build clean web UI with Rust.

Let's start with "Hello, World!".

```rust
use valerie::prelude::components::*;
use valerie::prelude::*;

fn ui() -> Node {
    h1!("Hello, World!").into()
}

#[valerie(start)]
pub fn run() {
    App::render_single(ui());
}
```

The above code when compiled, is a `.wasm` file of **14.8KB** (**6.8KB** gzip).

Let's have a look at states.

```rust
fn ui() -> Node {
    let string = StateMutex::new(String::new());
    let length = StateAtomic::from(&string, |x| x.len());

    div!(
        h3!(string.clone()),
        h3!(length),
        input!("text").bind(string)
    )
    .into()
}
```

This will show an input which binds to a `String`, and the `length` variable derives its value from the `string` State. And when you type something in the input text field, the data is updated magically.

The above code when compiled, is a `.wasm` file of **44.7KB** (**15.1KB** gzip).

One thing to notice here is `StateAtomic` and `StateMutex`, they are for types implementing `Copy` and `Clone` respectively. Internally `StateAtomic` uses atomics for achieving concurrency and `StateMutex` uses a mutex.

What if you want to click a button and increment some counter variable?

```rust
fn ui() -> Node {
    let count = StateAtomic::new(0);

    div!(
        h3!(count.clone()),

        button!("Click Me!")
        .on_event("click", count, |x, _| {
            *x += 1;
        })
    )
    .into()
}
```

The above code when compiled, is a `.wasm` file of **33.1KB** (**13.5KB** gzip).

These are some of the many features of Valerie that I have showed here. The library is still in a very early phase.
Valerie is also a `no-std` library.

Every piece of UI like a `String` or `div` implements the `Component` trait. You can even extract a part of UI into a different function which returns an `impl Component` and then reuse it when needed.

Rust follows the principle of **Zero-cost abstractions**, i.e. you don't have to pay the price of performance for the library features you don't use and whatever you do use you couldn't hand code any better. This means if you don't use a feature provided in Valerie, then it **won't** end up in the compiled `.wasm` file.

And comparing WASM with JS, after being received by the browser, your browser has to convert JS into an AST, compile the necessary functions and run it. Now, that's a lot of work. But in the case of WASM, your browser can directly run it using a JIT compiler, as your `.wasm` file is being received. Because a `.wasm` file is an optimised portable binary, the browser doesn't have to waste resources optimising it.

Thank you for reading through the article and do take a look at [Valerie](https://github.com/emmanuelantony2000/valerie).

{% github emmanuelantony2000/Valerie %}