Component Model adalah standar yang membungkus modul Wasm jadi komponen: modul yang menulis dengan jelas apa yang ia butuhkan dan apa yang ia berikan, dengan tipe data kaya seperti list<u8>, string, record, dan result, bukan cuma angka. Bayangin Bu Sri memesan jasa olah foto dari tukang yang berbeda-beda: satu dari Wonosobo, satu dari Jogja, satu dari Bandung. Dulu tiap tukang punya cara sendiri menerima pesanan: ada yang minta nomor rak dan jumlah foto, ada yang minta fotonya ditaruh di kotak tertentu. Tiap pasangan butuh kesepakatan khusus. Sekarang ada formulir pesanan standar yang menulis apa yang diserahkan dan apa yang kembali, plus aturan baku cara mengemas barangnya. Tukang mana pun yang bisa membaca formulir itu langsung bisa bekerja. Formulirnya adalah WIT, aturan mengemasnya canonical ABI, dan tukang-tukangnya komponen yang ditulis di Rust, Go, C, atau JavaScript.
🧪 Tebakan cepat: di WIT tertulis
grayscale: func(piksel: list<u8>) -> list<u8>. Fungsi core Wasm di dalam komponennya menerima apa: daftar byte utuh, dua angkai32, atau satu objek? Tebak dulu, jawabannya ada di bagian "Canonical ABI: dari list ke angka".
Core module dan komponen
Semua .wasm di pelajaran sebelumnya adalah core module. Di perbatasannya cuma angka yang lewat: i32, i64, f32, f64, plus referensi. Teks dan foto harus ditulis ke linear memory lalu dikirim sebagai pointer dan panjang, dan tiap pasangan bahasa butuh glue code sendiri (wasm-bindgen untuk Rust dan JS, syscall/js untuk Go). Kalau modul Rust mau dipakai modul Go, kedua sisi harus sepakat manual soal tata letak memorinya. Ide untuk menyelesaikan ini dulu bernama interface types; sekarang jadi bagian dari Component Model.
| Core module | Komponen | |
|---|---|---|
| Yang lewat perbatasan | i32, i64, f32, f64, referensi |
juga string, list, record, variant, option, result, resource |
| Memori | bisa di-export dan dibagi | tersembunyi di dalam komponen, gak dibagi |
| Kontrak | daftar fungsi dengan tipe angka | WIT: interface dan world |
| Byte ke-5 sampai ke-8 | 01 00 00 00 |
0d 00 01 00 |
Komponen adalah bungkus: di dalamnya ada satu atau lebih core module, ditambah tipe yang ditulis di WIT dan instruksi untuk mengubah tipe kaya jadi angka. Browser dan Node cuma mengenal core module. Chromium 147 menolak komponen dengan CompileError: WebAssembly.compile(): expected version 01 00 00 00, found 0d 00 01 00 @+4, WebKit 26.4 dengan CompileError: WebAssembly.Module doesn't parse at byte 0: unexpected version number 65549 expected 1.
WIT: kontrak tool foto Bu Sri
WIT (Wasm Interface Type) adalah bahasa untuk menulis kontraknya. Ini wit/olahfoto.wit:
package homestay:[email protected];
/// Foto kamar Homestay Kabut: RGBA, 4 byte per piksel.
interface foto {
record ukuran {
lebar: u32,
tinggi: u32,
}
/// abu = (r × 77 + g × 150 + b × 29) >> 8, alfa tetap.
grayscale: func(piksel: list<u8>) -> list<u8>;
/// Thumbnail rata-rata area. Gagal kalau jumlah byte gak cocok dengan ukurannya.
thumbnail: func(piksel: list<u8>, asal: ukuran, tujuan: ukuran) -> result<list<u8>, string>;
}
world olahfoto {
export foto;
}
package homestay:[email protected]memberi nama dan versi, formatnyanamespace:nama@versi.interface fotomengelompokkan fungsi dan tipe.record ukuranmirip objek dengan field tetap.list<u8>adalah deretan byte.result<list<u8>, string>artinya: berhasil dengan byte, atau gagal dengan pesan teks.world olahfotomenulis apa yang komponen ini export dan import. Yang ini cuma meng-exportfotodan gak butuh apa-apa dari luar.- Nama di WIT memakai kebab-case (
harga-inap). Generator mengubahnya ke gaya tiap bahasa:harga_inapdi Rust,hargaInapdi JS. - Tipe lain yang ada:
bool,s8sampais64,u8sampaiu64,f32,f64,char,string,option<T>,tuple,variant,enum,flags, danresource.
Build komponennya dari Rust
Proyeknya crate Rust biasa (olahfoto-komponen) dengan crate-type = ["cdylib"] dan satu dependency, wit-bindgen = "0.62". File WIT-nya di folder wit/.
wit_bindgen::generate!({ world: "olahfoto", path: "wit" });
use exports::homestay::kabut::foto::{Guest, Ukuran};
struct Olahfoto;
impl Guest for Olahfoto {
fn grayscale(mut piksel: Vec<u8>) -> Vec<u8> {
for p in piksel.chunks_exact_mut(4) {
let abu = ((p[0] as u32 * 77 + p[1] as u32 * 150 + p[2] as u32 * 29) >> 8) as u8;
(p[0], p[1], p[2]) = (abu, abu, abu);
}
piksel
}
fn thumbnail(piksel: Vec<u8>, asal: Ukuran, tujuan: Ukuran) -> Result<Vec<u8>, String> {
let perlu = (asal.lebar * asal.tinggi * 4) as usize;
if piksel.len() != perlu {
return Err(format!("piksel harus {} byte, dapat {}", perlu, piksel.len()));
}
if tujuan.lebar == 0 || tujuan.tinggi == 0 || tujuan.lebar > asal.lebar || tujuan.tinggi > asal.tinggi {
return Err(format!("ukuran tujuan {} × {} gak bisa dari {} × {}", tujuan.lebar, tujuan.tinggi, asal.lebar, asal.tinggi));
}
Ok(rata_rata_area(&piksel, asal, tujuan))
}
}
export!(Olahfoto);
generate!membaca WIT dan membuat traitGuest. Kamu cukup mengisi fungsinya dengan tipe Rust biasa:list<u8>jadiVec<u8>,stringjadiString,resultjadiResult.export!menyambungkan implementasimu ke export komponen.rata_rata_areaberisi loop yang sama persis denganresize_areadi pelajaran Image Processing, jadi gak ditulis ulang di sini.
$ cargo build --release --target wasm32-wasip2
$ wasm-tools component wit target/wasm32-wasip2/release/olahfoto_komponen.wasm
world root {
import wasi:io/[email protected];
import wasi:cli/[email protected];
... (14 import wasi, dipotong)
export homestay:kabut/[email protected];
}
- Target
wasm32-wasip2(rustc 1.99.0) langsung menghasilkan komponen: 50.511 byte. Import WASI-nya datang dari std Rust, yang siap menulis ke stderr dan keluar dari program. - Tanpa WASI:
cargo build --target wasm32-unknown-unknownlaluwasm-tools component new olahfoto_komponen.wasm -o olahfoto.wasm(wasm-tools 1.261.0). Hasilnya 21.924 byte dan WIT-nya cumaexport homestay:kabut/[email protected]. Versino_stdyang dipakai Lab: 8.374 byte. cargo componentdulu jadi cara yang direkomendasikan. Buku Component Model sekarang menulis tool itu sedang ditinggalkan, karenacargo builddengan targetwasm32-wasip2dan wit-bindgen sudah cukup.
Canonical ABI: dari list ke angka
Di dalam komponen, fungsinya tetap fungsi core Wasm yang cuma kenal angka. Canonical ABI adalah aturan baku untuk "menurunkan" (lower) nilai kaya jadi angka dan byte di linear memory, lalu "mengangkat" (lift) hasilnya balik: list dan string jadi pointer plus panjang, record dipecah jadi field-field-nya, result jadi satu byte penanda plus isinya. Karena aturannya sama untuk semua bahasa, Rust dan Go bisa saling memanggil tanpa sepakat apa pun lagi. Ini export core module di dalam olahfoto.wasm:
memory
homestay:kabut/[email protected]#grayscale (i32, i32) -> i32
homestay:kabut/[email protected]#thumbnail (i32, i32, i32, i32, i32, i32) -> i32
cabi_realloc (i32, i32, i32, i32) -> i32
cabi_post_homestay:kabut/[email protected]#grayscale (i32)
Jadi jawaban tebakannya: dua angka i32, pointer dan panjang, dan hasilnya satu i32: alamat "return area" yang berisi pointer dan panjang hasilnya. Ini urutan satu panggilan grayscale([200, 120, 40, 255]), angkanya dari Lab:
| Langkah | Yang terjadi |
|---|---|
| 1 | cabi_realloc(0, 0, 1, 4) memberi 1114112: tempat 4 byte di memori komponen |
| 2 | glue menyalin 4 byte ke alamat 1114112 |
| 3 | homestay:kabut/[email protected]#grayscale(1114112, 4) mengembalikan 1048912 |
| 4 | baca 8 byte di 1048912: ptr 1114112, len 4 |
| 5 | salin 4 byte keluar: [135, 135, 135, 255] |
| 6 | cabi_post_...#grayscale(1048912): komponen melepas memori hasilnya |
record ukurangak lewat memori: dipecah jadi duau32. Itu sebabnyathumbnailpunya 6 parameteri32. Kalau parameter yang sudah dipecah lebih dari 16, semuanya dikirim lewat memori.result<list<u8>, string>di return area: byte pertama 0 (ok) atau 1 (err), lalu pointer dan panjang di byte ke-4 dan ke-8.wasm-tools print olahfoto.wasmmenunjukkan pengikatnya:(canon lift (core func ...#grayscale) (memory $memory) (realloc $cabi_realloc) (post-return ...)).- Tiap komponen punya memorinya sendiri, jadi datanya selalu disalin: 3.840.000 byte foto masuk dan 3.840.000 byte keluar di tiap panggilan.
Menjalankan: Wasmtime dan jco
$ wasmtime run --invoke 'grayscale([200, 120, 40, 255])' olahfoto.wasm
[135, 135, 135, 255]
$ wasmtime run --invoke 'thumbnail([1, 2, 3, 4], {lebar: 1200, tinggi: 800}, {lebar: 400, tinggi: 267})' olahfoto.wasm
err("piksel harus 3840000 byte, dapat 4")
Wasmtime 49.0.2 menjalankan komponen langsung, tanpa JavaScript. Di browser dan Node, pakai jco (1.37.0) dari Bytecode Alliance. jco transpile olahfoto.wasm -o olahfoto mengubah komponen versi no_std tadi jadi core module biasa (olahfoto.core.wasm, 7.139 byte) plus ES module JavaScript (olahfoto.js, 98,2 KiB sebelum minify) yang berisi canonical ABI di atas:
import { foto } from './olahfoto/olahfoto.js';
foto.grayscale(new Uint8Array([200, 120, 40, 255])); // Uint8Array [135, 135, 135, 255]
try {
foto.thumbnail(new Uint8Array(16), { lebar: 1200, tinggi: 800 }, { lebar: 400, tinggi: 267 });
} catch (e) {
console.log(e.message); // piksel harus 3840000 byte, dapat 16
}
resultyang gagal dilempar sebagaiComponentError, isinya die.payload.- Hasil transpile ini jalan di Node 22, Chromium 147, dan WebKit 26.4. Gak ada browser yang menjalankan komponen langsung; jco yang menjembatani.
- Kalau komponennya meng-import WASI, jco mengarahkannya ke
@bytecodealliance/preview2-shim(0.28.0). Di browser, shim itu menyediakan jam, angka acak, stdout ke console, dan HTTP keluar lewatfetch. File, stdin, dan socket butuh adapter dari aplikasimu.
📌 WASI 0.2, 0.3, dan dukungan runtime
- WASI 0.2 dan 0.3 dibangun dari komponen.
foto-abudari pelajaran WASI, di-build untukwasm32-wasip2, meng-import 17 interface (wasi:io,wasi:clocks,wasi:cli,wasi:filesystem, versi 0.2.12) dan meng-exportwasi:cli/[email protected]. - World penting:
wasi:cli/commanduntuk program CLI,wasi:http/proxydi WASI 0.2 danwasi:http/service(plusmiddleware) di WASI 0.3 untuk server HTTP. - Di daftar proposal W3C WebAssembly CG, Component Model masih Phase 1, tapi rilis WASI-nya (0.2.0, 0.3.0, 0.3.1) dibuat stabil untuk dipakai di luar browser.
- Wasmtime mendukung penuh. Spin (sekarang di bawah Akamai) dan wasmCloud membangun platformnya di atas komponen. WasmEdge baru sampai loader dan validator (0.17.0). WAMR belum. Browser dan Node: lewat jco.
⚠️ Jebakan yang sering ketemu
- Memuat komponen dengan
WebAssembly.compile. Ditolak di byte ke-4:expected version 01 00 00 00, found 0d 00 01 00. Transpile dulu dengan jco. - Berharap memori dibagi. Komponen gak berbagi memori; tiap panggilan menyalin datanya. Untuk foto besar, ongkos itu nyata.
- Mengikuti tutorial lama.
cargo componentsedang ditinggalkan, dan API wit-bindgen berubah antar versi (0.25 lawan 0.62). Cek versi diCargo.toml. - Salah nama. WIT
harga-inap, Rustharga_inap, JShargaInap. Nama yang kamu ketik di--invokemengikuti WIT.
TL;DR: Component Model membungkus core module jadi komponen dengan kontrak WIT: package, interface, world, dan tipe kaya (list, string, record, result, option, variant, resource), sehingga modul dari bahasa berbeda bisa saling memanggil tanpa menyusun memori secara manual. Canonical ABI adalah aturan bakunya: list<u8> jadi dua i32 (pointer dan panjang) yang disalin lewat cabi_realloc, hasilnya dibaca dari return area, lalu dilepas dengan cabi_post_*; record dipecah jadi field-nya. Dari Rust: wit-bindgen 0.62 dengan cargo build --target wasm32-wasip2 (50.511 byte, ikut import WASI), atau wasm32-unknown-unknown plus wasm-tools component new (21.924 byte). Periksa dengan wasm-tools component wit. Wasmtime menjalankannya langsung (--invoke); browser dan Node gak mengenal komponen (expected version 01 00 00 00), jadi jco 1.37.0 mengubahnya jadi core module plus JS. WASI 0.2 dan 0.3 dibangun di atas komponen: wasi:cli/command, wasi:http/proxy, wasi:http/service.