Yewを使ってRustでWebAssemblyを試してみる

## はじめに
昨今、WebAssemblyを使用したアプリケーション開発がカジュアルにできるようになりましたが、
Yew(「ユー」と読みます)というRust製のフロントエンドwebフレームワークで楽しくWebAssemblyを体験できたので紹介します！ 


## 準備
前提としてnightly版のRustを使用してください。(stable版だと動かない可能性が高いです。)
まずは新規プロジェクトを作成します。

```
$ cargo new (プロジェクト名)
```

Rustはターゲットアーキテクチャを指定することができ、今回はWebAssemblyにコンパイルしたいので、下記のコマンドで `wasm32-unknown-unknown` を指定します。

```
$ rustup target add wasm32-unknown-unknown
```

次にcargo-webのインストールをします。

```
$ cargo install cargo-web
```

これによりクライアントサイドのアプリケーション開発に必要なcargoのサブコマンドを使用できるようになります。

あとはcargo-editを使用してYewのパッケージを追加します。

```
$ cargo add yew
```

これでYewで開発する準備が整いました！

## Yewの基本
Yewは独自の仮想DOMを実装しており、ElmやReduxの影響を受けています。
ReduxのようにUIの状態管理ができます。

今回はこのようなコードを書いてみました

```rust:src/main.rs
use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender};

struct Model { 
    state: State
}

struct State {
    greet: String,
    counter: i32,
}

impl State {
    fn morning(&mut self) {
        self.greet = "おはよう".to_string()
    }
    fn noon(&mut self) {
        self.greet = "こんにちは".to_string()
    }
    fn night(&mut self) {
        self.greet = "こんばんは".to_string()
    }
    fn countUp(&mut self) {
        self.counter = self.counter + 1
    }
}

enum Msg {
    ChangeGreet()
}

impl Component for Model {

    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
        Model {
            state: State {
                greet: "...".to_string(),
                counter: 0,
            },
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::ChangeGreet() => {
                self.state.countUp();
                if self.state.counter % 3 == 0 {
                    self.state.night();
                } else if self.state.counter % 3 == 1 {
                    self.state.morning();
                } else {
                    self.state.noon();
                }
            }
        }
        true
    }
}

impl Renderable<Model> for Model {
    fn view(&self) -> Html<Self> {
        html! {
            <div>{&self.state.greet}</div>
            <p>{&self.state.counter.to_string()}{"回目のあいさつ"}</p>
            <button onclick=|_| Msg::ChangeGreet(), >{ "あいさつするよ" }</button>
        }
    }
}

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

このコードではボタンを押すと、押した回数によって異なる挨拶を返してくれるUIを実装しています。
では重要なポイントを見ていきましょう。

### Model(状態)

```rust

struct Model { 
    state: State
}

struct State {
    greet: String,
    counter: i32,
}

impl State {
    fn morning(&mut self) {
        self.greet = "おはよう".to_string()
    }
    fn noon(&mut self) {
        self.greet = "こんにちは".to_string()
    }
    fn night(&mut self) {
        self.greet = "こんばんは".to_string()
    }
    fn countUp(&mut self) {
        self.counter = self.counter + 1
    }
}

```
ここでUIの状態を管理しています。Reduxでいうところのstoreです。
今回は`greet`と`counter`というプロパティを用意しています。
さらにState構造体に対してトレイトを実装します。

このトレイトを用いて状態を変化させます。
ReduxでいうところのReducerに近いです。

### update(状態変更)
```rust

fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::ChangeGreet() => {
                self.state.countUp();
                if self.state.counter % 3 == 0 {
                    self.state.night();
                } else if self.state.counter % 3 == 1 {
                    self.state.morning();
                } else {
                    self.state.noon();
                }
            }
        }
        true
    }
```
イベントとイベントハンドラを定義します。
今回は`ChangeGreet`のイベントを用意しました。
`ChangeGreet`が呼ばれる度に、`counter`が1つずつ増加し`counter`の値によって`greet`を変化させます。

### View(画面表示)
```rust

impl Renderable<Model> for Model {
    fn view(&self) -> Html<Self> {
        html! {
            <div>{&self.state.greet}</div>
            <p>{&self.state.counter.to_string()}{"回目のあいさつ"}</p>
            <button onclick=|_| Msg::ChangeGreet(), >{ "あいさつするよ" }</button>
        }
    }
}
```
`html!`マクロを使用してコンポーネントを作成します。JSXのようなテンプレートになっており、htmlタグのなかにRustのコードを記述します。先程updateで紹介した`ChangeGreet`がボタンを押す度に呼び出されます。

UIの状態管理ライブラリを使用したことがあれば、Model、update、Viewともに理解しやすいと思います。

### 動作確認
以下のコマンドでデバッグビルドとサーバの立ち上げを実施します。
`http://localhost:8000`でアクセスできます。

```
$ cargo web start
```

ビルドだけ実施したい場合は以下のコマンドを実施してください。

```
$ cargo web build --target wasm32-unknown-unknown
```

`target`ディレクトリが生成されるので中身を見てみると、`wasm32-unknown-unknown/debug/`内に`.js`ファイルと`.wasm`ファイルができます。

`.wasm`ファイルがRustから生成されたWebAssemblyであり、`.js`ファイルはfetchAPIを使用してこのWebAssemblyを読み込むために使用されます。

ちなみにリリースビルドをする場合は以下のコマンドを実施してください

```
$ cargo web build --target wasm32-unknown-unknown --release
```

リリースビルドすると最適化により`.wasm`ファイルのサイズも最適化されるので169KBに納まりました。
jsのコードも31KBで合わせてちょうど200KB程度なので、実際のWebアプリケーションに取り込んでも問題ないサイズですね。

### Yewへの期待
YewはWebAssemblyの入門としては充分なくらい楽しめます。
WebAssemblyにすることで、様々なWebアプリケーション間で使用することができるのも魅力です。使い所としては、Rustで計算処理をして、ついでにUIを作りたい場合には特に効果的だと思います。

今回はYewをやっていく上で、RustだけでなくWebAssembly、LLVMなどのコンパイラ基盤など様々な技術に出会うことができたことも収穫のひとつでした。

今回作成したリポジトリは[こちら](https://github.com/wataru-script/yew_asobi)になります。
少しでもお役に立てれば幸いです。