[Kagura] Kagura + Rust でWebページを作成

# 概要

ボタンを押すと、数字がカウントアップされていき、3の倍数の時は数字の代わりに青文字でFizzと、5の倍数の時は数字の代わりに赤背景でBuzzと、3と5両方の倍数の時は数字の代わりに赤背景に青文字でFizzBuzzと表示するWebアプリケーションを作ります。

# 準備

## wasm-pack をインストール

※cargoが必要です

```shell
$ cargo install wasm-pack
```

## クレートを作成

```shell
$ cargo new --lib fizzbuzz
$ cd fizzbuzz
```

この時点でのディレクトリ構成（一部省略）：

```
fizzbuzz
　├cargo.toml
　└src
　　└lib.rs
```

## webpack等のインストール

※npmが必要です
※yarn等を使う場合は適宜読み替えてください

```shell
$ 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` というファイルを作り、以下のように記述します。

```javascript
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：

```javascript
import("../pkg");
```

index.html：

```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` の例：

```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` の例：

```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：

```rust
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)],
    )
}
```

### 実行方法

```shell
$ npm start
```

`localhost:8080` でページを見られます。

### 実行結果例

<img width="64" alt="fizzbuzz.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199139/65c6bc50-4317-49f2-bc36-9bb96e31622c.png">

### 解説

#### `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`

状態の表示方法を記述しています。イベントにはイベントをメッセージにバインドするクロージャを記述します。

# For your information

[[Kagura] Rustでフロントエンドフレームワークを作ってみた。](https://qiita.com/ne_no_usa/items/9a062c1a6e09078a9ed6)