🦀

vite (react.ts) で wasmを動かす

6 min read

対象

  • WebAssemblyに興味があり、簡単なサンプルを動かしてみたい方

環境

$ sw_vers
ProductName:	macOS
ProductVersion:	11.4
BuildVersion:	20F71

$ cargo --version
cargo 1.53.0 (4369396ce 2021-04-27)

$ yarn --version
1.22.10

$ wasm-pack --version
wasm-pack 0.10.1

viteでプロジェクトの準備

先にviteでプロジェクトを作り、その中にwasm用のプロジェクトを作ります。

$ yarn create vite
$ cd <project_name>
$ yarn
$ yarn dev

3000ポートで以下を確認できます。
vite-init-page

wasmの準備

テンプレートを利用します。
プロジェクト名はわかりやすいようにwasmにしてます。

$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
Project Name: wasm

現時点でのプロジェクト構成はこんな感じになってます。

$ ls
index.html	package.json	tsconfig.json	wasm
node_modules	src		vite.config.ts	yarn.lock

wasmのビルド

wasm-packでビルドします。
未インストールなら、以下でインストールしておきます。

$ cargo install wasm-pack

デフォルトだと、webpack用にビルドされてしまうので、targetでwebを指定します。
cf. https://github.com/rustwasm/wasm-pack/issues/790#issuecomment-583893485

$ wasm-pack build --target web

warningで以下のような警告が出るので、set_panic_hookは外しておいて良いかもしれません。

warning: function is never used: `set_panic_hook`

これで、wasm/pkg配下にwasmがビルドされました。

viteでwasmを使う

App.tsxを修正します。

src/App.tsx
import React, {useEffect, useState} from 'react'
import logo from './logo.svg'
import './App.css'
+ import init, { greet } from '../wasm/pkg'

function App() {
  const [count, setCount] = useState(0)

+  useEffect(() => {
+    init()
+  }, [])

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>Hello Vite + React!</p>
        <p>
 -          <button type="button" onClick={() => setCount((count) => count + 1)}>
-+          <button type="button" onClick={() => greet()}>
+            count is: {count}
          </button>
        </p>
        <p>
          Edit <code>App.tsx</code> and save to test HMR updates.
        </p>
        <p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
          {' | '}
          <a
            className="App-link"
            href="https://vitejs.dev/guide/features.html"
            target="_blank"
            rel="noopener noreferrer"
          >
            Vite Docs
          </a>
        </p>
      </header>
    </div>
  )
}

export default App

ボタンをクリックすることで、動作確認できます。
wasm-sample

他の例

rust-cookbookの一番最初にある generate-random-numbersをwasm化してみます。

rustのコード追加

generate-random-numbersのコードを参考にして、以下のように追加します。

wasm/src/lib.rs
mod utils;

use wasm_bindgen::prelude::*;
+ use rand::Rng;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, wasm!");
}

+ #[wasm_bindgen]
+ pub fn gen_rand_num() {
+     let mut rng = rand::thread_rng();
+
+     let n1: u8 = rng.gen();
+     alert(&format!("Random u8: {}", n1));
+ }

Cargo.tomlにrandを追加しておきます。

wasm/Cargo.toml
[package]
name = "wasm"
version = "0.1.0"
authors = ["pilefort <pilefort2020@gmail.com>"]
edition = "2018"

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

[features]
default = ["console_error_panic_hook"]

[dependencies]
wasm-bindgen = "0.2.63"
+ rand = "0.8.4"

# 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.6", optional = true }

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.5", optional = true }

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

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

この状態でビルドすると、以下のエラーが出るので、エラーの指示に従います。

$ wasm-pack build --target web
error: the wasm32-unknown-unknown target is not supported by default, you may need to enable the "js" feature. For more information see: https://docs.rs/getrandom/#webassembly-support
wasm/Cargo.toml
[dependencies]
wasm-bindgen = "0.2.63"
rand = "0.8.4"
+ getrandom = { version = "0.2", features = ["js"] }

もう一度ビルドすると、ビルドに成功します。

$ wasm-pack build --target web
...
[INFO]: ✨   Done in 5.70s
[INFO]: 📦   Your wasm pkg is ready to publish at /Users/<user_name>/<project_name>/wasm/pkg.

src/App.tsxの修正

src/App.tsx
- import init, { greet } from '../wasm/pkg'
+ import init, { gen_rand_num } from '../wasm/pkg'

...

- <button type="button" onClick={() => greet()}>
-+ <button type="button" onClick={() => gen_rand_num()}>
+

動作確認

このような感じでランダムな数値を出せるようになりました。
random-number-wasm

サンプルコード

https://github.com/pilefort/vite-react-ts-with-wasm-sample

https://github.com/pilefort/vite-react-ts-with-wasm-sample/tree/samples/generate-random-number

以上

Discussion

ログインするとコメントできます