Praktik pertama: menginstall toolchain WAT (wasm-tools dan wabt), menulis modul .wat sederhana dengan fungsi export, mengkompilasinya menjadi .wasm, lalu menjalankannya di Node.js dan browser console

Setelah di episode 2 kita memahami arsitektur WebAssembly — stack-based VM, format .wat/.wasm, dan linear memory — sekarang saatnya praktik pertama: menulis Hello World sungguhan. Di episode ini kalian akan merasakan langsung alur kerja yang akan dipakai sepanjang series: tulis .wat → kompilasi .wasm → jalankan di host.
Mengapa Hello World penting? Karena alur "tulis, kompilasi, jalankan" inilah yang menjadi siklus dasar pengembangan Wasm. Setelah kalian bisa memproduksi modul pertama yang benar-benar berjalan, semua episode berikutnya tinggal memperluas: menambah instruksi, memory, interop, hingga toolchain dari bahasa tingkat tinggi.
Sebelum menulis kode, pastikan dua tool tersedia. Di episode 0 kalian sudah menginstall wasm-tools; kini lengkapi dengan wabt (WebAssembly Binary Toolkit) yang berisi wat2wasm dan wasm2wat.
cargo install wasm-tools
cargo install wabt
wasm-tools --version
wat2wasm --versionKeduanya menjalankan fungsi yang sama — mengkonversi .wat menjadi .wasm dan sebaliknya. Sepanjang series kita akan memakai wasm-tools (lebih modern), tapi wat2wasm berguna sebagai alternatif.
Tip
Ingin mencoba tanpa menginstall apa pun? Browser dan Node.js sudah punya fungsi WebAssembly.compile yang bisa membaca bytecode .wasm langsung dari Uint8Array. Kita memakai fitur ini di episode 6.
Buat file bernama hello.wat. Ini modul paling sederhana yang valid: mendeklarasikan satu fungsi add yang menerima dua parameter i32 dan mengembalikan hasil penjumlahannya.
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
(export "add" (func $add))
)Baca baris demi baris:
(module ...) — setiap file WAT adalah satu modul.(func $add ...) — mendefinisikan fungsi bernama $add.(param $a i32) — parameter a bertipe i32; ada dua di antaranya.(result i32) — fungsi ini mengembalikan i32.local.get $a — dorong nilai $a ke stack.i32.add — ambil dua nilai teratas stack, jumlahkan, dorong hasilnya kembali.(export "add" (func $add)) — mempublikasikan fungsi $add ke host dengan nama "add".Perhatikan bagaimana tiga instruksi local.get $a, local.get $b, i32.add merepresentasikan operasi a + b dalam model stack yang kita bahas di episode 2.
Sekarang konversi WAT menjadi bytecode binary:
wasm-tools parse hello.wat -o hello.wasm
wasm-tools validate hello.wasm
ls -l hello.wasmwasm-tools parse mengubah teks menjadi binary, dan wasm-tools validate memastikan modul lolos verifikasi keamanan (tipe cocok, instruksi valid). Ukuran file .wasm biasanya hanya puluhan byte — bandingkan dengan file binary native yang mencapai kilobyte.
Mari lihat kebalikannya: konversi .wasm kembali ke teks untuk membuktikan tidak ada data yang hilang:
wasm-tools print hello.wasmOutputnya akan menampilkan WAT yang setara dengan hello.wat — bukti bahwa kedua format adalah representasi dari modul yang sama.
Sekarang saatnya memanggil fungsi add dari JavaScript. Baca bytecode .wasm dan instansiasi lewat API WebAssembly:
import { readFile } from "node:fs/promises"
const bytes = await readFile(new URL("./hello.wasm", import.meta.url))
const { instance } = await WebAssembly.instantiate(bytes, {})
const hasil = instance.exports.add(40, 2)
console.log("40 + 2 =", hasil)Jalankan:
node hello.mjsJika semuanya benar, outputnya:
40 + 2 = 42Selamat — kalian baru saja mengeksekusi bytecode WebAssembly native dari Node.js! Fungsi add dijalankan sepenuhnya oleh mesin Wasm, bukan JavaScript.
Alur yang sama bisa dilakukan di browser mana pun. Buka DevTools (F12), pindah ke tab Console, dan jalankan:
const bytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// ...bytecode lainnya dari hello.wasm
])
const { instance } = await WebAssembly.instantiate(bytes)
console.log(instance.exports.add(2, 5))Untuk memuat file .wasm asli, gunakan instantiateStreaming dengan fetch (cara ini dipakai di episode 6). Yang penting untuk episode ini: browser dan Node.js mengeksekusi modul Wasm yang sama tanpa perubahan — itulah portabilitas yang kita bicarakan di episode 1.
Beberapa kesalahan yang umum terjadi di tahap awal:
local.get — fungsi yang memakai parameter tanpa local.get akan menghasilkan error validasi karena stack kosong.i32.add dengan input f32 gagal validasi; Wasm bersifat statically typed dan ketat.(export "add" ...) sama persis dengan yang dipanggil di JS (instance.exports.add)..wasm lewat fetch.Warning
Kesalahan validasi di Wasm terjadi sebelum eksekusi — ini fitur keamanan, bukan bug. Engine menolak modul yang tidak lolos verifikasi, jadi kalian tidak akan pernah menjalankan bytecode yang berbahaya secara tidak sengaja.
Pada episode 3 ini, kalian telah menyelesaikan siklus lengkap pertama: menulis .wat, mengkompilasi dengan wasm-tools, memvalidasi, dan mengeksekusi di Node.js serta browser.
Inti yang harus dibawa pulang:
wasm-tools (parse/validate/print) dan wabt (wat2wasm)..wat terdiri dari (module), (func), dan (export); instruksi berjalan di atas stack.wasm-tools parse → validate → panggil instance.exports dari JS.Di episode 4 selanjutnya kita akan mendalami bahasa teks WAT dan sintaksnya — struktur (module), (func), (export), (import), tipe (param)/(result), serta instruksi local.get/set, i32.const, aritmetika stack, call, dan deklarasi (memory). Sampai jumpa di episode 4!