Wasm di Edge (Cloudflare Workers) — WebAssembly

Wasm di Edge: Cloudflare Workers Cloudflare Workers memungkinkan kamu menjalankan Wasm di 300+ edge locations worldwide. Kombinasi Wasm + edge = cold start…

Edge artinya kode kamu jalan di server-server kecil yang tersebar di banyak kota, dekat dengan pengunjung, bukan di satu server pusat. Cloudflare Workers menjalankan JavaScript, dan Wasm yang di-import JavaScript itu, di data center Cloudflare memakai isolate V8, mesin yang sama dengan Chrome. Bayangin kalkulator harga Bu Sri dititipkan di loket agen travel di tiap kota, jadi tamu dari Medan atau Makassar gak perlu menelepon Dieng untuk tahu harganya. Tiap agen punya bilik kecil di gedung yang sama dengan agen lain, dan aturan gedungnya ketat: alatnya harus dibawa jadi dari rumah, gak boleh dirakit di loket, dan tiap tamu cuma boleh dilayani sekian menit. Loketnya data center edge, biliknya isolate, alat jadi itu modul Wasm yang di-compile saat deploy, dan menit pelayanan itu batas CPU time.

🧪 Tebakan cepat: di Playground, kita selalu memasang Wasm dengan WebAssembly.instantiate(bytes) dari Uint8Array. Kalau baris yang sama ditaruh di dalam Worker, jalan atau gak? Tebak dulu, jawabannya ada di bagian "Modul Wasm di Worker: di-import, bukan di-compile".

Edge dan isolate

Batas paket menurut halaman limits Cloudflare (diperbarui 8 Oktober 2026, dibaca 11 Oktober 2026):

Batas Workers Free Workers Paid
CPU time per request 10 ms 30 detik bawaan, bisa dinaikkan sampai 5 menit
Memori per isolate 128 MB 128 MB
Ukuran Worker 64 MiB 64 MiB
Startup (menjalankan global scope) 1 detik 1 detik
Subrequest per request 50 10.000

Ukuran Worker dihitung dari bundle yang belum dikompres; batas lama berbasis ukuran terkompresi sudah gak berlaku. CPU time adalah waktu prosesor yang benar-benar dipakai, bukan waktu menunggu fetch.

Modul Wasm di Worker: di-import, bukan di-compile

Workers melarang membuat kode dari data saat runtime: eval, new Function, WebAssembly.compile, WebAssembly.compileStreaming, WebAssembly.instantiateStreaming, dan WebAssembly.instantiate dengan buffer. Modul Wasm harus ikut di bundle, dan wrangler meng-compile-nya saat deploy:

import modul from './kabut-edge.wasm'; // wrangler memberi WebAssembly.Module yang sudah di-compile
const wasm = new WebAssembly.Instance(modul, {}).exports; // sekali per isolate, dipakai ulang tiap request

Ini Worker yang mencoba cara Playground, dijalankan dengan wrangler dev (wrangler 4.149.0) di laptop:

$ curl -i "http://127.0.0.1:8797/?malam=2"
HTTP/1.1 500 Internal Server Error
Content-Length: 96
Content-Type: application/json

{"error":"CompileError: WebAssembly.instantiate(): Wasm code generation disallowed by embedder"}

Jadi jawaban tebakannya: gak jalan. Error yang sama keluar untuk WebAssembly.compile(bytes) dan new WebAssembly.Module(bytes), dan new Function gagal dengan EvalError: Code generation from strings disallowed for this context. new WebAssembly.Instance(modul) dan WebAssembly.instantiate(modul) dari modul yang di-import tetap boleh.

Endpoint harga dan thumbnail

kabut-edge.wasm adalah crate Rust no_std kecil, 942 byte: hargaInap dari pelajaran wasm-pack, dan thumbnail dengan rata-rata area dari pelajaran image processing.

/// Total menginap: malam × harga per malam.
#[unsafe(export_name = "hargaInap")]
pub extern "C" fn harga_inap(malam: i32, harga_per_malam: i32) -> i32 {
    malam * harga_per_malam
}
// thumbnail(src, sw, sh, dst, dw, dh): sama dengan resize_area di olahfoto.wasm

Inti Worker-nya (src/index.js), dengan wrangler.jsonc berisi "main": "src/index.js" dan "compatibility_date": "2026-10-01":

const worker = {
  async fetch(request) {
    const url = new URL(request.url);
    if (url.pathname === '/harga') {
      const kamar = KAMAR[url.searchParams.get('kamar')];
      const malam = Number(url.searchParams.get('malam'));
      if (!kamar) return Response.json({ error: 'kamar tidak dikenal' }, { status: 404 });
      if (!Number.isInteger(malam) || malam < 1 || malam > 30) return Response.json({ error: 'malam harus 1 sampai 30' }, { status: 400 });
      return Response.json({ kamar: kamar[0], malam, hargaPerMalam: kamar[1], total: wasm.hargaInap(malam, kamar[1]) });
    }
    if (url.pathname === '/thumbnail') {
      const w = Number(url.searchParams.get('w') || 200);
      if (![100, 200, 400].includes(w)) return Response.json({ error: 'w harus 100, 200, atau 400' }, { status: 400 });
      const h = Math.round((w * H) / W);
      if (!fotoSiap) {
        wasm.memory.grow(Math.ceil((DST + 400 * 267 * 4 - SRC) / 65536)); // cukup untuk thumbnail terbesar
        new Uint8Array(wasm.memory.buffer).set(fotoKamarTeras(), SRC);
        fotoSiap = true;
      }
      wasm.thumbnail(SRC, W, H, DST, w, h);
      const body = await png(new Uint8Array(wasm.memory.buffer, DST, w * h * 4), w, h);
      return new Response(body, { headers: { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=86400' } });
    }
    return Response.json({ error: 'gak ada' }, { status: 404 });
  },
};

export default worker;

Hasil aslinya dari wrangler dev:

$ curl -i "http://127.0.0.1:8799/harga?kamar=teras&malam=2"
HTTP/1.1 200 OK
Content-Length: 71
Content-Type: application/json

{"kamar":"Kamar Teras","malam":2,"hargaPerMalam":350000,"total":700000}
$ curl -s -o t200.png -w "w=200 %{http_code} %{content_type} %{size_download} bytes %{time_total}s\n" "http://127.0.0.1:8799/thumbnail?w=200"
w=200 200 image/png 2510 bytes 0.050581s

Log wrangler untuk tiga request thumbnail berturut-turut: GET /thumbnail 200 OK (48ms), lalu (7ms), lalu (8ms). Request pertama membuat foto 3.840.000 byte dan menumbuhkan memori; berikutnya cuma resize dan PNG. Thumbnail 400 lebar 4.721 byte, 100 lebar 1.330 byte. Ukuran PNG-nya tergantung implementasi deflate: kode yang sama di Chromium 147 juga memberi 2.510 byte, di WebKit 26.4 2.594 byte, di Firefox 146 2.477 byte, dan semuanya gambar yang sama.

📌 workers-rs

Worker juga bisa ditulis seluruhnya di Rust dengan crate worker (0.8.7) dan alat build worker-build (0.8.7): fungsi Rust dengan atribut #[event(fetch)] jadi handler-nya, dan wasm-bindgen membuatkan JavaScript perantara. Proyek baru dibuat dengan cargo generate cloudflare/workers-rs. Untuk endpoint di pelajaran ini, Worker JavaScript dengan satu modul Wasm kecil sudah cukup.

📌 Runtime edge lain

Deploy

wrangler deploy meng-upload Worker beserta modul .wasm-nya ke Cloudflare, setelah wrangler login. Pelajaran ini cuma menjalankan wrangler dev di laptop: workerd, runtime yang sama dengan produksi, tapi lokal. Satu bedanya penting: lokal, batas CPU gak dipaksakan. Loop 120 ms CPU di wrangler dev selesai normal, padahal di paket Free batasnya 10 ms.

⚠️ Jebakan yang sering ketemu

TL;DR: Cloudflare Workers menjalankan JavaScript dan Wasm di isolate V8 di banyak kota, dengan cold start yang hampir selalu tertutup (isolate dimuat saat jabat tangan TLS, 99,99% request hangat). Modul Wasm di-import dari file .wasm (WebAssembly.Module yang di-compile wrangler), dan new WebAssembly.Instance(modul, {}) sekali di global scope; compile dari bytes saat runtime ditolak: Wasm code generation disallowed by embedder. Endpoint Homestay Kabut: /harga?kamar=teras&malam=2 memberi {"total":700000} dari hargaInap, /thumbnail?w=200 memberi PNG 2.510 byte dari thumbnail (rata-rata area), 48 ms di request pertama lalu 7 sampai 8 ms. Batas paket (Oktober 2026): CPU 10 ms (Free) dan 30 detik sampai 5 menit (Paid), memori 128 MB, Worker 64 MiB, startup 1 detik. Jalankan lokal dengan wrangler dev, deploy dengan wrangler deploy. Runtime lain: Fastly Compute (Wasmtime), Deno (import .wasm sejak 2.1), Vercel (?module, sekarang menyarankan Node.js), Fermyon Spin (Akamai).

Yang akan kamu pelajari