Intro to Yew, a Rust Frontend Framework
Tags: frontend, rust
Published: 2020-11-16 20:03:20

---
title: Intro to Yew, a Rust Frontend Framework
published: true
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/aqst97j3ks262x9r02vr.jpg
tags: frontend, rust
series: Yew
---

Everyone is talking about Rust lately, and it seems to be delivering what other C/C++ killers didn't. Safe, high-performance system software.

But with the dawn of WebAssembly (WASM), Rust can also dive into the frontend realm. Since Rust has a minimal runtime, it's one of the few programming languages that can actually deliver sane package sizes for WASM based web frontends.

The creators of [the Yew framework](https://yew.rs/docs/en/) try to tackle that problem. Is Yew smaller than JavaScript-based frameworks? No! Is it faster? Also No! But it's easier? Well... No.

So why am I looking into this?!

Well, I guess in the hope that a Yew developer chimes in and tells me that I did it wrong! :D

Also, because I want to learn a bit more Rust and know mostly stuff about frontend development, so I can at least re-use some of my skills while getting the hang of it. 

I talked so much about backend stuff on "Fullstack Frontend", I thought it was time to write some frontend content again.

# The Bootstrap Starter

In this article, I will try to build the Bootstrap starter template with Yew. So no interactions, just setting the development environment up and pump some HTML into it.

# Prerequisites

I assume you have [Rust and Cargo installed](https://doc.rust-lang.org/cargo/getting-started/installation.html).

## Setting Up the Environment

Install wasm-pack, a WebAssembly build-tool for Rust.

    $ cargo install wasm-pack

## Creating the Rust Project

Use Cargo to create a new project.

    $ cargo new --lib bootstrap-starter
    $ cd bootstrap-starter

## Configuring the Packages

Cargo automatically downloads packages you add as dependencies to the `Cargo.toml`.

```toml
[package]
name = "bootstrap-starter"
version = "0.1.0"
authors = ["John Doe <dev@example.com>"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2.67"
yew = "0.17"
```

We need two dependencies here. `wasm-bindgen` is used to link up the WASM code with JavaScript and, well, the Yew framework itself.

## Creating the HTML File

WASM apps can't stand alone in the browser; they have to be loaded via JavaScript, which in turn has to be embedded into HTML.

So let's create a static HTML file `static/index.html` with this content:

```html
<!doctype html>

<title>Bootstrap Starter</title>

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">

<style type="text/css">
body { padding-top: 5rem }
.starter-template {
  padding: 3rem 1.5rem;
  text-align: center;
}
</style>

<script type="module">
  import init from "./wasm.js";
  init();
</script>
```

WASM-Pack will create a `static/wasm.js` file that our JavaScript can load later. I also added some CSS and Bootstrap to make the example look at least a bit interesting.

## Creating the Components

I used Bootstrap in this example, [the starter template](https://getbootstrap.com/docs/4.0/examples/starter-template/), be exact.

I tried to split it into two components, nothing fancy. No interaction, just minimal Yew components.

Let's replace the content of your `src/lib.rs` file with the following content:

```rust
#![recursion_limit="1000"]

mod content;
mod navigation;

use content::Content;
use navigation::Navigation;
use wasm_bindgen::prelude::*;
use yew::prelude::*;

struct Model {}

impl Component for Model {
    type Message = ();
    type Properties = ();

    fn create(_: Self::Properties, _link: ComponentLink<Self>) -> Self {
        Self {}
    }

    fn update(&mut self, _msg: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, _props: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        html! {
            <div>
                <Navigation />
                <Content />
            </div>
        }
    }
}

#[wasm_bindgen(start)]
pub fn run_app() {
    App::<Model>::new().mount_to_body();
}
```

This is what came up as I tried to remove as much code from the Yew examples as possible while still getting it to compile.

Rust doesn't have classes, and functional components are still in the making, so a struct with a trait implementation is as close as Yew currently gets to React class components.

If you come from a React background, you'll notice the `view` method that uses an `html!` macro, allowing JSX like syntax. The other methods are `create`, which is the constructor of the component. `update`, which handles interactions via messages as Redux would, I guess? And finally, `change` which handles new props that come from the parent component. They all don't return anything interesting because I didn't implement any interactions, but the `Component` trait doesn't come with default implementations for them, so I had to do it myself.

The first line tells the compiler not to blow up when the `html!` macro's HTML is nested too deep.

The `mod` statements import the other components, which we will create next. The `use` statements make some things a bit easier to... well... use, haha. After `use content::Content;` we can use Content inside the `html!` macro like it wasn't defined in another file.

The `use` statements for `wasm_bindgen` and `yew` make some other code available to run the app at the end of the file. A bit like the `render` function of ReactDOM, just with the extra step of calling out of WASM into JavaScript.

For the `Content` component, you have to create a new file `src/content.rs` with the following code:

```rust
use yew::prelude::*;

pub struct Content {}

impl Component for Content {
    type Message = ();
    type Properties = ();

    fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
        Content {}
    }

    fn update(&mut self, _msg: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, _props: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        html! {
            <main class="container">
                <div class="starter-template">
                    <h1>{"Bootstrap starter template"}</h1>
                    <p class="lead">
                        {"Use this document as a way to quickly start any new project."}
                        <br/>
                        {"All you get is this text and a mostly barebones HTML document."}
                    </p>
                </div>
            </main>
        }
    }
}
```
Nothing too spectacular here. As with JSX, you need to close your tags properly. But different from JSX, you have to wrap your strings into curly braces and quotation marks.

The navigation component isn't much different. Create a new file `src/navigation.rs` and fill this code in:

```rust
use yew::prelude::*;

pub struct Navigation {}

impl Component for Navigation {
    type Message = ();
    type Properties = ();

    fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
        Navigation {}
    }

    fn update(&mut self, _msg: Self::Message) -> ShouldRender {
        false
    }

    fn change(&mut self, _props: Self::Properties) -> ShouldRender {
        false
    }

    fn view(&self) -> Html {
        html! {
            <nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
                <a class="navbar-brand" href="#">{"Navbar"}</a>
                <button class="navbar-toggler">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarsExampleDefault">
                    <ul class="navbar-nav mr-auto">
                        <li class="nav-item active">
                            <a class="nav-link" href="#">
                                {"Home "}<span class="sr-only">{"(current)"}</span>
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="https://example.com">{"Link"}</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link disabled" href="#">{"Disabled"}</a>
                        </li>
                    </ul>
                    <form class="form-inline my-2 my-lg-0">
                        <input class="form-control mr-sm-2" type="text" placeholder="Search" />
                        <button class="btn btn-outline-success my-2 my-sm-0" type="submit">{"Search"}</button>
                    </form>
                </div>
            </nav>
        }
    }
}
```

Again, nothing special here. The `input` tag has to be closed properly, and the text-nodes have to be inside curly braces and quotation-marks.

## Build the App

If you run the following command, you should end up with more files inside the `static` directory besides the `index.html`.

    $ wasm-pack build --target web --out-name wasm --out-dir ./static

These files contain the actual Rust code compiled to WASM and many wasm-pack/wasm-bindgen helpers to link up the WASM with JavaScript.

I ended up with about 140KB, which is quite steep compared to JavaScript, but I didn't optimize anything here, just plain out of the box compilation.

On the other hand, it's still much smaller than what other languages that compile to WASM currently achieve, especially those with giant runtimes.

## Running the Example

If you point an HTTP server to the `static` directory, you will see nothing out of the ordinary. But if you look into the dev-tools' network tab, you will see what files are actually loaded and how big they are.

I used the miniserve crate that was proposed on the Yew guide. It's installed with:

    $ cargo +nightly install miniserve

But it could be that you have to install the nightly toolchain before.

    $ rustup toolchain install nightly 

After that, you can run miniserve and look at your fresh WASM frontend:

```
$ miniserve ./static --index index.html
```

## Summary

Yew is an interesting approach to front-end development. Rust's type-system is really nice and can prevent many JavaScript-related errors. 

Also, the `html!` macro is a nice touch and certainly better integrated into Rust than JSX is into JavaScript (because it isn't really).

I didn't build anything complex here, so I can't say anything about the rest of the framework, just that the size isn't perfect, and the benchmarks I saw didn't look very good either. But I guess this will change when WASM gets better integration with the browser in the future.