はじめに
昨今、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の状態管理ができます。
今回はこのようなコードを書いてみました
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です。
今回はgreet
とcounter
というプロパティを用意しています。
さらに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などのコンパイラ基盤など様々な技術に出会うことができたことも収穫のひとつでした。
今回作成したリポジトリはこちらになります。
少しでもお役に立てれば幸いです。