完成品

スクリーンショット 2023-05-16 21.06.18.png

めちゃくちゃ簡素ですが、今回はNuxt3でWasmを動かすという最低限の目標のためご容赦ください。
また筆者が普段使っているUIフレームワークはReact系統でNext.jsやFresh(deno)です。今回Vue系のフレームワークに初めて触っているので変なところがあったら指摘してくださると助かります。

使用IDE: VSCode
OS: MacOS 13.3.1
(余談ですが筆者は普段JetBrain系のIDEを使用しています。しかしNuxt3の各種機能がIDE側で上手く補完されないため、VSCodeを今回は使用しています。Nuxt3をJetBrains系IDEで上手く使う方法をご存知の方がいらっしゃいましたら教えてくださると幸いです。)

Nuxt3側のインストール

以下コマンドでnuxt3の初期化をします。

npx nuxi@latest init nuxt3-wasm

終わったらプロジェクトのディレクトリに移動してnode_modulesのインストールをします。

cd nuxt3-wasm
yarn install

ここで一度動作確認しておきましょう。

yarn dev

次にwasm-pack用viteプラグイン、vite-plugin-wasm-packをインストールします。

yarn add vite vite-plugin-wasm-pack -D

nuxt.config.ts にプラグインを追加します。

import wasmpack from "vite-plugin-wasm-pack";
export default defineNuxtConfig({
    vite: {
        plugins: [wasmpack("rs_lib")]
    }
})

package.json内にwasmビルド用のコマンドを追加しておくと便利です。

{
  "name": "nuxt-app",
  "private": true,
  "scripts": {
    "build": "nuxt build",
    "dev": "nuxt dev",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare",
+   "wasm": "wasm-pack build rs_lib --target web"
  },
  "devDependencies": {
    "@types/node": "^18",
    "nuxt": "^3.4.3",
    "vite": "^4.3.6",
    "vite-plugin-wasm-pack": "^0.1.12"
  }
}

Rust側のインストール

Rust本体のインストールは終わっているものと仮定します。
wasm-packをインストールします.
wasm-packでRustのコードをWebAssemblyにコンパイルします。

cargo install wasm-pack

cargoプロジェクトを作成します。

cargo new --lib rs_lib

Cargo.toml内に以下の内容を記述します。

[package]
name = "rs_lib"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2.74"

早速Rustでの処理を記述しています。

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: isize, b: isize) -> isize {
    a + b
}

#[wasm_bindgen]
pub fn sub(a: isize, b: isize) -> isize {
    a - b
}

ここでは非常にシンプルなadd関数とsub関数を定義しています。
符号付整数型isizeは使用しているマシンのビットにあったサイズをとる整数型です。64bitのコンピュータならi64型になります。アトリビュート#[wasm_bindgen]の説明はmdn web docがわかりやすいのでそのまま引用します。

wasm-pack は 別のツールの wasm-bindgen を利用して、JavaScript と Rust の型を繋いでいます。wasm-bindgen によって JavaScript が文字列に関する Rust API を呼び出すことや Rust の関数が JavaScript の例外をキャッチすることができるようになります。
引用元: https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm

単体テストも書いておきましょう。


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
    #[test]
    fn test_sub() {
        let result = sub(2, 2);
        assert_eq!(result, 0);
    }
}

testを実行するためには rs_libディレクトリに移動して,cargo testコマンドを実行します。

(base) stead08@MacBook-Air nuxt3-blog % cd rs_lib    
(base) stead08@MacBook-Air rs_lib % cargo test
    Finished test [unoptimized + debuginfo] target(s) in 0.07s
     Running unittests src/lib.rs (target/debug/deps/rs_lib-a79a83277aba64c8)

running 2 tests
test tests::test_add ... ok
test tests::test_sub ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests rs_lib

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

問題なく通ってますね。
次にrustのコードをwasm用にコンパイルします。
プロジェクトのディレクトリに戻って,先ほど設定したyarn wasmコマンドを実行します。

yarn wasm

これでwasm-packによってrustコードがwasm向けにコンパイルされます。そしてrs_lib内にpkgディレクトリが作成されそこにnpmパッケージがビルドされます。

pkg
├── package.json
├── rs_lib.d.ts
├── rs_lib.js
├── rs_lib_bg.wasm
└── rs_lib_bg.wasm.d.ts

これでwasmファイルの作成は完了です。

フロントエンド実装。

app.vueに直接書いてもいい気がしますが、pagesディレクトリを作成してそこにindex.vueを作成する方式でやってみます。
app.vueを以下のようにします。

<template>
  <div>
    <NuxtPage />
  </div>
</template>

新たにpagesディレクトリを作成し、直下にindex.vueを作成します。

<script setup lang="ts">
import init, { add } from "rs_lib";

const two_numbers = ref<{a: number, b: number}>({a: 0, b: 0});
const sum = ref(0); 

onMounted(async () => {
  await init(); 
});

const calculateSum = () => { 
  const a = two_numbers.value.a;
  const b = two_numbers.value.b;
  sum.value = add(a, b); 
};

</script>

<template>
    <div>
        <h1>Nuxt3 * WebAssembly Demo </h1>
        <input type="number" v-model="two_numbers.a" />
        <input type="number" v-model="two_numbers.b" />
        <button @click="calculateSum">Add</button>
        <p>Sum calculated by WebAssembly: {{ sum }}</p> 
    </div>
</template>

vite-plugin-wasm-packによって簡単にwasmが呼び出せるようになっています。

yarn devで動作確認します

yarn dev
スクリーンショット 2023-05-16 21.06.18.png

クライアントサイドで加算処理がwasmで実行され、結果が表示されてます。

まとめ

今回はnuxt3とwasmで超絶シンプルなwebアプリを作成してみました。
次はサーバーサイドで処理させて結果を表示するSSRの部分でもwasmが使えたら使ってみようと思います。