導入
WASM(WebAssembly)は、ブラウザ上で高速にプログラムを動作させるためのバイナリのフォーマットの一種です。C/C++/Rustなどの言語で書かれたコードをブラウザ上で動作させるために、このバイナリフォーマットにコンパイルします。この記事では、RustとWASMを使って、Reactアプリケーションを構築する方法を紹介します。
この記事で触れる点
- Rust + WASM + React環境構築
- Vite + wasm-packでの高速セットアップ
- RustコードをReactから呼び出す基本のパターン実装
Rust × WebAssembly の利点
- 高速
- 安全
- 軽量
- メモリ安全性
特に、画像処理やアルゴリズム、ゲームロジックなど、計算量の多い処理で、ブラウザ上で高速に動作させることができ、技術的な期待値が高いと感じています。
また、Viteと組み合わせることで、高速な開発体験を得られ、エコシステムも充実していると感じています。
使用技術スタック(2025年11月時点)
フロントエンド
- React 19.2
- Vite 7.2.*
- 言語: TypeScript
WebAssembly
- 言語: Rust 1.91.*
- wasm-pack 0.13.1: RustをWASMに変換するツール
- wasm-bindgen 0.2.84: RustとJavaScript間のブリッジ
Viteプラグイン
- vite-plugin-wasm 3.5.0: WASMモジュールの統合
- vite-plugin-top-level-await 1.6.0: async/awaitサポート
環境構築
Requirements
以下のインストールを前提条件とします。
- Node: v24.11.*
- cargo: v1.91.*
インストールされていない場合は各種インストールをしてください。
1. wasm-packのインストール
cargo install wasm-pack
インストールの確認:
wasm-pack --version
# wasm-pack 0.13.1 みたいな表示がでればOK
2. プロジェクトディレクトリの準備
まず、プロジェクト全体を管理するディレクトリを作成します:
mkdir [project-name]
cd [project-name]
3. Reactプロジェクトの作成(Vite使用)
npm create vite@latest frontend
対話形式で以下の選択を行います:
基本的には自由ですが、ここでは、以下のように選択しました。
-
Select a framework:
Reactを選択 -
Select a variant:
TypeScriptを選択 -
Use rolldown-vite (Experimental)?:
Noを選択 -
Install with npm and start now?:
Yesを選択
セットアップが完了したら、開発サーバーをブラウザで確認できます。ブラウザで 表示のlocalhostを開くと、Viteのデフォルトのページが表示されます。 Ctrl+C で停止させます。
次に、依存関係をインストールします。
cd frontend
npm install
4. Vite用WASMプラグインのインストール
frontendディレクトリで、WASMを扱うためのプラグインをインストールします:
npm install -D vite-plugin-wasm vite-plugin-top-level-await
cd ..
5. Vite設定の更新
frontend/vite.config.tsを以下のように書き換えてみます。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'
export default defineConfig({
plugins: [
react(),
wasm(),
topLevelAwait()
],
build: {
target: 'esnext'
}
})
ポイント
-
wasm(): WASMファイルを処理 -
topLevelAwait(): トップレベルでのasync/awaitをサポート -
target: 'esnext': 最新のES機能を有効化
6. Rustプロジェクトの作成
wasm-packのテンプレート機能を使って構築します。
wasm-pack new [wasm-project-name]
7. Cargo.tomlの確認と調整
生成された[wasm-project-name]/Cargo.tomlを確認してください。テンプレートですでに適切な設定がされていますが、最適化のため以下を追加します:
[package]
name = "[wasm-project-name]"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2"
console_error_panic_hook = { version = "0.1.7", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
追加設定の説明:
-
opt-level = "z": WASMファイルサイズを最小化 -
lto = true: 最適化を強化(ビルド時間は長くなるが、実行速度向上) -
codegen-units = 1: 並列コンパイルを無効化して最適化を優先 -
console_error_panic_hook: panicメッセージをブラウザコンソールに表示(デバッグに便利)
動作確認1:デフォルトのWASMを表示
まず、テンプレートで作成されたサンプルコードが動作することを確認してみます。
1. デフォルトのWASMをビルド
wasm-pack newで生成された[wasm-project-name]/src/lib.rsには、すでにサンプルの関数が含まれています。
[wasm-project-name]/src/lib.rs
mod utils;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, [wasm-project-name]!");
}
このまま一度ビルドしてみます
cd [wasm-project-name]
wasm-pack build --target web --out-dir ../frontend/src/wasm
cd ..
2. Reactで呼び出してみる
frontend/src/App.tsxを以下のように編集します
frontend/src/App.tsx
import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import init, { greet } from './wasm/[wasm_project_name]'
// ↑ Cargo.toml の name をスネークケースに変換(例: chess-wasm → chess_wasm)
function App() {
const [wasmReady, setWasmReady] = useState(false)
useEffect(() => {
// WASM モジュールを初期化
init()
.then(() => {
setWasmReady(true)
})
.catch((err) => {
console.error('WASM初期化エラー:', err)
})
}, [])
const handleClick = () => {
if (wasmReady) {
greet()
}
}
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React + WASM</h1>
{wasmReady ? (
<div className="card">
<button onClick={handleClick}>Rustの関数を呼び出す</button>
</div>
) : (
<p>Loading...</p>
)}
</>
)
}
export default App
ポイント
-
init(): WASMバイナリを読みこんで、メモリを初期化(wasm-bindgenの自動生成) -
wasmReady: 初期化完了フラグ(完了前の関数呼び出しエラーを防ぐ)
3. 開発サーバーを起動
npm run dev
以下のような画面が表示され、ボタンを押すとalertが表示されるはずです。
動作確認2:Rustに関数を追加
次に、独自の関数を追加して、変更が反映されることを確認します。
1. 簡単な計算関数を追加
[wasm-project-name]/src/lib.rsを以下のように編集
[wasm-project-name]/src/lib.rs
mod utils;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, chess-wasm!");
}
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[wasm_bindgen]
pub fn message(name: &str) -> String {
format!("Hello, {}!", name)
}
編集し終わったら、再ビルドしてください。
2. React側で指定した関数を使う
frontend/src/App.tsxを更新
frontend/src/App.tsx
import { useState, useEffect } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import init, { greet, add, message } from './wasm/[wasm_project_name]'
// ↑ Cargo.tomlのnameをスネークケースに変換(例: chess-wasm → chess_wasm)
function App() {
const [wasmReady, setWasmReady] = useState(false)
const [addResult, setAddResult] = useState<number | null>(null)
const [messageResult, setMessageResult] = useState<string>('')
useEffect(() => {
// WASMモジュールを初期化
init().then(() => {
setWasmReady(true)
}).catch(err => {
console.error('WASM初期化エラー:', err)
})
}, [])
const handleClick = () => {
if (wasmReady) {
greet()
}
}
const handleClickAdd = () => {
if (wasmReady) {
const result = add(1, 2)
console.log(result)
setAddResult(result)
}
}
const handleClickMessage = () => {
if (wasmReady) {
const result = message("Rust")
console.log(result)
setMessageResult(result)
}
}
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React + WASM</h1>
{wasmReady ? (
<div className="card">
<button onClick={handleClick}>
Rustの関数を呼び出す
</button>
</div>
) : (
<p>Loading...</p>
)}
<div className="card">
<button onClick={handleClickAdd}>
add
</button>
<p>結果: {addResult !== null ? addResult : '未実行'}</p>
</div>
<div className="card">
<button onClick={handleClickMessage}>
message
</button>
<p>結果: {messageResult || '未実行'}</p>
</div>
</>
)
}
export default App
3. 開発サーバーを起動
開発サーバーを起動し、各ボタンをクリックして動作を確認しましょう。
以下のように表示されれば、成功です。
最後に
この記事では、2025年の最新技術スタックを使って、Rust + WebAssembly + Reactの開発環境を構築しました。
今後の記事では、実際のパフォーマンスの比較や、このモノレポをベースとしたゲームロジックの開発の記事を投稿しようと考えております。

