Menggunakan WebAssembly untuk pemrosesan data dan binary: codec, image/video transcoding, parse dan serialize data dengan serde, plus SIMD, AOT/JIT tier-up, dan caching WebAssembly.Module dengan compileStreaming

Setelah di episode 12 kita melihat Wasm berperan di edge computing — pada episode ini kita kembali ke salah satu kasus penggunaannya yang paling nyata dan menguntungkan: pemrosesan data dan binary. Compress gambar, transcoding video, parsing format binary — semua pekerjaan yang membebani JavaScript kini bisa dijalankan secepat native di browser.
Mengapa Wasm unggul di sini? Karena workload ini bersifat komputasi intensif dan deterministic — sangat cocok untuk bytecode yang dikompilasi ke kode mesin. Satu modul codec yang ditulis dalam C bisa berjalan di browser, server, dan edge tanpa perubahan, dengan performa yang mendekati native. Ini bukan sekadar teori: seluruh industri (Figma, Google, 1Password) sudah membangun fitur di atas pola ini.
Contoh paling klasik adalah zlib — library kompresi C yang berumur puluhan tahun, dikompilasi ke Wasm:
emcc -O2 -s EXPORTED_FUNCTIONS="[_compress2, _uncompress]" \
-s ALLOW_MEMORY_GROWTH=1 zlib-*.c -o zlib.jsHasilnya: modul zlib.wasm yang bisa dikompresi data secepat library native, tanpa server. Untuk file teks, JSON, atau HTML besar yang dikirim ke client, kompresi di sisi client bisa menghemat bandwidth secara signifikan.
import { compress2, uncompress } from "./zlib.js"
const teks = "data berulang ".repeat(1000)
const compressed = compress2(new TextEncoder().encode(teks))
console.log("ukuran asli:", teks.length, "->", compressed.length)
const kembali = uncompress(compressed)
console.log(new TextDecoder().decode(kembali))Alur transcoding gambar biasanya: user memilih file → browser memprosesnya → hasil dikirim. Dengan Wasm, pemrosesan dilakukan lokal — cepat dan privasi terjaga karena data tidak meninggalkan perangkat.
Pola umum dengan canvas + memory sharing:
const { instance } = await WebAssembly.instantiateStreaming(
fetch("resize.wasm"),
)
async function proses(file) {
const bitmap = await createImageBitmap(file)
// tulis pixel ke linear memory wasm
const memory = instance.exports.memory
const buf = new Uint8Array(memory.buffer)
const pixelData = await getPixelData(bitmap) // ImageData
buf.set(pixelData, 0)
// panggil resize di wasm (gambar keluar juga di memory)
instance.exports.resize(0, bitmap.width, bitmap.height, 512, 512)
// baca hasil & render ke canvas
const hasil = new Uint8ClampedArray(memory.buffer, 0, 512 * 512 * 4)
const canvas = document.getElementById("out")
canvas.width = 512
canvas.height = 512
canvas.getContext("2d").putImageData(
new ImageData(hasil, 512, 512), 0, 0,
)
}Inilah pola inti: pixel data masuk via memory linear, diproses di Wasm, hasil dibaca kembali — tidak ada serialisasi JSON yang boros.
Di dunia Rust, serde (serialization framework) bekerja mulus dengan wasm-bindgen. Kalian bisa parse format binary besar — parquet, protobuf, atau format custom — langsung di Wasm:
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[derive(Serialize, Deserialize)]
struct Transaksi {
id: u64,
nominal: f64,
tag: String,
}
#[wasm_bindgen]
pub fn parse_transaksi(data: &[u8]) -> Result<JsValue, JsValue> {
let t: Transaksi = serde_json::from_slice(data)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(serde_wasm_bindgen::to_value(&t)
.map_err(|e| JsValue::from_str(&e.to_string()))?)
}serde_json::from_slice mem-parse 1 MB JSON jauh lebih cepat di Wasm daripada JSON.parse di JS untuk kasus skema kompleks. Untuk format binary (bincode, postcard), kecepatannya bahkan lebih tinggi — dan ukuran datanya lebih kecil.
Tip
Untuk parsing binary dengan volume sangat tinggi, pertimbangkan zero-copy: wasm-bindgen bisa bekerja langsung di atas Uint8Array tanpa menyalin, menggunakan pola boxed slice — mengurangi garbage collection dan mempercepat throughput 2-3x.
SIMD (dari episode 9) adalah senjata utama data processing. Contoh nyata: memfilter array besar berdasarkan nilai:
#[wasm_bindgen]
pub fn threshold(data: &[f32], batas: f32) -> Vec<f32> {
data.iter()
.filter(|&&x| x > batas)
.copied()
.collect()
}Compiler Rust dengan -O3 otomatis menerapkan SIMD pada loop seperti ini ketika fitur simd128 aktif — hasilnya percepatan signifikan untuk sinyal, audio, atau analitik real-time di browser.
Engine browser mengkompilasi Wasm dalam dua tahap:
Konsekuensinya: jangan mengukur performa dari run pertama — beri waktu engine untuk "memanaskan" fungsi hot-path.
Ini strategi paling praktis untuk aplikasi nyata. WebAssembly.Module bisa di-cache dan di-instansiasi ulang dengan sangat murah (instance bisa dibuat ratusan kali per detik):
async function muatModul(url) {
const cache = await caches.open("wasm-cache")
let modul = await cache.match(url).then((r) => r?.arrayBuffer())
if (modul) {
return await WebAssembly.Module.instantiate(modul) // dari cache
}
const response = await fetch(url)
const modulBaru = await WebAssembly.compileStreaming(response)
await cache.put(url, new Response(
await WebAssembly.Module.export(modulBaru) // ... serialisasi
))
return modulBaru
}Pola yang disederhanakan — intinya: kompilasi sekali, reuse berkali-kali. Kombinasi compileStreaming (kompilasi sambil download) + caching menghindari biaya kompilasi ulang pada kunjungan berikutnya.
Warning
Jangan pernah mem-parse data untrusted dengan parser yang belum diuji terhadap input korup — modul Wasm bisa trap atau menghabiskan memory. Selalu batasi ukuran input dan lakukan fuzz testing (kita bahas di episode 14).
Pada episode 13 ini, kalian telah memahami cara memakai WebAssembly untuk data & binary processing yang efisien.
Inti yang harus dibawa pulang:
JSON.parse untuk data besar.compileStreaming + caching WebAssembly.Module menghilangkan biaya kompilasi berulang.Di episode 14 selanjutnya kita akan membahas testing & debugging — cargo test untuk target wasm, wasm-bindgen-test, browser testing dengan Playwright, plus wasm-tools print/dump, DWARF debugging, dan section name. Sampai jumpa di episode 14!