Integrasi WebAssembly dengan Claude Code: Rust, wasm-pack, dan Vite
Panduan integrasi Rust WebAssembly ke Vite dengan Claude Code, wrapper bertipe, benchmark, use case, dan pitfall umum.
Peran Claude Code dalam integrasi WebAssembly
WebAssembly, sering disingkat Wasm, adalah format biner portabel yang memungkinkan kode Rust, C, C++, dan bahasa lain berjalan di browser atau Node.js. Wasm bukan pengganti penuh JavaScript. Dalam proyek nyata, Wasm paling berguna sebagai akselerator untuk bagian kecil yang berat: pemrosesan gambar, kompresi, manipulasi byte untuk kriptografi, kalkulasi numerik, agregasi CSV, atau penggunaan ulang library Rust/C++ yang sudah ada.
Claude Code membantu karena integrasi Wasm yang baik tidak selesai di satu file. Kita perlu fungsi Rust, build dengan wasm-pack, glue code dari wasm-bindgen, loading async di Vite, wrapper TypeScript, benchmark, dan review prompt yang memeriksa biaya batas JS-Wasm. Batas JS-Wasm adalah titik saat data berpindah dari JavaScript ke WebAssembly atau sebaliknya. Jika batas ini dilintasi terlalu sering dengan panggilan kecil, hasilnya bisa lebih lambat daripada JavaScript biasa.
Artikel ini membuat contoh kecil yang bisa dicopy: membalik warna buffer RGBA, menjumlahkan kolom numerik dari CSV, dan menghitung checksum ringan pada bytes. Tiga contoh ini mewakili data gambar, teks, dan biner. Pola yang sama bisa diperluas untuk pemrosesan cepat di browser, porting aset Rust/C++, kompresi, codec khusus, atau kalkulasi lokal ketika data tidak boleh dikirim ke server. Untuk gambaran performa yang lebih luas, baca juga Claude Code performance optimization.
Gunakan referensi resmi saat bekerja: MDN WebAssembly untuk platform, wasm-bindgen Guide untuk jembatan Rust-JavaScript, dan wasm-pack repository untuk alur build. Minta Claude Code mengikuti batas ini sebelum membuat loader khusus.
Tentukan use case sebelum menulis kode
Wasm tidak otomatis lebih cepat. Wasm bagus ketika menerima blok data besar, memprosesnya dalam loop rapat, lalu mengembalikan hasil. Wasm kurang cocok jika JavaScript memanggil fungsi kecil ribuan kali. Jadi prompt pertama sebaiknya bukan “ubah ke Wasm”, tetapi “operasi mana yang pantas melewati batas JS-Wasm dan bagaimana cara mengukurnya”.
| Use case | Mengapa cocok untuk Wasm | Yang perlu dicek Claude Code |
|---|---|---|
| Pemrosesan gambar | Buffer RGBA cocok untuk loop linear | Jumlah copy, baca/tulis Canvas, benchmark adil |
| Kriptografi, kompresi, codec | Banyak bekerja dengan byte array dan library Rust | Apakah perlu library audited dan bagian mana yang tidak boleh custom |
| CSV dan kalkulasi numerik | Parsing dan agregasi mengulang banyak operasi | Baris kosong, NaN, file besar, strategi error |
| Porting Rust atau C++ | Logika yang sudah teruji bisa dipakai di browser | OS API, file I/O, thread, dependency yang tidak kompatibel |
| Pemrosesan berat di browser | Data sensitif tetap di perangkat pengguna | Ukuran load awal, fallback, target browser |
Dalam eksperimen Masa, pendekatan paling aman adalah memindahkan satu fungsi dulu lalu mengukur. Pada gambar, fungsi Rust cepat, tetapi waktu membaca dan menulis ImageData bisa mendominasi. Pada CSV, mengirim seluruh teks sekali lebih stabil daripada memanggil Wasm per baris. Constraint seperti ini harus masuk ke prompt Claude Code.
Modul Rust minimal dengan wasm-pack
wasm-pack membangun crate Rust, menjalankan wasm-bindgen, lalu menghasilkan folder pkg berisi binary Wasm, loader JavaScript, metadata package, dan deklarasi TypeScript. wasm-bindgen adalah library yang mengekspos fungsi Rust tertentu agar bisa dipanggil dari JavaScript.
# Cargo.toml
[package]
name = "wasm-lab"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn invert_rgba(pixels: &mut [u8]) {
for chunk in pixels.chunks_exact_mut(4) {
chunk[0] = 255 - chunk[0];
chunk[1] = 255 - chunk[1];
chunk[2] = 255 - chunk[2];
}
}
#[wasm_bindgen]
pub fn sum_csv_column(csv: &str, column: usize) -> f64 {
csv.lines()
.filter(|line| !line.trim().is_empty())
.filter_map(|line| line.split(',').nth(column))
.filter_map(|cell| cell.trim().parse::<f64>().ok())
.sum()
}
#[wasm_bindgen]
pub fn fnv1a32(bytes: &[u8]) -> u32 {
let mut hash = 0x811c9dc5u32;
for byte in bytes {
hash ^= u32::from(*byte);
hash = hash.wrapping_mul(0x01000193);
}
hash
}
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
wasm-pack build --target web --out-dir pkg
Fungsi fnv1a32 bukan hash kriptografi yang aman. Untuk password, signature, pembayaran, atau token, gunakan Web Crypto API atau library yang sudah diaudit. Di sini fungsinya hanya untuk contoh data biner yang kecil.
Loading di Vite dengan wrapper bertipe
Setelah build, akan ada pkg/wasm_lab.js dan pkg/wasm_lab.d.ts. Di Vite, import modul yang dihasilkan, tunggu init(), lalu expose API kecil lewat TypeScript wrapper. Dengan begitu UI tidak memanggil Wasm sebelum siap dan tidak menginisialisasi ulang pada setiap klik.
// src/wasm-client.ts
import init, {
fnv1a32,
invert_rgba,
sum_csv_column,
} from "../pkg/wasm_lab";
export type WasmClient = {
invertImage(imageData: ImageData): Promise<ImageData>;
sumCsvColumn(csv: string, columnIndex: number): Promise<number>;
checksum(bytes: Uint8Array): Promise<number>;
};
let initPromise: Promise<void> | undefined;
async function ensureWasm(): Promise<void> {
initPromise ??= init().then(() => undefined);
return initPromise;
}
export const wasmClient: WasmClient = {
async invertImage(imageData) {
await ensureWasm();
const pixels = new Uint8Array(
imageData.data.buffer,
imageData.data.byteOffset,
imageData.data.byteLength,
);
invert_rgba(pixels);
return imageData;
},
async sumCsvColumn(csv, columnIndex) {
await ensureWasm();
return sum_csv_column(csv, columnIndex);
},
async checksum(bytes) {
await ensureWasm();
return fnv1a32(bytes);
},
};
// src/main.ts
import { wasmClient } from "./wasm-client";
const fileInput = document.querySelector<HTMLInputElement>("#csv-file");
const output = document.querySelector<HTMLPreElement>("#output");
fileInput?.addEventListener("change", async () => {
const file = fileInput.files?.[0];
if (!file || !output) return;
const csv = await file.text();
const total = await wasmClient.sumCsvColumn(csv, 2);
output.textContent = `column 2 total: ${total.toFixed(2)}`;
});
Untuk alur wasm-pack --target web, mulai dari konfigurasi Vite standar. Plugin baru diperlukan jika kamu mengimpor .wasm mentah atau memakai pola bundling lain. Bug awal biasanya berasal dari path yang salah atau init() yang belum ditunggu.
Prompt review untuk Claude Code
Claude Code sebaiknya diminta mengimplementasikan lalu melakukan review kritis. Prompt review harus fokus pada async init, copy memori, batas JS-Wasm, DOM, tipe, dan perintah verifikasi.
Review only these files:
- src/lib.rs
- pkg/wasm_lab.d.ts
- src/wasm-client.ts
- src/main.ts
- src/bench.ts
Goal:
Integrate the Rust WebAssembly module into the Vite app without changing UI behavior.
Check:
1. init() is awaited before any exported Wasm function is called.
2. init() is cached and not repeated for every click or file upload.
3. Large arrays cross the JS-Wasm boundary at most once per user action.
4. DOM updates stay in TypeScript, not inside Rust.
5. The wrapper exposes typed methods and keeps generated pkg files out of hand edits.
6. Benchmarks compare the same input data for JavaScript and Wasm.
Run:
wasm-pack build --target web --out-dir pkg
npm run typecheck
npm run build
Untuk tim, simpan aturan ini di CLAUDE.md. Setiap perubahan Wasm akan melewati review yang sama, bukan bergantung pada ingatan satu orang.
Benchmark dan langkah verifikasi
Jangan menyetujui migrasi Wasm hanya karena terasa cepat. Ukur input yang sama dan output yang sama. Benchmark berikut membandingkan inversi RGBA di JavaScript dan Wasm.
// src/bench.ts
import { wasmClient } from "./wasm-client";
function invertJs(pixels: Uint8Array): void {
for (let index = 0; index < pixels.length; index += 4) {
pixels[index] = 255 - pixels[index];
pixels[index + 1] = 255 - pixels[index + 1];
pixels[index + 2] = 255 - pixels[index + 2];
}
}
function cloneImageData(source: Uint8Array, width: number, height: number): ImageData {
return new ImageData(new Uint8ClampedArray(source), width, height);
}
export async function runBench(): Promise<void> {
const width = 1920;
const height = 1080;
const source = new Uint8Array(width * height * 4);
crypto.getRandomValues(source);
const jsPixels = new Uint8Array(source);
const wasmImage = cloneImageData(source, width, height);
const jsStart = performance.now();
invertJs(jsPixels);
const jsMs = performance.now() - jsStart;
const wasmStart = performance.now();
await wasmClient.invertImage(wasmImage);
const wasmMs = performance.now() - wasmStart;
console.table({
javascriptMs: Number(jsMs.toFixed(2)),
wasmMs: Number(wasmMs.toFixed(2)),
ratio: Number((jsMs / wasmMs).toFixed(2)),
});
}
wasm-pack build --target web --out-dir pkg
npm run typecheck
npm run build
npm run dev
Jika hasil Wasm mengecewakan, cek konversi data lebih dulu. Canvas, ImageData, string, dan build development bisa menyembunyikan biaya sebenarnya. Kirim output benchmark ke Claude Code dan minta rekomendasi: tetap memakai Wasm, pindah ke Web Worker, atau cukup optimasi JavaScript.
Pitfall yang sering muncul
Pitfall pertama adalah inisialisasi async. init() harus selesai sebelum fungsi export dipanggil. Cache promise ini di wrapper.
Pitfall kedua adalah ukuran bundle. Setiap crate Rust bisa memperbesar .wasm. Mulai dari satu fungsi dan cek production build.
Pitfall ketiga adalah biaya batas JS-Wasm. Jangan memanggil fungsi kecil di dalam loop. Kirim array, string, atau buffer besar sekali jalan.
Pitfall keempat adalah mencoba mengontrol DOM dari Wasm. Event, render, accessibility, dan error message sebaiknya tetap di TypeScript.
Pitfall kelima adalah copy memori yang tidak terlihat. Typed array, string, dan ImageData bisa disalin oleh binding. Benchmark harus memasukkan biaya konversi ini.
Pitfall keenam adalah kompatibilitas browser dan security header. Wasm dasar sudah luas dukungannya, tetapi Wasm threads dan SharedArrayBuffer butuh COOP dan COEP. Situs dengan iklan, iframe, atau CDN perlu menguji hal ini lebih awal.
Workflow tim dan CTA
Untuk eksperimen pribadi, kode ini cukup. Untuk tim, tentukan logika mana yang pindah ke Rust, apa yang tetap di TypeScript, bagaimana memperlakukan file generated di pkg, browser apa yang didukung, dan benchmark mana yang memblokir merge. Aturan ini sebaiknya masuk ke CLAUDE.md dan prompt review.
ClaudeCodeLab dapat membantu menerapkan alur ini ke repository nyata: memilih use case Wasm yang tepat, mereview aset Rust/C++, mendesain benchmark, dan melatih tim memakai Claude Code dengan aman. Jika WebAssembly memengaruhi performa produksi, pemrosesan data sensitif di browser, atau arsitektur frontend bersama, mulai dari Claude Code training and consultation.
Catatan verifikasi
Saat mencoba alur ini, masalah awal bukan pada fungsi Rust, tetapi pada kapan init() dipanggil. Setelah inisialisasi disimpan di wasm-client.ts, image processing, CSV aggregation, dan checksum berjalan lewat jalur yang sama. Input kecil cukup cepat di JavaScript; buffer Full HD dan CSV besar lebih jelas menunjukkan trade-off. Ukur seluruh batas JS-Wasm, bukan hanya isi fungsi.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission receipt Claude Code: mencatat scope, bukti, dan rollback
Pola permission receipt untuk Claude Code: aksi yang diizinkan, batas approval, command verifikasi, rollback, dan cek CTA revenue.
Agent Harness Aman untuk Claude Code dan Codex: Permission, Verifikasi, dan Rollback
Rancang Agent Harness praktis untuk Claude Code dan Codex dengan policy, plan, verification, dan recovery layer.
Subagent Claude Code: panduan praktis untuk delegasi artikel dan kode
Panduan subagent Claude Code untuk membagi pekerjaan artikel dan kode: aturan delegasi, prompt, risiko, dan checklist.