RustでビルドしたWASMをNode.jsで扱う方法

2024/12/14に公開

もちべ~しょん

最近typescriptを用いてNode.jsでぼちぼち遊んでるんだけど、そいやWASMでビルドしたRustのライブラリ読み込めないかなというのが元々のとっかかりだった。加えてECMAScript modulesとしてパッケージングする方法が出てこなかった。そんな中、ECMAScript modulesとしてパッケージの作成と利用に成功したこともあってとりあえず現時点での知見をまとめとこうかなぁって。

利用ライブラリとか

  • OS Windows11
  • Node.js Ver 23.4.0
  • tsx 4.19.2
  • typescript 5.7.2
  • nightly-x86_64-pc-windows-msvc (default)
  • rustc 1.85.0-nightly (327c7ee43 2024-12-13)

仕込み

Node.js及びCargoは既に導入済みで利用可能という前提で進めていくので、その点何卒ご了承の程

Rust側

まず、wasm32-unknown-unknownターゲットを利用中のToolchainに追加する必要がある。rustupが既に利用可能なら、以下のコマンドで導入が完了する。

rustup target add wasm32-unknown-unknown

次に、wasm-bindegenのCLIを導入する。これもcargoが導入済みなら以下のコマンドでインストールできる

cargo install -f wasm-bindgen-cli

以上でRust側の仕込みは完了となる[1]

Node.js側

Node.Js側で必要な事前準備は特にないので省略。ただ、今回はpnpmを使うので必要であれば導入しておく必要がある。

Rust側の実装とパッケージ作成

それでは早速Rust側の実装とビルドを行っていくことにしよう。

プロジェクトを作成する

以下のコマンドを実行してとりあえずLibクレートをこさえる

cargo new wasm_lib --lib

次に、作成されたCargo.tomlを以下のように変更しよう

[package]
name = "wasm_lib"
version = "0.1.0"
edition = "2024"

[lib]
# Cdylibを指定
crate-type = ["cdylib"]

[dependencies]
#wasm-bindgenを追加
wasm-bindgen = ">=0.2"

実装

ここまでできたら早速実装に移ろう。今回は単純に整数の足し算を定義してその関数を公開することにする。lib.rsを以下のように実装した。

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add_i64(left: i64, right: i64) -> i64 {
    left + right
}

wasm_bindgenAttributeを指定しておくことを忘れずにしておこう。

ビルドとパッケージ作成

ここまでできたら早速ビルドする。

cargo build --target wasm32-unknown-unknown --release

--releaseの有無は任意で構わない。

次に、ビルドしたwasmをNode.jsで利用できるようにパッケージを作成する。今回はwasm-packが残念ながら使えないので、wasm-bindgenのCLIを使うことにした。

wasm-bindgen --target experimental-nodejs-module --out-dir .\pkg .\target\wasm32-unknown-unknown\release\wasm_lib.wasm

このコマンドの実行後、プロジェクトフォルダ直下にpkgというフォルダが生成される。その中のpackage.jsonを以下のように編集する。

{
  "name": "wasm_lib",
  "version": "1.0.0",
  "main": "wasm_lib.js",
  "types": "wasm_lib.d.ts",
  "type": "module",
  "files": [
    "wasm_lib.js",
    "wasm_lib.d.ts",
    "wasm_lib_bg.wasm"
  ]
}

ちなみに、wasm-bindgenを実行すると、全て上書きされてしまうので、適宜別の場所に保存しておいた方が良いかもしれない。

ここまででRust方面の操作は終了となる。

Node.jsの準備

ここから先は、Node.jsとTypeScriptの操作になる。まずはプロジェクトをこさえよう

pmpn init

ここで、以下のようにpackage.jsonを編集しておく

{
  "name": "hello_wasm",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module"
}

次に、必要なパッケージをインストールしよう

pnpm add typescript tsx -D

そして、先に作成しておいたpkgを追加する(ここではプロジェクトフォルダ直下にコピーしている)

pnpm add ./pkg

また、linkしたければ、以下のようになる

pnpm link ./pkg 

次に、tsconfig.jsonを以下のように定義しておこう

{
  "compilerOptions": {
    "target": "ES2023",
    "module": "ES2022",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "moduleResolution": "Node16"
  }
}

(´・ω・`)実行よ~

ここまでできたらあと一息。プロジェクトフォルダ直下にindex.tsをこさえ手、内容を以下のようにする

import {add_i64} from "wasm_lib";

let ans = add_i64(42n, 114514n);
console.log(ans)

ここまでできたら、とりあえず、tsxを使って動くのか確認してみよう

PS ...> pnpm exec tsx .\index.ts
114556n

以上のように実行できたら成功かなって。

ちなみにビルドして実行するなら

 pnpm exec tsc ;pnpm exec node ./index.js

これでも同様に実行できるはずである。

まとめ

WebやBundlerのビルド方法とその利用方法はそれなりにあったんだけど、Node.jsでJavaScript moduleとして利用する方法が殆ど無かったので自分の備忘録を兼ねてまとめてみた。

脚注
  1. wasm-pack使わないの?って声が聞こえてきそうだけど、targetの部分を見て貰うと解るとおり、CommonJs Modulesにしか対応していないので、今回は使うコトが出来なかった。 ↩︎

Discussion