ข้ามไปยังเนื้อหา

ออบเจกต์ Imports

ไม่ใช่ทุก WebAssembly module จะพึ่งพาตัวเองได้ทั้งหมด เมื่อ module ต้องการเรียกฟังก์ชัน JavaScript, อ่านข้อมูลจาก shared memory หรือเข้าถึงค่า global ที่ host จัดการ จะประกาศ dependency เหล่านั้นด้วยคำสั่ง (import ...) ในระหว่าง instantiation JavaScript ต้องส่งค่าที่ตรงกันมาให้ — นี่คือสิ่งที่เรียกว่า imports object

การประกาศ 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 ที่ส่งให้ 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

นี่คือตัวอย่าง 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
WebAssembly

ฟังก์ชันเป็น import ที่พบบ่อยที่สุด แต่กลไกเดียวกันนี้ใช้ได้กับ memory, globals และ tables ด้วย:

// Import a shared Memory
const 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

ตัวเลือกBenefitCost
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

ใน `(import "env" "log" ...)` ตัวไหนคือ module namespace?
imports object สำหรับ `(import "env" "log" (func ...))` มีรูปร่างแบบไหน?
error อะไรถูก throw เมื่อ imports object ขาด import ที่จำเป็น?