Rust の WASM フレームワーク "Yew" に入門しおみくじアプリを作成する
この記事では、Rustでウェブアプリケーションを構築するためのフレームワーク、Yewのセットアップと使い始め方について調査した内容を記載します。
Yew とは?
Yewは、Rustで書かれたモダンなWebアプリケーションフレームワークです。フロントエンドとバックエンドの両方をRustで開発できる、いわゆるフルスタックのフレームワークです。
フロントエンド機能
- React、Vue、Angularなどと同様のコンポーネントベースのアーキテクチャを採用
- Rustの特性を活かした以下のメリットがあります
- 型安全性: コンパイル時にエラーを検出でき、実行時エラーを防げます
- 高パフォーマンス: Rustの高速性を生かせます
- メモリ安全性: データ競合などのメモリ安全性問題が起きにくくなります
フルスタック開発のメリット
- 一つの言語(Rust)で開発できるため、学習コストを削減できます
- コード全体で一貫した構文やツールを使えるので、開発効率が向上します
- フロントエンドとバックエンドで同じデータ構造を使え、データの受け渡しが簡単になります
つまり、Yewを使えばRustの安全性、並行性、高速性をフルスタックで活かしたモダンなWebアプリケーション開発が可能になります。
Rustがフロントエンドで動作する理由
Rustがフロントエンドで動作できるのは、WebAssembly(Wasm) という技術のおかげです。
WebAssemblyとは、ウェブブラウザで高速に実行できるバイナリフォーマットです。JavaScriptと並行して動作し、ネイティブアプリケーションに近いパフォーマンスを発揮できます。
Rustは、その設計思想とパフォーマンスの高さからWebAssemblyの開発言語に適しているとされます。Rustで書かれたコードは、WebAssemblyにコンパイルすることで、ウェブブラウザ上で直接実行できます。
つまり、WebAssemblyを介してRustをフロントエンドで活用できるため、高速で安全なウェブアプリケーション開発が可能になります。Rustの強みを最大限に生かしつつ、JavaScriptとも親和性が高いのが特徴です。
「おみくじ」アプリを作成する
Yewフレームワークを使って「おみくじ」アプリを作成します。
ボタンをクリックすると、ランダムにおみくじの結果が選ばれ、画面に表示されるアプリです。
1. 前提条件と環境のセットアップ
まずは、Rustの開発環境をセットアップします。
Windowsの場合は、Microsoft公式ドキュメントを参考にしてください。
次に、WebAssemblyバイナリをビルドするための設定を行います。
rustup target add wasm32-unknown-unknown
-
rustup
はRustツールチェーンのインストーラーで、ツールチェーンの管理と更新に使用します -
target add
で新しいターゲットプラットフォームをコンパイルに追加します -
wasm32-unknown-unknown
はWebAssembly(Wasm)出力用のターゲットで、Rustをブラウザで実行できるようになります
最後に、WebAssemblyアプリケーションのビルドとデプロイを可能にするTrunkパッケージをインストールします。
cargo install --locked trunk
-
cargo install
でRustのパッケージマネージャ(Cargo)を使ってパッケージをインストールします -
--locked
フラグで、Cargo.lock
ファイルに記録された依存関係のバージョンを使用するよう指示し、ビルドの再現性を高めます -
trunk
はWebAssemblyアプリのビルド・デプロイツールです
以上で準備が整いましたので、おみくじアプリの作成に取り掛ります。
2. 新しい Yew プロジェクトの作成
次に、新しいYewプロジェクトを作成します。以下のコマンドで新規プロジェクトを作成してください。
# --libフラグを指定してライブラリ(lib.rs)を作成します
cargo new --lib yew_fortune_app
cd yew_fortune_app
-
cargo new
でCargoプロジェクトを新規作成します -
--lib
フラグを渡すことで、バイナリではなくライブラリを作成するよう指示しています -
yew_fortune_app
は新しいプロジェクトの名前です
Yewアプリはライブラリとして実装されるため、--lib
フラグを指定してプロジェクトを作成しています。
作成されたプロジェクトにはsrc/lib.rs
ファイルがあり、ここにアプリケーションのコードを記述していきます。
3. 依存関係を追加する
新しいプロジェクトが作成されたら、Cargo.toml
ファイルに必要な依存関係を追加します。
[package]
name = "yew_fortune_app"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
yew = "0.17"
wasm-bindgen = "0.2"
rand = "0.8"
# WebベースのWASMアプリでは、getrandomの機能を直接有効化する必要があります
getrandom = { version = "0.2", features = ["js"] }
-
[lib]
セクションでcrate-type
をcdylib
とrlib
に設定しています。-
cdylib
: WebAssemblyコンパクトデシリアライズドバイナリを出力します。 -
rlib
: Rustライブラリを出力します。
-
-
[dependencies]
セクションで、アプリに必要な依存関係を記載しています。-
yew
: Yewフレームワーク本体です。 -
wasm-bindgen
: Rust/WebAssembly間のデータバインディングを行います。 -
rand
: 乱数生成ライブラリです。おみくじ結果をランダムに選ぶため使用します。 -
getrandom
: 安全な乱数生成のための依存ライブラリで、WebAssemblyターゲット向けに"js"
フィーチャを有効化しています。
-
4. シンプルな「おみくじ」アプリケーションを書く
次に、シンプルな「おみくじ」アプリケーションのコードを書きます。
src/lib.rs
ファイルを以下のように編集します。
// Rustの標準ライブラリからrand(乱数生成)をインポート
// Web上で動作するためのwasm_bindgenライブラリから、preludeモジュールをインポート
// Yewから、preludeモジュールをインポート
use rand::Rng;
use wasm_bindgen::prelude::*;
use yew::prelude::*;
// Modelという構造体を定義
// Reactでいうところのコンポーネントに相当する
// ComponentLinkはコンポーネント内のイベントハンドリングに使用
// fortuneはおみくじの結果を保持する文字列
struct Model {
link: ComponentLink<Self>,
fortune: String,
}
// コンポーネントのメッセージ(イベント)を定義
// Msg::DrawFortuneがおみくじを引くイベント
enum Msg {
DrawFortune,
}
// Modelに対してComponentトレイトを実装
impl Component for Model {
// メッセージの型を指定
type Message = Msg;
// プロパティの型を指定(本例ではプロパティを持たない)
type Properties = ();
// コンポーネントの初期化処理
// React のクラスコンポーネントの constructor に相当
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
fortune: String::from("おみくじを引いてみてください!"),
}
}
// メッセージ(イベント)が発生した際の処理
// React の componentDidUpdate に相当
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
// DrawFortuneイベントが発生した場合
Msg::DrawFortune => {
// 乱数生成器を初期化
let mut rng = rand::thread_rng();
// おみくじの結果の候補を定義
let fortunes = vec!["大吉", "中吉", "小吉", "吉", "凶"];
// 乱数を使って候補からおみくじの結果を選択し、fortuneに格納
self.fortune = fortunes[rng.gen_range(0..fortunes.len())].to_string();
// コンポーネントの再描画を指示
true
}
}
}
// プロパティが変更された際の処理
// React の componentWillReceiveProps に相当
// 本例ではプロパティを持たないので、常にfalseを返す
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
false
}
// コンポーネントの描画処理
// React の render に相当
fn view(&self) -> Html {
// JSXライクなマクロを使ってHTMLを生成
html! {
<div>
// ボタンをクリックした際のイベントハンドラを登録
<button onclick=self.link.callback(|_| Msg::DrawFortune)>{ "おみくじを引く" }</button>
// おみくじの結果を表示
<p>{ &self.fortune }</p>
</div>
}
}
}
// WebAssemblyのエントリポイントを定義
#[wasm_bindgen(start)]
pub fn run_app() {
// ModelコンポーネントをDOMツリーにマウント
App::<Model>::new().mount_to_body();
}
このコードはWebAssemblyを使用してブラウザで実行されます。#[wasm_bindgen(start)]
アトリビュートが付けられたrun_app
関数が、アプリケーションのエントリーポイントとして機能します。この関数が呼び出されると、Yewアプリケーションが起動し、Model
がルートコンポーネントとしてレンダリングされます。
次にstatic
フォルダを作成し、その中にindex.html
ファイルを追加します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Yew Fortune App</title>
<script type="module">
import init from "./wasm.js";
init();
</script>
</head>
<body></body>
</html>
-
<script type="module">
:type="module"属性は、このスクリプトがES Modulesであることを示します。 -
import init from "./wasm.js";
:init関数をwasm.jsファイルからインポートします。wasm.jsはアプリケーションをビルドする際に生成されます。 -
init();
:インポートしたinit関数を呼び出します。この関数でWebAssemblyアプリケーションを初期化します。
これでシンプルな「おみくじ」アプリケーションのコードが書けました。次は実際にビルドして動作確認します。
5. アプリケーションの実行と表示
最後に、アプリケーションをビルドしてブラウザで実行・表示します。
まず、以下のコマンドでWebAssemblyパッケージをビルドし、./static
ディレクトリにwasm
という名前で出力します。
wasm-pack build --target web --out-name wasm --out-dir ./static
-
wasm-pack build
:wasm-pack
ツールを使ってWebAssemblyパッケージをビルドします -
--target web
: ビルドターゲットをWebに設定し、ブラウザで直接実行できるよう出力します -
--out-name wasm
: 出力ファイル名をwasm
(wasm.js
)に設定します -
--out-dir ./static
: ビルド成果物の出力ディレクトリを./static
に設定します
次に、Rustの最新の開発版ツールチェーンをインストールし、それを使ってminiserve
をインストールします。
rustup toolchain install nightly-x86_64-pc-windows-msvc
cargo +nightly install miniserve
-
rustup toolchain install nightly-x86_64-pc-windows-msvc
: Rustの最新開発版ツールチェーン(nightly
)を64bit Windowsに合わせてインストールします -
cargo +nightly install miniserve
:miniserve
という小さな高速HTTPファイルサーバをインストールします。+nightly
フラグで開発版ツールチェーンを使用するよう指示しています
最後に、./static
ディレクトリをホストします。
miniserve ./static --index index.html
-
miniserve ./static
:miniserve
を使って./static
ディレクトリをホストします -
--index index.html
: サーバのデフォルトインデックスページをindex.html
に設定します
これで http://127.0.0.1:8080
にアクセスすると、作成した「おみくじ」アプリが表示されます。
「おみくじを引く」ボタンを押すと、ランダムにおみくじの結果が表示されます。
以上で、RustとYewを使ったWebアプリケーションの作成が完了しました。
関数型コンポーネントを使ってみる
Yewの最新版では、Function Components
と呼ばれる新しい形式のコンポーネントが導入されています。これはReactのFunction Components
に似ており、よりシンプルで直感的なコードを書くことができます。
1. Yewのバージョンを上げる
Yewのバージョンを0.17
からFunction Components
がサポートされる0.19
にバージョンアップします。Cargo.toml
を次のように修正します。
yew = "0.19"
2. 「おみくじ」アプリケーションを修正する
src/lib.rs
ファイルを以下のように編集します。
use rand::Rng;
use wasm_bindgen::prelude::*;
use yew::prelude::*;
// Reactの関数コンポーネントに相当します。Fortuneという名前の関数コンポーネントを定義します。
#[function_component(Fortune)]
fn fortune() -> Html {
// ReactのuseStateに相当します。おみくじの結果を保持する状態を作成します。
let fortune = use_state(|| String::from("おみくじを引いてみてください!"));
// ReactのuseCallbackに相当します。おみくじを引くためのコールバック関数を定義します。
let draw_fortune = Callback::from({
let fortune = fortune.clone();
move |_| {
let mut rng = rand::thread_rng();
let fortunes = vec!["大吉", "中吉", "小吉", "吉", "凶"];
fortune.set(fortunes[rng.gen_range(0..fortunes.len())].to_string())
}
});
// ReactのJSXに相当します。HTMLを返します。
html! {
<div>
<button onclick={draw_fortune}>{ "おみくじを引く" }</button>
<p>{ &*fortune }</p>
</div>
}
}
// ReactのReactDOM.renderに相当します。アプリケーションを起動するための関数を定義します。
#[wasm_bindgen(start)]
fn run_app() {
// Yewアプリケーションを起動します。Fortuneコンポーネントがルートコンポーネントとして使用されます。
yew::start_app::<Fortune>();
}
先ほどと同様にビルドとホスティングを行うことができます。
wasm-pack build --target web --out-name wasm --out-dir ./static
miniserve ./static --index index.html
このように、Function Components
を使うと、コンポーネントのロジックをひとまとめにできるので、コードがシンプルになり可読性が向上します。
ReactのFunction Components
と同様に、フックも利用できます。この例ではuse_state
フックを使って状態管理を行っています。
Function Components
の導入により、Yewのコンポーネント記述がより直感的で分かりやすくなりました。
Discussion