คอมไพล์และสร้าง Instance
การโหลด WebAssembly module จาก JavaScript เป็น process สองขั้นตอน: compile ไบต์ให้กลายเป็น module artifact ที่ผ่านการตรวจสอบแล้ว จากนั้น instantiate module นั้นเพื่อให้ได้ instance ที่ทำงานได้จริงพร้อม exports ที่เรียกใช้ได้ ตัวแปรส่วนกลาง WebAssembly ของ browser เปิดเผย API เฉพาะสำหรับทั้งสองขั้นตอน รวมถึง shortcut ที่สะดวกซึ่งทำทั้งสองอย่างในคราวเดียว
WebAssembly.compile()
หัวข้อที่มีชื่อว่า “WebAssembly.compile()”WebAssembly.compile(bufferSource) รับ BufferSource ใดก็ได้ (เช่น Uint8Array, ArrayBuffer หรือรูปแบบที่คล้ายกัน) และคืนค่าเป็น Promise<WebAssembly.Module> ผลลัพธ์ Module ที่ได้คือ artifact ที่คอมไพล์และตรวจสอบแล้ว — browser ได้แยกวิเคราะห์และปรับแต่งไบต์แล้ว แต่ยังไม่มีการรันโค้ดใดเลย
const response = await fetch('module.wasm');const bytes = await response.arrayBuffer();const module = await WebAssembly.compile(bytes);// `module` can be cached, cloned, or passed to a Worker via postMessageเนื่องจากออบเจกต์ Module สามารถ serialize ได้ จึงเก็บไว้ใน IndexedDB หรือส่งไปยัง thread อื่นด้วย postMessage ได้ — คุณจ่ายค่าใช้จ่ายการคอมไพล์เพียงครั้งเดียวและนำผลลัพธ์มาใช้ซ้ำได้
WebAssembly.instantiate()
หัวข้อที่มีชื่อว่า “WebAssembly.instantiate()”WebAssembly.instantiate() มีสอง overload:
// Overload 1 — pass raw bytes: compiles AND instantiates in one callconst { module, instance } = await WebAssembly.instantiate(bytes, importObject);
// Overload 2 — pass an already-compiled Module: instantiates onlyconst instance = await WebAssembly.instantiate(module, importObject);// Note: this overload returns a WebAssembly.Instance directly, not {module, instance}Overload แรกเป็นจุดเริ่มต้นที่พบบ่อยที่สุด ส่วน overload ที่สองมีประโยชน์เมื่อต้องการ instantiate module ที่คอมไพล์แล้วหลายครั้ง เช่น เพื่อสร้าง sandbox ที่แยกจากกันและไม่แชร์ state ใดๆ
ออบเจกต์ instance.exports
หัวข้อที่มีชื่อว่า “ออบเจกต์ instance.exports”หลังจาก instantiation แล้ว instance.exports คือออบเจกต์ JavaScript ธรรมดา โดย key ของตัวเองคือชื่อที่ประกาศไว้ในส่วน export ของ Wasm module ทุกฟังก์ชัน memory, global หรือ table ที่ export จะปรากฏเป็น property ฟังก์ชันที่ export ออกมาทำงานเหมือนฟังก์ชัน JS ทั่วไป — เรียกใช้ได้เลย ส่งตัวเลข JS เข้าไป และรับตัวเลข JS กลับมา
const { instance } = await WebAssembly.instantiate(bytes, {});const { square, cube } = instance.exports;
console.log(square(5)); // 25console.log(cube(3)); // 27WAT module ที่กำหนด exports เหล่านั้นมีหน้าตาดังนี้:
(module (func (export "square") (param $n i32) (result i32) local.get $n local.get $n i32.mul) (func (export "cube") (param $n i32) (result i32) local.get $n local.get $n i32.mul local.get $n i32.mul))ทดลองรัน
หัวข้อที่มีชื่อว่า “ทดลองรัน”ตัวรันด้านล่างจะคอมไพล์ module, instantiate และเรียกใช้ทั้งสอง export
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
instantiateStreaming | คอมไพล์ทับซ้อนกับการดาวน์โหลด ได้ instance พร้อมใช้เร็วที่สุด | ต้องการ Content-Type: application/wasm จาก server ถ้าผิดจะ reject ด้วย TypeError ทันที |
compile + instantiate แยกขั้นตอน | ควบคุมได้ละเอียด เช่น cache Module ไว้ใช้ instantiate ซ้ำหลายครั้งโดยไม่ต้องคอมไพล์ใหม่ | ต้องมี buffer เต็มไฟล์อยู่ในหน่วยความจำก่อนจึงจะเริ่มคอมไพล์ได้ |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- โหลดไฟล์ด้วย
arrayBuffer()ก่อนเสมอแล้วค่อยเรียกWebAssembly.instantiate()ทั้งที่ควรใช้instantiateStreamingเพื่อคอมไพล์ไปพร้อมกับดาวน์โหลด - คอมไพล์ module ใหม่ทุกครั้งที่ต้องการ instance เพิ่ม แทนที่จะแยก
WebAssembly.compile()ออกมาครั้งเดียวแล้วเรียกWebAssembly.instantiate(module, ...)ซ้ำจากModuleเดิม - ลืมว่า export ที่เป็น
i64จะกลับมาเป็นBigIntแล้วเอาไปบวก/คูณกับnumberตรงๆ โดยไม่แปลงชนิดก่อน ทำให้โค้ด throw error
💡 ตัวอย่างจากของจริง
Squoosh คอมไพล์ Wasm codec หลายตัวไว้ล่วงหน้าเพียงครั้งเดียว แล้ว instantiate ใหม่ทุกครั้งที่ผู้ใช้เลือกบีบอัดภาพด้วย codec นั้น เพื่อเลี่ยงต้นทุนการคอมไพล์ซ้ำซ้อนในทุก session