Hoshikawa's Secret Room

Build Website with Rust and WebAssembly

Kaede Hoshikawa
2021-10-03

Until recently, if one wants to do web programming, JavaScript seems to be the only choice. However, with the development of WebAssembly, Web Development without JavaScript is becoming increasingly practical.

This article will give an introduction of how to build a website with Rust and WebAssembly.

What is WebAssembly

WebAssembly

WebAssembly is an efficient, platform-agnostic low-level binary exectuable format. It can be executed efficiently by browsers. Chrome, Safari and Firefox all support WebAssembly.

For more information about WebAssembly, you can visit this website: https://webassembly.org/

Why building a website with Rust?

Rust

There are a couple reasons why I chose to use Rust:

  1. Final Executable Size: As we are building a website, everything needs to be loaded from the Internet. Bundle size becomes important. If one chooses to use a programming language with a heavy runtime, it would inevitably lead to a longer loading time.
  2. Speed: If you have used React, you should have noticed that when rendering a lot of components (or doing CPU intensive tasks), the page would become sluggish. If we are going to build a website with WebAssembly, it better be efficient.
  3. Maturity: Among the languages that are available under WebAssembly, Rust should be the most mature one.
  4. Safety: Rust is known for its safety. It can discover a lot of errors at compile time.

Pick a Framework

Yew

Nowadays, if we are going to build a website in JavaScript, we will be mostly likely using a framework like React or Vue. So does using Rust for Web Programming. There're 2 relatively popular in Rust community: Yew and Seed.

Yew uses a React-inspired approach where as Seed mimics the architecture of Elm.

As I am more familiar with React, I have picked Yew for this article.

0. Prerequisites

  1. Rust + wasm32 Toolchain

    As the introduction of Rust is out of the scope of this article, please refer to https://rustup.rs/ for instructions on Rust Installation.

    After installing Rust, we also need the WebAssembly Toolchain, you can install it with the following command:

    rustup target add wasm32-unknown-unknown
    
  2. Trunk

    Under macOS, you can install it with the following command:

    brew install trunk
    

    For other platforms, please refer to: https://trunkrs.dev

    When building a website with React, if we use Webpack Dev Server, we can view the changes instantly. Although we can use Webpack with WebAssembly, it requires some glue code written in JavaScript, which kind of defeats the purpose of using WebAssembly. If we use Trunk, we can then view the changes without writing any JavaScript.

1. Create Repository

To create a Rust repository, the easiest way is to use Cargo. Cargo is the Package Manager of the Rust Programming Language. You may find it familiar to Python's pip or JavaScript's npm and yarn.

To create a repository, enter the following command in Terminal:

cargo new --bin first-yew-app

cargo add yew  # requires cargo-edit
# cargo-edit can be installed with `cargo install cargo-edit`

cargo will then create a directory named first-yew-app and initialise the directory with Rust files.

2. Create index.html

To use Trunk, you need to create an index.html under first-yew-app directory.

You do not need to define <body /> in index.html, <head /> only would suffice.

Here's an example of index.html:

<!DOCTYPE HTML>
<html>
  <head>
    <title>First Yew App</title>
  </head>
</html>

For options available in index.html, please refer to Trunk's Documentation.

3. Create our first Component

Just like React, Yew is a Component-based framework. Yew current only supports Struct Component (Corresponding to React's Class Component). Function Component is planned to be released in the upcoming 0.19 release.

Let's create a simple Component:

use yew::prelude::*;

pub struct App;

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

    fn create(_props: 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>{"Hello, World!"}</div>
        }
    }
}

Methods in React Components correspond to the following Yew struct methods:

  • constructor() -> create()
  • render() -> view()
  • componentDidMount() -> rendered()
  • shouldComponentUpdate() -> update()
  • componentWillUpdate() -> change()
  • componentWillUnmount() -> destroy()

4. Render App

Replace the conent of main to the following:

fn main() {
    yew::start_app::<App>();
}

To start the development server, you can use the following command under the first-yew-app directory:

trunk serve --open

Your default browser should open with a hello world page.

5. Implement a Simple Counter Component

To update the state of a Component, one needs to use Message.

Any Enum can be the Message type of a Component.

Let's define a simple Message:

#[derive(Debug)]
pub enum AppMsg {
    Increment,
}

And replace the content of App to the following:

pub struct App {
    counter: u64,
    link: ComponentLink<Self>,
}

App now has 2 fields, counter and link. counter stores the current count. link is a special type - ComponentLink<Self>. This is a type that holds a "link" to the current Component. You can use it to send the message back to the Component.

We can then implement the counter with the following code:

impl Component for App {
    type Message = AppMsg;
    type Properties = ();

    fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
        Self { counter: 0, link }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            AppMsg::Increment => {
                self.counter += 1;
            }
        }

        true
    }

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

    fn view(&self) -> Html {
        let increment = self.link.callback(|_| AppMsg::Increment);

        html! {
            <div>
                {format!("Current Counter: {}", self.counter)}
                <br />
                <button onclick=increment>{"Increment"}</button>
            </div>
        }
    }
}

First, we need to specify type Message to be our previously defined AppMsg, which makes App to be able to receive AppMsg as Message. Every time the Component receives a Message, it will call the update method, you can mutate the Component in the update method, if update returns true, it means that App needs to be re-rendered. You can specify the rendering result in the view method. The content above create a div that contains the current count and a button that increases the counter when clicked.

You can use self.link.callback to create a Event "handler" that can be passed to on* method for an element and it will be called when that event is fired.

6. Final Result

Full Example: https://github.com/futursolo/fl-www-examples/tree/master/first-yew-app

7. Limitiations

As Web Development with Rust is still in its early days, the ecosystem is still not as mature as JavaScript. You may face issues like:

  1. No server-side rendering
  2. No good styled-component-ish Css-In-Rust Library(So I made one)
  3. No Function Component
  4. No Code-splitting (React.lazy)

etc.

8. Final Thoughts

It is actually possible to develop a website using Rust. However, due to the maturity of WebAssembly and Yew, you may need to do some digging.

As you may have guessed, this website is also made with Rust + Yew + WebAssembly.

You can see the source code here:

https://github.com/futursolo/furtherland-www

Comments

Coming Soon...