ออบเจกต์ Imports
ไม่ใช่ทุก WebAssembly module จะพึ่งพาตัวเองได้ทั้งหมด เมื่อ module ต้องการเรียกฟังก์ชัน JavaScript, อ่านข้อมูลจาก shared memory หรือเข้าถึงค่า global ที่ host จัดการ จะประกาศ dependency เหล่านั้นด้วยคำสั่ง (import ...) ในระหว่าง instantiation JavaScript ต้องส่งค่าที่ตรงกันมาให้ — นี่คือสิ่งที่เรียกว่า imports object
ไวยากรณ์การประกาศ import
หัวข้อที่มีชื่อว่า “ไวยากรณ์การประกาศ import”การประกาศ import จะระบุชื่อของสิ่งที่ต้องการนำเข้าและอธิบาย type ของตัวเอง อาร์กิวเมนต์ string สองตัวแรกสร้าง namespace สองระดับ: module name (key ชั้นนอก) และ field name (key ชั้นใน):
(module (import "env" "log" (func $log (param i32))))ที่นี่ "env" คือ module namespace และ "log" คือ field name การประกาศนี้หมายความว่า: “module นี้ต้องการฟังก์ชันชื่อ log ภายใน namespace env โดยฟังก์ชันนั้นต้องรับอาร์กิวเมนต์ i32 หนึ่งตัว”
รูปแบบของ imports object
หัวข้อที่มีชื่อว่า “รูปแบบของ imports object”imports object ที่ส่งให้ WebAssembly.instantiate() สะท้อน namespace สองระดับนั้น — key ชั้นนอกคือ module name และ key ชั้นในคือ field name:
const importObject = { env: { log: (n) => console.log(n) }};ค่าภายในแต่ละตัวต้องตรงกับ type ที่ประกาศไว้ใน WAT ถ้าเป็น function import ต้องใช้ฟังก์ชัน JS; memory import ต้องใช้ WebAssembly.Memory; และ global import ต้องใช้ WebAssembly.Global
การ import ฟังก์ชัน JS
หัวข้อที่มีชื่อว่า “การ import ฟังก์ชัน JS”นี่คือตัวอย่าง WAT module ที่สมบูรณ์ซึ่ง import ฟังก์ชัน log และเรียกใช้จากฟังก์ชัน run ที่ export ออกมา:
(module (import "env" "log" (func $log (param i32))) (func (export "run") i32.const 42 call $log))ฟังก์ชัน run push ค่าคงที่ 42 ขึ้น stack จากนั้นเรียก $log — ซึ่งรับค่านั้นและส่งต่อไปยังฟังก์ชัน JS ที่ระบุไว้ใน imports object
ฝั่ง JavaScript:
const { instance } = await WebAssembly.instantiate(bytes, { env: { log: (n) => console.log('Wasm says:', n) }});instance.exports.run(); // prints: Wasm says: 42ทดลองรัน
หัวข้อที่มีชื่อว่า “ทดลองรัน”สิ่งอื่นที่ import ได้
หัวข้อที่มีชื่อว่า “สิ่งอื่นที่ import ได้”ฟังก์ชันเป็น import ที่พบบ่อยที่สุด แต่กลไกเดียวกันนี้ใช้ได้กับ memory, globals และ tables ด้วย:
// Import a shared Memoryconst memory = new WebAssembly.Memory({ initial: 1 });const importObject = { env: { memory, log: (n) => console.log(n) }};การ WebAssembly.Memory import ทำให้ JavaScript และ Wasm แชร์ ArrayBuffer เดียวกัน ทำให้สามารถส่ง string, array และข้อมูลโครงสร้างอื่นๆ ไปมาได้โดยการอ่านและเขียน byte ดิบ Globals และ tables ก็ใช้รูปแบบเดียวกัน — ประกาศใน WAT ด้วย (import ...) และส่ง WebAssembly.Global หรือ WebAssembly.Table ที่ตรงกันใน imports object
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
instantiateStreaming + imports object | ส่ง imports พร้อมเริ่ม stream compile ได้ทันที ลด latency ก่อนโค้ดที่ import ไว้จะเริ่มถูกเรียก | server ต้องตอบ Content-Type: application/wasm มิเช่นนั้น reject ด้วย TypeError ก่อนแม้แต่จะตรวจ imports |
instantiate จาก buffer เต็มไฟล์ | ไม่สนใจ MIME type ของ server ใช้ได้แน่นอนทุกกรณี | ต้องรอดาวน์โหลดครบไฟล์ก่อน ทำให้ host functions ที่ import ไว้พร้อมใช้ช้ากว่า |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ส่ง field name หรือตัวพิมพ์ใหญ่-เล็กใน imports object ไม่ตรงกับที่ประกาศไว้ใน
(import ...)พอดี เช่นพิมพ์Logแทนlogทำให้เกิดLinkError - ส่ง string หรือ array ก้อนใหญ่เข้าไปทีละ argument ผ่านฟังก์ชันที่ import ไว้ แทนที่จะเขียนข้อมูลลง shared memory ที่ import เข้าไปครั้งเดียวแล้วส่งแค่ pointer/length
- ลืมใช้
BigIntเมื่อฟังก์ชันที่ import ต้องรับพารามิเตอร์ชนิดi64จากฝั่ง Wasm ทำให้ JS callback ได้ค่าที่ไม่ตรงกับที่คาดไว้
💡 ตัวอย่างจากของจริง
AutoCAD Web พอร์ต engine C++ เดิมมาเป็น Wasm แล้ว import ฟังก์ชัน host จำนวนมาก (rendering, input handling, file I/O) เพื่อเชื่อม engine เข้ากับ browser APIs ให้ผู้ใช้โต้ตอบกับ CAD model ได้แบบ real-time