DEV Community

Cover image for Valerie: Rethinking Web Apps in Rust
Emmanuel Antony
Emmanuel Antony

Posted on

Valerie: Rethinking Web Apps in Rust

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 matching 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.

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

fn run() {
    App::render(ui)
}
Enter fullscreen mode Exit fullscreen mode

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

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

    div(
        x,
        button("Add 1").on_click(|| { x += 1; }),
    )
}
Enter fullscreen mode Exit fullscreen mode

And how would we bind data to inputs?

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

    div(
        text,
        input("text").bind(text),
    )
}
Enter fullscreen mode Exit fullscreen mode

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. The idea behind Valerie was to enable people to build clean web UI with Rust.

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

use valerie::prelude::components::*;
use valerie::prelude::*;

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

#[valerie(start)]
pub fn run() {
    App::render_single(ui());
}
Enter fullscreen mode Exit fullscreen mode

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

Let's have a look at states.

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()
}
Enter fullscreen mode Exit fullscreen mode

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?

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

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

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

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.

GitHub logo emmanuelantony2000 / valerie

Rust front-end framework for building web apps

Valerie

CI License Cargo Documentation Discord

Rust front-end framework for building web apps.

Valerie is still in a very early phase A lot of features are not available at the moment A lot of work is left and you are welcome to try it out.

  • No Virtual DOM.
  • UI can be made in a simple manner, by following an MVVM architecture rather an MVC architecture.
  • Use state variables to update the UI where required.
  • Written without any unsafe code.

Architecture

  • Every UI element has to implement the Component trait.
  • A page is a function which returns a Node.
  • Two type of State variables
    • StateAtomic for types implementing Copy.
    • StateMutex for types implementing Clone.

Setting up

  • Run cargo new --lib some_name
  • Add valerie to the dependencies
  • Create a static directory and create an index.html inside it
<!doctype html>
<html lang="en">
    <head>
        <meta charset="
โ€ฆ
Enter fullscreen mode Exit fullscreen mode

Top comments (13)

Collapse
 
wulymammoth profile image
David • Edited

This was a fun read because you've basically captured some of the ideas that's been swirling around in my head for a while and I've been interested in Rust for a long while, but still have not had time to sink into learning it as I waste time reading Rust articles and watching Rust videos on YouTube... lol

I'm going to toss a couple things onto the heap that may serve as inspirations that may or may not align with your current goals with Valerie:
1) have you checked out Phoenix the web framework in the Elixir community? The default mode in Elixir is message passing being that it runs on the Erlang VM and an actor-based model of concurrency. I used to write a lot of JS, but this gives me an alternative not to and, frankly, I thought LiveView was going to be a gimmick, but this was pretty spectacular: youtube.com/watch?v=MZvmYaFkNJI. The core team built a dashboard providing real-time observability and introspection: news.ycombinator.com/item?id=22892401
2) you may already be familiar with SvelteJS, but def check it out if you aren't, because it was the library that made it apparent to me that a virtual-DOM is NOT needed and it is additional overhead which I think aligns with what you're trying to accomplish

Collapse
 
emmanuelantony2000 profile image
Emmanuel Antony

Thanks. ๐Ÿ˜
I'll do check out Phoenix. Yes I have checked out Svelte. And I have also derived some ideas for the syntax from Jetpack Compose and SwiftUI.

Collapse
 
intermundos profile image
intermundos

13kb for a div with button is pretty solid, but the direction is nice. Keep it up!

Collapse
 
autra profile image
Augustin Trancart

State update should be asynchronous. Period.

Care to elaborate? What do you mean exactly by "state updates"? On every major reactive front framework (Vue+React at least), the mutation part of a state update is completely synchronous (though all other stuff : action emissions, rendering etc.... are not).

Collapse
 
alilee profile image
Alister Lee

Have you seen github.com/schell/mogwai ?

Collapse
 
emmanuelantony2000 profile image
Emmanuel Antony

Oh yes, I have. Now, Valerie is focussed at simplicity and performance. We don't have benchmarks yet. The project is still very young. The idea is for people to write high performance wasm web apps, small in size, even if they aren't that fluent with Rust without having to maintain a complex UI code.

Collapse
 
s_p profile image
Shreevari SP

Very intuitive!

Collapse
 
itsjzt profile image
Saurabh Sharma • Edited

I see this as a step in bring you own language on web.

Finally you're not limited to the language of web, you can use anything.

Collapse
 
arcticspacefox profile image
ArcticSpaceFox

Pretty cool, keep goin

Collapse
 
drdougtheman profile image
drdougtheman

Is this SSR? How do I setup a server to handle it?

Collapse
 
emmanuelantony2000 profile image
Emmanuel Antony

No this is Client side rendered, SSR support will be added soon, do keep an eye out. ๐Ÿ˜

Collapse
 
msfjarvis profile image
Harsh Shandilya

This looks extremely promising ๐Ÿ™Œ
I'll be keeping an eye out.

Collapse
 
kelleyvanevert profile image
Kelley van Evert

Fun! I hope to see more of it :)