メインコンテンツまでスキップ

Cursor での WebAssembly 開発:Rust、AssemblyScript、ブラウザ統合

WebAssembly Development in Cursor

WebAssembly(WASM)は、コンパイルされたコードを JavaScript と並行して実行することで、ブラウザにネイティブに近いパフォーマンスをもたらします。AI駆動の IDE エディタである Cursor は、Rust および AssemblyScript プロジェクトのスキャフォールディング、wasm-pack 設定の生成、AI支援によるホットループの最適化により、WebAssembly 開発を加速します。このガイドは、プロジェクトのセットアップからブラウザ統合までの完全なワークフローをカバーします。

前提条件​

始める前に、以下のものがインストールされていることを確認してください:

  • IDE エディタとしての Cursor(最新バージョンを推奨)
  • Rust から WASM へのコンパイル用の Rust ツールチェーン(rustup、cargo)
  • JavaScript ツールと AssemblyScript 用の Node.js 18 以上
  • Rust を WebAssembly パッケージにビルドするための wasm-pack
  • テスト用のモダンブラウザ(Chrome、Firefox、または Edge)
ヒント

Rust ツールチェーンがない場合、Cursor の統合ターミナルでインストールできます。Rust インストーラーを実行した後、rustup target add wasm32-unknown-unknown で WASM ターゲットを追加します。cargo install wasm-pack で wasm-pack をインストールするか、公式インストーラースクリプトを使用します。

Cursor での Rust から WebAssembly への変換​

Rust は、パフォーマンス、安全性、ファーストクラスのツールサポートにより、WebAssembly をターゲットとする最も人気のある言語です。Cursor は Rust WASM モジュールのスキャフォールディング、ビルド、デバッグを支援します。

Rust WASM プロジェクトの作成​

Cursor で新しいフォルダを開き、AI チャットを使用してプロジェクトを生成します:

Scaffold a Rust project for WebAssembly that:
- Uses wasm-bindgen to bridge Rust and JavaScript
- Exports a function that adds two numbers
- Exports a function that reverses a string
- Includes a Cargo.toml configured for cdylib
- Has a unit test file

Cursor は標準的な Rust WASM 構造を生成します:

rust-wasm-app/
src/
lib.rs
utils.rs
tests/
web.rs
Cargo.toml

生成された Cargo.toml:

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

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

[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"

[dev-dependencies]
wasm-bindgen-test = "0.3"

[profile.release]
opt-level = "s"
lto = true
codegen-units = 1

生成された src/lib.rs:

use wasm_bindgen::prelude::*;

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

#[wasm_bindgen]
pub fn reverse_string(input: &str) -> String {
input.chars().rev().collect()
}

#[wasm_bindgen]
pub fn factorial(n: u32) -> u64 {
(1..=n as u64).product()
}

wasm-pack でのビルド​

Cursor のターミナルにより、wasm-pack の実行がシームレスになります。AI にビルドスクリプトの作成を依頼します:

Create a build script that runs wasm-pack with the correct target and
output directory, then copies the generated pkg folder into a web folder.

Cursor は build.sh を生成します:

#!/usr/bin/env bash
set -e

echo "Building Rust to WebAssembly..."
wasm-pack build --target web --out-dir pkg

echo "Copying package to web folder..."
mkdir -p web/pkg
cp -r pkg/* web/pkg/

echo "Build complete. Open web/index.html in a browser."

Cursor のターミナルから実行します:

chmod +x build.sh
./build.sh

WASM のテスト作成​

Rust WASM 関数は wasm-bindgen-test でテストできます。Cursor はテストファイルを生成します:

use rust_wasm_app::{add, reverse_string, factorial};
use wasm_bindgen_test::*;

#[wasm_bindgen_test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}

#[wasm_bindgen_test]
fn test_reverse_string() {
assert_eq!(reverse_string("cursor"), "rosruc");
assert_eq!(reverse_string(""), "");
}

#[wasm_bindgen_test]
fn test_factorial() {
assert_eq!(factorial(0), 1);
assert_eq!(factorial(5), 120);
assert_eq!(factorial(10), 3628800);
}

Cursor のターミナルでテストを実行します:

wasm-pack test --headless --firefox
警告

WebAssembly テストはブラウザエンジン内で実行され、ネイティブな Rust テストとしては実行されません。wasm-pack test の実行時に --chrome、--firefox、--safari でブラウザを指定する必要があります。ブラウザがインストールされていない場合、テストは接続エラーでサイレントに失敗します。エラー出力を Cursor の AI チャットに貼り付ければ、診断を支援できます。

Cursor での AssemblyScript 開発​

AssemblyScript は TypeScript に似た構文を直接 WebAssembly にコンパイルするため、TypeScript に既に慣れている開発者にとって優れた選択肢です。構文が TypeScript と重なるため、Cursor は AssemblyScript の強力なサポートを提供します。

AssemblyScript プロジェクトのセットアップ​

新しいフォルダを作成し、Cursor に AssemblyScript プロジェクトのスキャフォールディングを依頼します:

Scaffold an AssemblyScript project that:
- Has an assembly/index.ts entry file
- Exports functions for image processing (grayscale, invert)
- Includes an asconfig.json for release builds
- Has a package.json with the assemblyscript dependency

Cursor は以下を生成します:

assemblyscript-app/
assembly/
index.ts
image.ts
asconfig.json
package.json
index.html

assembly/index.ts:

export function grayscale(
pixels: Uint8Array,
width: i32,
height: i32
): Uint8Array {
const result = new Uint8Array(pixels.length);
for (let i = 0; i < pixels.length; i += 4) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
const gray = <u8>((r * 77 + g * 150 + b * 29) >> 8);
result[i] = gray;
result[i + 1] = gray;
result[i + 2] = gray;
result[i + 3] = pixels[i + 3]; // preserve alpha
}
return result;
}

export function invert(pixels: Uint8Array): Uint8Array {
const result = new Uint8Array(pixels.length);
for (let i = 0; i < pixels.length; i += 4) {
result[i] = 255 - pixels[i];
result[i + 1] = 255 - pixels[i + 1];
result[i + 2] = 255 - pixels[i + 2];
result[i + 3] = pixels[i + 3];
}
return result;
}

asconfig.json:

{
"entries": ["assembly/index.ts"],
"targets": {
"release": {
"optimizeLevel": 3,
"shrinkLevel": 0,
"converge": false,
"noAssert": false
},
"debug": {
"debug": true
}
},
"options": {
"exportRuntime": true,
"exportMemory": true
}
}

AssemblyScript のビルド​

Cursor のターミナルで依存関係をインストールしてビルドします:

npm install
npx asc assembly/index.ts --target release --outFile build/release.wasm
ヒント

AssemblyScript の構文は TypeScript に非常に近いため、Cursor の TypeScript 言語サーバーが役立つインライン診断を提供します。主な違いは、AssemblyScript では i32、u8、f64 のような明示的な数値型が必要なことです。Cursor に「この TypeScript 関数を有効な AssemblyScript に変換して」と依頼すると、型を正しく注釈します。

ブラウザ統合​

WebAssembly はブラウザからロードして呼び出せる場合にのみ有用です。Cursor は Rust と AssemblyScript の両方のモジュールの JavaScript グルーコードを生成できます。

Rust WASM モジュールのロード​

Cursor に Rust パッケージをロードする HTML ページの作成を依頼します:

Create a web/index.html that:
- Loads the wasm module using the web target
- Calls the add and reverse_string functions
- Displays the results on the page
- Includes error handling for failed module loading

Cursor は web/index.html を生成します:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Cursor WASM Demo</title>
</head>
<body>
<h1>WebAssembly in Cursor</h1>
<p>Add result: <span id="add-result"></span></p>
<p>Reverse result: <span id="reverse-result"></span></p>
<p>Factorial result: <span id="factorial-result"></span></p>

<script type="module">
import init, { add, reverse_string, factorial } from './pkg/rust_wasm_app.js';

async function run() {
try {
await init();
document.getElementById('add-result').textContent = add(40, 2);
document.getElementById('reverse-result').textContent = reverse_string('hello');
document.getElementById('factorial-result').textContent = factorial(10);
} catch (err) {
console.error('Failed to load WASM module:', err);
}
}

run();
</script>
</body>
</html>

AssemblyScript モジュールのロード​

AssemblyScript はスタンドアロンの .wasm ファイルを生成し、手動でインスタンス化します。Cursor はローダーを生成します:

async function loadAssemblyScriptModule() {
const response = await fetch('build/release.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
env: {
abort: (msg, file, line, column) => {
console.error(`Abort at ${file}:${line}:${column}: ${msg}`);
},
},
});
return instance.exports;
}

const exports = await loadAssemblyScriptModule();
const grayPixels = exports.grayscale(rgbaPixels, width, height);

JavaScript と WASM 間のメモリ共有​

画像処理などのパフォーマンスクリティカルな操作では、データのコピーを避けてください。WebAssembly のリニアメモリを使用してメモリを直接共有します。Cursor はメモリブリッジを生成します:

// JavaScript side
function processImageWithWasm(wasmExports, imageData) {
const { __new, __pin, __unpin, grayscale } = wasmExports;
const ptr = __new(imageData.length, 1); // 1 = Uint8Array id
const wasmMemory = new Uint8Array(wasmExports.memory.buffer);
wasmMemory.set(imageData, ptr);
const resultPtr = grayscale(ptr, width, height);
__pin(resultPtr);
const result = wasmMemory.slice(
resultPtr,
resultPtr + imageData.length
);
__unpin(resultPtr);
return result;
}
警告

WebAssembly のリニアメモリは割り当て中に成長する可能性があり、保持している JavaScript の TypedArray ビューが無効になります。メモリを割り当てる可能性のある WASM 関数の後には、必ず wasmExports.memory.buffer からビューを再作成してください。出力にランダムなゴミが見える場合、古いメモリビューがほぼ常に原因です。

AI 支援の WASM 最適化​

Cursor の AI は、メモリレイアウト、SIMD 命令、アルゴリズムの複雑さを推論できるため、パフォーマンスクリティカルな WASM コードの最適化に特に効果的です。

ホットループの最適化​

Rust または AssemblyScript コードのループを選択し、Cursor に尋ねます:

Optimize this loop for WebAssembly. Use SIMD where possible and
minimize memory allocations inside the loop.

Cursor は Rust の std::simd を使用してスカラーループをベクトル化バージョンに書き直します:

use std::simd::{f32x4, SimdFloat};

#[wasm_bindgen]
pub fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 {
let mut sum = f32x4::splat(0.0);
let chunks = a.chunks_exact(4).zip(b.chunks_exact(4));
for (a_chunk, b_chunk) in chunks {
let va = f32x4::from_slice(a_chunk);
let vb = f32x4::from_slice(b_chunk);
sum += va * vb;
}
let mut total = sum.reduce_sum();
let remainder = a.len() % 4;
for i in (a.len() - remainder)..a.len() {
total += a[i] * b[i];
}
total
}

バイナリサイズの削減​

大きな WASM バイナリはページ読み込みを遅くします。Cursor にバイナリの分析を依頼します:

My WebAssembly binary is 1.2 MB. Suggest Cargo.toml and code changes
to reduce the binary size for web delivery.

Cursor はプロファイル調整とデッドコード除去を推奨します:

[profile.release]
opt-level = "z" # optimize for size
lto = true # link-time optimization
codegen-units = 1 # single codegen unit
panic = "abort" # no unwinding tables
strip = true # strip symbols
// Avoid formatting and panicking code in hot paths
#[cfg(not(feature = "debug"))]
#[panic_handler]
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
loop {}
}
ヒント

追加のサイズ削減のためにポストビルドステップとして wasm-opt を使用します。Cursor に「ビルドスクリプトに -Oz で wasm-opt パスを追加して」と依頼すると、wasm-pack ステップの後にコマンドを挿入します。これにより最終バイナリがさらに 10% から 20% 削減されます。

Cursor での WebAssembly デバッグ​

WASM のデバッグは、エラーが生のトラップとして現れるため、JavaScript のデバッグよりも困難です。Cursor はいくつかの戦略を提供します。

Rust WASM のソースマップ​

ソースマップを有効にして、Cursor とブラウザの開発者ツールが WASM フレームを Rust ソースにマッピングできるようにします:

wasm-pack build --target web -- --features console_error_panic_hook

lib.rs に、ブラウザコンソールに出力する panic フックをインストールします:

use console_error_panic_hook::set_once as set_panic_hook;

#[wasm_bindgen(start)]
pub fn init() {
set_panic_hook();
console_log::init_with_level(log::Level::Info).ok();
}

これらの依存関係を Cargo.toml に追加します:

[dependencies]
console_error_panic_hook = "0.1"
console_log = "1"
log = "0.4"

AI 駆動のエラー診断​

WASM モジュールが RuntimeError: unreachable をスローした場合、スタックトレースを Cursor のチャットに貼り付けます:

My WASM module crashes with "RuntimeError: unreachable executed"
when processing large arrays. Here is the Rust source and the JS
loader. Find the bug.

Cursor はオフバイワンエラー、整数オーバーフロー、アライメントされていないメモリアクセスなど、トラップの原因を特定します。

一般的なパターンとスニペット​

以下は Cursor が WASM プロジェクト用にオンデマンドで生成できる再利用可能なパターンです。

構造化データの受け渡し​

JavaScript と Rust の間で複雑なオブジェクトを渡すには、serde-wasm-bindgen を使用します:

use serde::{Serialize, Deserialize};
use serde_wasm_bindgen::{from_value, to_value};
use wasm_bindgen::prelude::*;

#[derive(Serialize, Deserialize)]
pub struct Point {
pub x: f64,
pub y: f64,
}

#[wasm_bindgen]
pub fn compute_centroid(points_js: JsValue) -> Result<JsValue, JsValue> {
let points: Vec<Point> = from_value(points_js)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
if points.is_empty() {
return Err(JsValue::from_str("Cannot compute centroid of empty set"));
}
let (sum_x, sum_y) = points.iter()
.fold((0.0, 0.0), |(sx, sy), p| (sx + p.x, sy + p.y));
let centroid = Point {
x: sum_x / points.len() as f64,
y: sum_y / points.len() as f64,
};
to_value(&centroid)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}

JavaScript からの呼び出しのスロットリング​

タイトなアニメーションループで呼び出される WASM 関数は、メインスレッドをブロックする可能性があります。Web Worker を使用して重い計算をオフロードします。Cursor は worker のスキャフォールディングを生成します:

// worker.js
import init, { process_image } from './pkg/rust_wasm_app.js';

self.onmessage = async (event) => {
await init();
const { pixels, width, height } = event.data;
const result = process_image(pixels, width, height);
self.postMessage({ result }, [result.buffer]);
};
// main.js
const worker = new Worker(new URL('./worker.js', import.meta.url), {
type: 'module',
});
worker.onmessage = (event) => {
drawProcessedPixels(event.data.result);
};
worker.postMessage({ pixels, width, height });
警告

転送リスト([result.buffer])で ArrayBuffer を転送することが、メインスレッドと Worker 間で大きなバッファをコピーせずに受け渡す唯一の方法です。数メガバイトの画像で転送リストを忘れると、メッセージごとに完全なコピーが発生し、WebAssembly を使用するパフォーマンス上の利点が破壊されます。

ベストプラクティスのまとめ​

Cursor で WebAssembly アプリケーションを構築する際、以下の原則に従ってください:

  • 適切な言語を選択する:パフォーマンスと安全性には Rust、TypeScript に似た学習曲線には AssemblyScript。
  • JS-WASM 境界を最小限にする:少数の大きな呼び出しが、多数の小さな呼び出しよりも優れています。
  • メモリを共有する 画像や音声などの大きなデータのバッファをコピーする代わりに。
  • リリースプロファイルでサイズ最適化を有効にし、ビルド後に wasm-opt を実行する。
  • 数ミリ秒以上かかる計算には Web Worker を使用し UI のブロックを避ける。
  • panic フックをインストールし ランタイムエラーが不透明なトラップではなく読みやすいメッセージとして表示されるようにする。

WebAssembly のパフォーマンスと Cursor の AI 駆動コード生成を組み合わせることで、通常のボイラープレートの一部で高性能なブラウザモジュールをリリースできます。意図を明確に記述し、Cursor にプロジェクトとグルーコードのスキャフォールディングを任せ、重要なアルゴリズムにエネルギーを集中させてください。