JS Interop & การโหลด
WebAssembly ไม่สามารถทำงานได้โดยลำพัง ทำงานอยู่ภายใน host environment เสมอ และบนเว็บนั้น host ก็คือ JavaScript ฝั่ง JS มีหน้าที่ดึงข้อมูล bytes จากไฟล์ .wasm คอมไพล์ให้กลายเป็น WebAssembly.Module สร้าง instance ของ module นั้นพร้อมกับ imports ที่จำเป็น และในที่สุดก็เรียกฟังก์ชันที่ module เปิดเผยออกมา ส่วน Wasm นั้นอาศัยอยู่ในสภาพแวดล้อมหน่วยความจำที่ถูก sandbox อย่างเข้มงวด — ไม่สามารถเข้าถึง DOM, เครือข่าย หรือ browser API ใด ๆ ได้เลย เว้นแต่ JavaScript จะส่งฟังก์ชันที่ทำสิ่งนั้นมาให้
ขอบเขตระหว่าง host กับ module
หัวข้อที่มีชื่อว่า “ขอบเขตระหว่าง host กับ module”ความสัมพันธ์ระหว่าง JS และ Wasm นั้นมีเจตนาและชัดเจน ก่อนที่โค้ด Wasm ใด ๆ จะทำงาน JavaScript ต้องเตรียม imports object — JS object ธรรมดาที่มี keys ตรงกับการประกาศ import ภายใน module WAT
imports เหล่านั้นอาจเป็นฟังก์ชัน JS, instance ของ WebAssembly.Memory, instance ของ WebAssembly.Global หรือ instance ของ WebAssembly.Table ก็ได้ ในทางกลับกัน เมื่อ module ถูก instantiate แล้ว JS จะได้รับ object instance.exports กลับมา ซึ่งประกอบด้วยทุก symbol ที่ module เลือกจะ export ออกมา
flowchart LR JS["JavaScript (host)"] COMPILE["WebAssembly.instantiate(bytes, imports)"] IMPORTS["imports object (functions, memory, globals)"] MODULE["WebAssembly Module"] EXPORTS["instance.exports (functions, memory, globals)"] JS -->|"fetch .wasm bytes"| COMPILE JS -->|"provides"| IMPORTS IMPORTS --> COMPILE COMPILE --> MODULE MODULE -->|"exposes"| EXPORTS EXPORTS -->|"JS calls exports"| JS
ตัวอย่างที่รันได้แบบเรียบง่ายที่สุด
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้แบบเรียบง่ายที่สุด”module WAT ด้านล่าง export ฟังก์ชันเดียวชื่อ greet ที่ไม่รับอาร์กิวเมนต์และคืนค่าจำนวนเต็ม 42 ฝั่ง JavaScript คอมไพล์ bytes สร้าง instance ของ module ด้วย imports object ว่าง {} และเรียก instance.exports.greet()
(module (func (export "greet") (result i32) i32.const 42))const bytes = await compileWat(wat);const { instance } = await WebAssembly.instantiate(bytes, {});console.log('greet() =', instance.exports.greet()); // 42