AxumでWASM

AxumでWASMを利用してみます。

WASMで引数に＋１して戻す関数を作成し、結果をブラウザに表示する、というプログラムを作成してみます。

最初にWASMプロジェクトを作成します。
プロジェクト名を「wasm1」とします。

```
wasm-pack new wasm1
cd wasm1
```

Cargo.tomlファイルは下記のとおりに作成されます。

wasm1/Cargo.toml

```
[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]

[dependencies]
wasm-bindgen = "0.2.84"

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }

[dev-dependencies]
wasm-bindgen-test = "0.3.34"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
```

lib.rsファイルの内容をプラス１する内容に書き換えます。

wasm1/src/lib.rs

```
mod utils;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn plus1(x: i32) -> i32 {
    x+1
}
```

WASMプロジェクトをビルドします。

```
wasm-pack build --target web
```

別フォルダにWebプロジェクト（webapp1）を作成します。

```
cd ..
cargo new webapp1
cd webapp1
```

Cargo.tomlファイルのdependenciesに依存関係を追加します。

webapp1/Cargo.toml

```
[dependencies]
axum = "0.5"
hyper = "0.14"
tokio = { version = "1", features = ["full"] }
tower = "0.4"
```

main.rsファイルにWebサーバーを設定する為、書き換えます。

webapp1/src/main.rs

```
use axum::{
    response::{IntoResponse, Response},
    routing::get,
    Router,
};
use hyper::header::{HeaderValue, CONTENT_TYPE};
use hyper::Body;
use std::{fs, net::SocketAddr};
use tokio::main;

#[main]
async fn main() {
    let app = Router::new()
        .route("/", get(serve_index))
        .route("/static/wasm1.js", get(serve_js))
        .route("/static/wasm1_bg.wasm", get(serve_wasm));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("Listening on http://{}", addr);

    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn serve_index() -> impl IntoResponse {
    let index_html = fs::read_to_string("static/index.html").unwrap();
    Response::builder()
        .header(CONTENT_TYPE, HeaderValue::from_static("text/html"))
        .body(Body::from(index_html))
        .unwrap()
}

async fn serve_js() -> impl IntoResponse {
    let js = fs::read("static/wasm1.js").unwrap();
    Response::builder()
        .header(CONTENT_TYPE, HeaderValue::from_static("application/javascript"))
        .body(Body::from(js))
        .unwrap()
}

async fn serve_wasm() -> impl IntoResponse {
    let wasm_bytes = fs::read("static/wasm1_bg.wasm").unwrap();
    Response::builder()
        .header(CONTENT_TYPE, HeaderValue::from_static("application/wasm"))
        .body(Body::from(wasm_bytes))
        .unwrap()
}
```

ホームページ（index.html）ファイルを作成します。

```
mkdir static
cd static
touch index.html
```
webapp1/index.html

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WASM with Axum</title>
</head>
<body>
    <h1>Hello, WASM with Axum!</h1>
    <p id="result"></p>
    <script type="module">
        import init, { plus1 } from './static/wasm1.js';

        async function run() {
            await init();
            const result = plus1(42);
            document.getElementById("result").textContent = `42 + 1 = ${result}`;
        }

        run();
    </script>
</body>
</html>
```

wasm1プロジェクトでコンパイル後に作成された
- wasm1.js
- wasm1_bg.wasm
ファイルをwebapp1プロジェクトのstaticフォルダ下にコピーします。

```
cd ../../wasm1/pkg
cp -r ./wasm1.js ../../webapp1/static/
cp -r ./wasm1_bg.wasm ../../webapp1/static/
```

webapp1プロジェクトに戻ってコンパイル＆実行します

```
cd ../../webapp1
cargo build
cargo run
```

```localhost:3000/```にアクセスすると画面上に「42+1＝43」と表示されます。

![20240604_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/87133/d426178f-62ce-fcf9-c3b6-e45aa77e8b43.png)

環境
ホストPC：Windows10
VirtualBox：ArchLinux
cargo：cargo 1.78.0 (54d8815d0 2024-03-26)
rustup：rustup 1.27.1 (54dd3d00f 2024-04-24)
wasm-pack：wasm-pack 0.12.1

以上