[RustでWasm]Hello, world

rust-wasm Rust

近年流行っていると噂のWebAssemblyの環境構築を試したので、忘備録として書いておきます。
フォルダ構成は以下のようになります。

.
├── README.md
├── wasm
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── wasm.md
│   ├── pkg
│   ├── src
│   └── target
└── web
    ├── README.md
    ├── bootstrap.js
    ├── index.html
    ├── index.ts
    ├── package-lock.json
    ├── package.json
    ├── tsconfig.json
    ├── web.md
    └── webpack.config.js

コードはgithubに置いています。

Rustの環境構築

使用したRustのバージョンは以下。

$ cargo --version
cargo 1.74.0-nightly (96fe1c9e1 2023-08-29)

$ rustc -V
rustc 1.74.0-nightly (35e416303 2023-09-01)

$ rustup -V
rustup 1.26.0 (5af9b9484 2023-04-05)

Rustのlibraryの作成

mkdir wasm
cd wasm
cargo init --lib

これでライブラリ用のパッケージ環境が作成されます。
まずはwasm-packをinstallします。

cargo install wasm-pack

次にWasmのためのライブラリwasm-bindgenと軽量なメモリアロケータwee_allocをinstallします。
今回用いるCargo.tomlは以下のようになります。

[package]
name = "wasm-sample"
version = "0.1.0"
edition = "2021"

[dependencies]
wasm-bindgen = "0.2.63"
wee_alloc = "0.4.5"

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

Hello, worldの実装

今回は画面にalertを出すrustのコードを書いてみます。
lib.rsを以下のように書き換えます。

use wasm_bindgen::prelude::*;

#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
pub fn hello() {
    alert("hello, world!");
}

#[wasm_bindgen]
extern "C" {
    pub fn alert(name: &str);
}

ここで、 pub fn alertはJavaScriptのalertを呼び出す関数です。詳しい仕組みは rust-wasmを参照してください。
以下のコマンドでwasmのファイルをビルドします。

wasm-pack build --target web

これでpkgフォルダが作成され、以下のようにwasmのためのコードが生成されたと思います。

├── pkg
│   ├── README.md
│   ├── package.json
│   ├── wasm_sample.d.ts
│   ├── wasm_sample.js
│   ├── wasm_sample_bg.wasm
│   └── wasm_sample_bg.wasm.d.ts

これを適切にフロントエンドから呼び出せばHello, world成功です。これで一旦wasm側は終了となります。

フロントエンドの環境構築

今回は素朴にwebpack+typescriptを用いたフロントエンドの環境を構築します。使用したNode.jsのバージョンは以下。

$ node --version
v16.16.0

$ npm --version
8.11.0

NodeModules, wasmのinstall

まずpackage.jsonを以下のように作成します。

{
  "name": "wasm-game",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "dev": "webpack-dev-server",
    "build": "webpack build"
  },
  "keywords": [],
  "author": "ganemaruko",
  "dependencies": {
    "copy-webpack-plugin": "^10.0.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1"
  },
  "devDependencies": {
    "ts-loader": "^9.4.4",
    "typescript": "^5.2.2",
    "webpack-dev-server": "^4.6.0"
  }
}

そして、npm installを実行

npm install

ここで、先ほどの手順でビルドしておいたwasmをnode_modulesに加えます。package.jsonを以下のように変更します。

{
  "name": "wasm-game",
  ...略
  "dependencies": {
    "copy-webpack-plugin": "^10.0.0",

    "webpack": "^5.65.0",
    !この一行を追加!
    "wasm-sample": "file:../wasm/pkg",
    "webpack-cli": "^4.9.1"
  },
  "devDependencies": {
    "ts-loader": "^9.4.4",
    "typescript": "^5.2.2",
    "webpack-dev-server": "^4.6.0"
  }
}

wasm-sampleのバージョン部分に相対パスを入れることで、npm install実行時に該当箇所からモジュールをinstallしてくれます。
この状態で以下を実行してください。

npm install wasm-sample

これでwasmがtypescriptから呼び出せるようになったはずです

wasm-sampleという名前はrust側のモジュール名と一致していなければならないことに注意してください

また、webpackとtypescriptを用いるので、webpack.config.jstsconfig.jsonも作成しておきます。詳細は割愛しますが、以下のようになります。

const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  entry: "./index.ts",
  output: {
    path: path.resolve(__dirname, "public"),
    filename: "index.js",
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  mode: "development",
  plugins: [
    new CopyWebpackPlugin({
      patterns: [{ from: "./index.html", to: "./" }],
    }),
  ],
};

index.tsをコンパイルしてpublic/index.jsに出力する、というところだけ押さえていただければ大丈夫です。
合わせてtsconfig.jsonは以下になります。

{
    "compilerOptions": {
      "outDir": "./public/",
      "noImplicitAny": true,
      "module": "es6",
      "target": "es5",
      "allowJs": true,
      "moduleResolution": "node"
    }
  }

これてtypescriptからwasmを呼ぶ出す準備が整いました。

index.tsの作成

wasmファイルを呼び出すindex.tsを作成します。以下のようなコードになります。

import init, { hello } from "wasm-sample";

init().then((_) => {
  hello();
  console.log("OK!");
});

wasm-sampleが正しくinstallできていればhelloにきちんと型が当たっているはずです。

先ほどのwebpack.config.jsでこのindex.tspublic/index.jsにコンパイルされるように設定されているので、index.htmlを以下のように作成します。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Wasm Sample</title>
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

これで準備OKです。webpack-dev-serverを起動します。

npm run dev

これでlocalhost:8080を確認すると、以下のようにalertが上がっているのが確認できると思います。

これでrustをコンパイルしたwasmをtypescriptから呼びだせました。

コメント

タイトルとURLをコピーしました