Help us understand the problem. What is going on with this article?

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の状態管理ができます。

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

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(状態)

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です。
今回はgreetcounterというプロパティを用意しています。
さらにState構造体に対してトレイトを実装します。

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

update(状態変更)

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(画面表示)

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などのコンパイラ基盤など様々な技術に出会うことができたことも収穫のひとつでした。

今回作成したリポジトリはこちらになります。
少しでもお役に立てれば幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away