本記事はQiita Advent Calender 2025 moonbitおよびわたなべの16日目です!
16日目は、私渡邊が作成します!
本記事では、MoonBitでWebAssembly(wasm)をサクッと実装し、Node.jsおよびbrowserから呼び出してみたいと思います!
- wasmをきいたことはあるが触ったことはない
- moonbitに興味がある
- 公式ドキュメントや記事を読んだがうまくいかなかった
という方にちょうど良い入門記事かと思います
MoonBitのプロジェクトをセットアップする
2025/12/21現在、moonbitのシンタックスハイライトがQiitaに存在していないため、Rustで代用しております
1. インストール
こちらのドキュメントの通り、MoonBitをインストール。
moon new <project_name>
でテンプレートが立ち上がります。今回私はmoonbit_wasmという名前にしてみました。
2. 動作確認
moon run cmd/main
これで動作するかと思います(2025/12/21現在)。
cmd/main/main.mbtが実行される形です。
moon newで立ち上げるとフィボナッチ数列を計算するコードがrootにある.mbtファイルに記述されており、それをmain.mbtから呼び出す構造になっています。
///|
fn main {
println(@lib.fib(10))
}
このように10番目の数列を計算するようになっているため、この10を変更すれば値が変わることも確認できれば、問題なく動作しているかと思われます。
wasmとしてbuildする
rootに記述されているコード
pub fn fib(n : Int) -> Int64 {
loop (n, 0L, 1L) {
(0, _, b) => b
(i, a, b) => continue (i - 1, b, a + b)
}
}
このフィボナッチ数列を計算する関数をwasmとしてbuildします。
1. moon.pkg.jsonへの記述
wasmとしてbuildする関数をmoon.pkg.jsonにて指定することが必要です。cmd/main/moon.pkg.jsonというファイルもありますが編集するのはrootにある方ですね。
{
"link": {
"wasm": {
"exports": ["fib"]
}
}
}
exportする関数名を記述します。"link"や"wasm"など全てmoonbitの拡張を入れていれば補完が効きましたので、自然に入力できるかと思います。
2. buildする
moon build --target wasm
これで、指定した関数がwasmとしてbuildされます。
/target/wasm/release/build/moonbit_wasm.wasmに.wasmファイルが生成されていれば成功です。
ファイル名がmoombit_wasm.wasmなのは、私がmoon newの時にmoombit_wasmを指定したからですね。各々、指定したプロジェクト名を冠したファイルが生成されるかと思います。
/target配下は、moon new時に生成される.gitignoreに指定されているので、勝手にgitに記録されないようになっているところは親切ですね。
wasmを呼んで実行してみる
せっかく作ったので実行してみましょう!
1. ローカルのNode.jsから実行してみる
まずは一番簡単な感じで、Node.jsから実行してみましょう。
私はbunを使ったので、npmやpnpm使う方は適宜読み替えていただければと思います。
MoonBitのrootにbunの関連ファイルがごちゃごちゃと置かれるのはちょっとしんどかったので、フォルダを作ってその中から実行しています。
mkdir node_for_wasm
cd node_for_wasm
bun init
> Blankを選択してbun run index.tsで動作すれば問題なく準備できています。
import fs from "node:fs/promises";
// wasmのパスは適宜書き換えてください
const bytes = await fs.readFile("../target/wasm/release/build/moonbit_wasm.wasm");
const { instance } = await WebAssembly.instantiate(bytes, {});
const fib = instance.exports.fib as (n: number) => bigint;
console.log(fib(10).toString());
index.tsをこんな感じに書き換えて、無事計算できれば成功です。
2. Browserから実行してみる
rootに次のhtmlファイルを配置します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>first_wasm</title>
</head>
<body>
<h1>fib calculator</h1>
<div>
<input id="input">
<button id="button">calc fib</button>
<p>has not called</p>
</div>
</body>
<script defer>
let wasmInstance;
// wasm をロード
async function loadWasm() {
const response = await fetch("./target/wasm/release/build/moonbit_wasm.wasm");
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {});
wasmInstance = instance;
}
// 初期ロード
loadWasm();
// ボタン押下時の処理
const button = document.getElementById("button");
const input = document.getElementById("input");
button.addEventListener("click", () => {
if (!wasmInstance) {
alert("wasm not loaded yet");
return;
}
const raw = input.value.trim();
const n = parseInt(raw, 10);
if (
Number.isNaN(n) ||
n < 1 ||
n > 91 ||
n.toString() !== raw
) {
alert("91以下の自然数を入力してください");
return;
}
// wasm の関数を呼ぶ(例: add)
const result = wasmInstance.exports.fib(n);
// <p> を書き換える
const p = document.querySelector("p");
p.textContent = `result: ${result}`;
});
</script>
</html>
ターミナルからnpx serveしてlocalhost:3000にアクセス。
実行して計算されれば成功です。
3. web上にdeployして実行
今回はNetlifyにdeployします。
先述していますが/targetはgitに記録されず、流石にこれをGitHubにアップしてしまうのは微妙なので、Netlify上でBuildしてもらいます。
netlify.tomlをルートに追加。Build commandを記述します。
[build]
command = "curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash && export PATH=\"$HOME/.moon/bin:$PATH\" && moon build --target wasm --release"
publish = "."
MoonBitをインストールして、moon buildしています。
これで、GitHubと連携すれば問題なく動作するかと思います。
Deploy例
うまくいけばこんな感じで動作するかと思います。
ということで、私も無事wasmの世界に入門することができました。
公開するほどのものでもないかもしれませんがGitHubがこちら。
簡単に触ってみて
wasmは難しそうというイメージがありましたが、流石にwasmでの利用を視野に入れて作られた言語だけあってすんなりと実装までいけました。
今回はフィボナッチ数列の計算で恩恵は少ないかもしれませんが、MoonBitはJSONを直接matchできる素晴らしい機能がありますので、JSON validatorなんかは明確にメリットを活かせるかと思います。
Rustとの書き味の違いなども実際触ってみて感じましたが、またの機会に書いてみたいと思います!
