概要
ボタンを押すと、数字がカウントアップされていき、3の倍数の時は数字の代わりに青文字でFizzと、5の倍数の時は数字の代わりに赤背景でBuzzと、3と5両方の倍数の時は数字の代わりに赤背景に青文字でFizzBuzzと表示するWebアプリケーションを作ります。
準備
wasm-pack をインストール
※cargoが必要です
$ cargo install wasm-pack
クレートを作成
$ cargo new --lib fizzbuzz
$ cd fizzbuzz
この時点でのディレクトリ構成(一部省略):
fizzbuzz
├cargo.toml
└src
└lib.rs
webpack等のインストール
※npmが必要です
※yarn等を使う場合は適宜読み替えてください
$ npm init
$ npm install -D @wasm-tool/wasm-pack-plugin html-webpack-plugin webpack webpack-cli webpack-dev-server
この時点でのディレクトリ構成(一部省略):
fizzbuzz
├cargo.toml
├package.json
├package-lock.json
└src
└lib.rs
webpack.config.js
の作成
クレートのルートディレクトリに webpack.config.js
というファイルを作り、以下のように記述します。
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
resolve: {
extensions: [".js"]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, "./src/index.html")
}),
new WasmPackPlugin({
crateDirectory: path.join(__dirname, "./")
})
]
};
この時点でのディレクトリ構成(一部省略):
fizzbuzz
├cargo.toml
├package.json
├package-lock.json
├webpack.config.js
└src
└lib.rs
その他、必要なファイルの作成
fizzbazz/src に index.js
と index.html
を作成し、それぞれ以下のように記述します。
index.js:
import("../pkg");
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>fizzbuzz</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
この時点でのディレクトリ構成(一部省略):
fizzbuzz
├cargo.toml
├package.json
├package-lock.json
├webpack.config.js
└src
├index.html
├index.js
└lib.rs
その他、設定の作成
package.json
の scripts
に "start": "./node_modules/.bin/webpack-dev-server"
を追加します。
この時点での package.json
の例:
{
"name": "fizzbuzz",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "./node_modules/.bin/webpack-dev-server"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "^1.0.1",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.8",
"webpack-dev-server": "^3.8.0"
}
}
cargo.toml
の [dependencies]
に kagura = "0.1.3"
と wasm-bindgen = "0.2"
を、[lib]
に crate-type = ["cdylib", "rlib"]
を追加します。
この時点での cargo.toml
の例:
[package]
name = "fizzbuzz"
version = "0.1.0"
authors = ["****************"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
kagura = "0.8.6"
wasm-bindgen = "0.2"
アプリケーションの作成
Kagura について
Kagura はWebフロントエンド用のフレームワークです。
kagura::Component::new()
によりコンポーネントを作成し、そのコンポーネントとエントリーポイントの id を kagura::run()
に渡すとWebアプリケーションが実行されます。
ソースコード
lib.rs:
extern crate kagura;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn main() {
kagura::run(kagura::Component::new(init, update, render), "app");
}
type State = u64;
enum Msg {
CountUp,
}
struct Sub;
fn init() -> (State, Cmd<Msg, Sub>) {
(0, Cmd::none())
}
fn update(state: &mut State, msg: &Msg) -> Cmd<Msg, Sub> {
match msg {
Msg::CountUp => {
*state += 1;
}
}
Cmd::none()
}
fn render(state: &State) -> kagura::Html<Msg> {
use kagura::Attributes;
use kagura::Events;
use kagura::Html;
let text = if state % 15 == 0 {
"fizzbuzz".to_string()
} else if state % 5 == 0 {
"buzz".to_string()
} else if state % 3 == 0 {
"fizz".to_string()
} else {
state.to_string()
};
let color = if state % 3 == 0 { "#00f" } else { "#000" };
let bg_color = if state % 5 == 0 { "#f00" } else { "#fff" };
Html::button(
Attributes::new()
.style("color", color)
.style("background-color", bg_color)
.style("width", "10ch")
.style("height", "2rem"),
Events::new().on_click(|_| Msg::CountUp),
vec![Html::unsafe_text(text)],
)
}
実行方法
$ npm start
localhost:8080
でページを見られます。
実行結果例
解説
main()
kagura::run()
でアプリケーションを開始しています。kagura::Component::new
でルートコンポーネントとなるコンポーネントを作成し、エントリーポイントのIDを app
としています。
kagura::Component::new()
は第1引数に初期状態、第2引数にupdate、第3引数にrenderを取ります。updateは状態とメッセージを受け取り、状態を更新する関数へのポインタです。renderはメッセージを受け取りHtmlを返す関数へのポインタです。
State
Msg
Sub
状態を表す型を type State = u64
として定義しています。当然ながら、名前は State
でなくとも、統一されていればコンパイルは通ります。
同様にメッセージを表す型を Msg
としています。 Sub
は今回は使いませんが update
の戻り値で必要になるので定義しておきました。Sub
は複数のコンポーネントを組み合わせて使うときに必要になります。
update
状態の更新方法を記述しています。今回は Msg::CountUp
メッセージを受け取ると状態を1増やします。
render
状態の表示方法を記述しています。イベントにはイベントをメッセージにバインドするクロージャを記述します。