- นับตั้งแต่เปิดตัวครั้งแรกในปี 2017 WebAssembly ได้พัฒนามาอย่างต่อเนื่องพร้อม รองรับการรันภาษาระดับล่างอย่าง C/C++ แต่ก็ยังคง ถูกปฏิบัติเป็นภาษาชั้นสอง บนแพลตฟอร์มเว็บ
- มีเพียง JavaScript เท่านั้นที่ โต้ตอบกับ Web API ได้โดยตรง ส่วน WebAssembly ต้องเขียน โค้ด binding ของ JS (glue code) ที่ซับซ้อนเพื่อทำสิ่งนี้
- โครงสร้างเช่นนี้นำไปสู่ ขั้นตอนการโหลดที่ซับซ้อน, โอเวอร์เฮดด้านประสิทธิภาพ, และ การแยกขาดของ toolchain ตามภาษา ซึ่งทำให้ประสบการณ์ของนักพัฒนาแย่ลง
- เพื่อแก้ปัญหานี้ Mozilla จึงเสนอ WebAssembly Component Model ที่ทำให้สามารถ เรียกใช้ Web API และโหลดโมดูลด้วยวิธีที่เป็นมาตรฐานได้โดยไม่ต้องพึ่ง JS
- หากโมเดลนี้ได้รับการใช้งานอย่างแพร่หลาย WebAssembly ก็มีแนวโน้มจะกลายเป็น สภาพแวดล้อมการรันชั้นหนึ่งภายในเบราว์เซอร์ และวางรากฐานให้แม้นักพัฒนาทั่วไปก็ใช้งานได้ง่ายขึ้น
เหตุใด WebAssembly จึงถูกปฏิบัติเป็นภาษาชั้นสอง
- WebAssembly สามารถ เข้าถึงแพลตฟอร์มเว็บ ได้ผ่าน JavaScript เท่านั้น และไม่มีสิทธิ์เรียก Web API โดยตรง
- JavaScript โหลดได้ง่ายผ่านแท็ก
<script> แต่ WebAssembly ต้องมี กระบวนการโหลดแบบแมนนวลผ่าน JS API
- ต้องผ่านการเรียก API ที่ซับซ้อนอย่าง
WebAssembly.instantiateStreaming() และนักพัฒนาต้องจำสิ่งเหล่านี้หรือใช้เครื่องมือช่วยอัตโนมัติ
- ข้อเสนอ esm-integration ทำให้สามารถ import ไฟล์
.wasm ได้โดยตรงผ่านระบบโมดูลของ JS จึงช่วยลดความซับซ้อนของขั้นตอนการโหลด
- สามารถโหลดได้โดยตรงในรูปแบบ
<script type="module" src="/module.wasm"></script>
ข้อจำกัดในการเข้าถึง Web API
- งานที่ใน JavaScript ทำได้ด้วย
console.log("hello, world") เพียงบรรทัดเดียว กลับต้องใช้ขั้นตอนซับซ้อนใน WebAssembly เช่น การเข้าถึงหน่วยความจำของ JS, การถอดรหัสสตริง, การห่อฟังก์ชัน
- WebAssembly ไม่สามารถเข้าถึงอ็อบเจ็กต์
console หรือ DOM ได้ จึงต้องเรียกผ่าน JS ทางอ้อมด้วย การแชร์หน่วยความจำและการ import/export ฟังก์ชัน
- โค้ด binding (glue code) ที่เกิดขึ้นในกระบวนการนี้แตกต่างกันไปในแต่ละภาษา และมักถูกสร้างอัตโนมัติด้วยเครื่องมืออย่าง
embind, wasm-bindgen
- อย่างไรก็ตาม สิ่งนี้ทำให้เกิดปัญหาอย่างความซับซ้อนในการ build ที่เพิ่มขึ้น, runtime overhead และความไม่เข้ากันระหว่างภาษา
สาเหตุทางเทคนิคที่ทำให้ WebAssembly ยังไม่กลายเป็นภาษาชั้นหนึ่ง
- ความยากในการผสานรวมกับคอมไพเลอร์: คอมไพเลอร์ของแต่ละภาษาต้องสร้างโค้ดสำหรับผสานรวมกับ JS และแพลตฟอร์มเว็บแยกกัน ซึ่งไม่สามารถนำกลับมาใช้ซ้ำได้
- ความไม่เข้ากันของคอมไพเลอร์มาตรฐาน: ไฟล์ที่สร้างด้วย
rustc --target=wasm ไม่สามารถรันในเบราว์เซอร์ได้ทันที
- จำเป็นต้องติดตั้ง toolchain ที่ไม่เป็นทางการ เพิ่มเติมเพื่อรองรับการผสานรวมกับแพลตฟอร์ม
- อคติของ ecosystem ด้านเอกสาร: เอกสารเว็บอย่าง MDN ส่วนใหญ่เขียนโดย ยึด JavaScript เป็นศูนย์กลาง ทำให้ผู้ใช้ภาษาอื่นมีอุปสรรคในการเริ่มต้นสูง
- ปัญหาด้านประสิทธิภาพ: การเรียก DOM ผ่าน JS binding ทำให้ ประสิทธิภาพลดลง 45% เมื่อเทียบกับการเรียกโดยตรง
- ในการทดลองของเฟรมเวิร์ก Dodrio เมื่อเอา JS glue ออก เวลาที่ใช้ในการนำการเปลี่ยนแปลง DOM ไปใช้ลดลงครึ่งหนึ่ง
- การพึ่งพา JavaScript: หากต้องการใช้ WebAssembly ในงานจริง สุดท้ายก็ยังต้องเข้าใจ JS อยู่ดี ซึ่งนำไปสู่ปัญหา abstraction leakage
การมาของ WebAssembly Component Model
- WebAssembly Component Model กำหนด หน่วยการรันที่เป็นมาตรฐาน ซึ่งหลายภาษาและหลายรันไทม์สามารถใช้ร่วมกันได้
- ทำให้สามารถเข้าถึง Web API, โหลดโมดูล และลิงก์ได้ โดยตรงโดยไม่ต้องผ่าน JS
- สามารถสร้างได้จากหลายภาษา และรองรับการรันได้ทั้งบนเบราว์เซอร์หรือรันไทม์อย่าง Wasmtime
- สามารถประกาศ API ที่ต้องการผ่าน WIT (Interface Description Language) และเรียกใช้ได้โดยตรงจากภายในคอมโพเนนต์
- เบราว์เซอร์สามารถโหลดคอมโพเนนต์ได้โดยตรงผ่าน
<script type="module" src="component.wasm"></script> และ จัดการ Web API binding ให้อัตโนมัติโดยไม่ต้องใช้ JS
การทำงานร่วมกับ JavaScript
- Component Model ยังรองรับ โครงสร้างแอปแบบไฮบริด
- ตัวอย่างเช่น เขียน image decoder ด้วย WebAssembly แล้วเรียกจาก JS ในรูปแบบ
import { Image } from "image-lib.wasm"; ได้
- JS สามารถใช้ WebAssembly component ผ่านการ import/export เหมือนโมดูลทั่วไป
แนวโน้มในอนาคตและการมีส่วนร่วม
- Mozilla กำลังทำงานด้าน การทำมาตรฐานของ Component Model ร่วมกับ WebAssembly CG และ Google ก็อยู่ในขั้นตอนการพิจารณา
- นักพัฒนาสามารถทดลองได้ผ่าน Jco หรือ Wasmtime ทั้งในเบราว์เซอร์และ CLI
- หากโมเดลนี้ได้รับการยอมรับอย่างแพร่หลาย WebAssembly ก็มีโอกาสขยายจาก ฟีเจอร์สำหรับผู้ใช้ขั้นสูง ไปเป็น เทคโนโลยีเว็บที่นักพัฒนาทั่วไปใช้งานได้
4 ความคิดเห็น
นี่ก็แทบจะเป็นแค่ความหวังแบบฉบับ Mozilla มากกว่า ฟรอนต์เอนด์ไม่อาจหลุดพ้นจากโครงสร้างของระบบลิมบิกที่ตอบสนองต่อปฏิกิริยาของตลาดได้ ตั้งแต่ตอนที่ WebAssembly ปรากฏตัวขึ้นก็มีการพอร์ต Doom 3 ทันที DOM ในเบราว์เซอร์สมัยใหม่ก็เปลี่ยนเป็นอ็อบเจ็กต์พร็อกซีแบบน้ำหนักเบามานานแล้ว และเมื่อพิจารณาถึงชุดคำสั่งเฉพาะสำหรับ JavaScript ของ CPU สมัยใหม่ รวมถึงข้อจำกัดเชิงควอนตัมของซิงเกิลคอร์ ก็ไม่มีวันที่แนวทางแบบนี้จะได้เปรียบในเชิงมูลค่าตลาด
ไบนารี WebAssembly ที่รันอยู่ใน Electron จะมีความหมายอะไรล่ะ? นี่ก็ดูเหมือนเป็นแค่อีกกรณีของการล่าชื่อเสียงจากการพอร์ต Rust หรือไม่ก็ GitKraken CLI เท่านั้นเอง
อย่างอื่นไม่รู้หรอก แต่แนวทางการแทรกโมดูล wasm เป็นไฟล์แบบ
<script type="module" src="/module.wasm"></script>นี่ชวนสนใจทีเดียวและคงต้องบอกด้วยว่าคำกล่าวอ้างที่ว่า WebAssembly มีอุปสรรคในการเข้าถึงสูงนั้นช่างน่าขัน สิ่งที่เกิดขึ้นก็แค่ความจำเป็นที่จะต้องทำมันต่ำกว่าความตั้งใจจะจ่ายเท่านั้น อยากได้ความเร็วกับขนาดที่เล็ก แต่ก็ยังอยากใช้ DOM กับ CSS งั้นเหรอ? นี่มันตลกร้ายอะไรกันครับ
ความเห็นจาก Hacker News
น่าเสียดายที่พอเลิกเป้าหมายแรกเริ่มที่จะรองรับ WebIDL ใน WebAssembly แล้วหันไปสร้าง IDL ตัวใหม่ กลับไม่ได้มองว่าการไม่มีการเข้าถึง DOM เป็นปัญหา
แน่นอนว่าเข้าใจความเป็นจริงของตลาด แต่ก็ยังอดเสียดายเวลาที่สูญเสียไปไม่ได้
ลิงก์อ้างอิงที่เกี่ยวข้อง: ประวัติ commit, บทความย้อนหลังเกี่ยวกับ stringref, บทความ ACM
หลังจากนั้นก็มีการเพิ่มเป้าหมายอีกสองข้อ คือ รองรับ API ที่ไม่ใช่เว็บ และ การทำงานร่วมกันระหว่างภาษา
WebIDL เป็นผลรวมของ JS กับ Web API จึงมีพลังในการแสดงออกสูง แต่ก็มีแนวคิดหลายอย่างที่ขัดกับเป้าหมายเหล่านี้
เพราะแบบนั้น component interface จึงเลือกแนวทางแบบ จุดตัดที่พกพาได้สูงกว่า แม้จะลดความสามารถในการแสดงออกลง
ส่วนตัวคิดว่าการเข้าถึง DOM สำคัญ แต่ Wasm CG ก็ยุ่งอยู่กับเรื่องที่มีลำดับความสำคัญสูงกว่า
เหตุผลที่เขียนบทความนี้ก็เพื่อบอกว่ายังจำปัญหานี้ได้อยู่ และมีแผนจะทำงานต่อไป
toolchain กับกระบวนการ build ซับซ้อนเกินไป จนทุกครั้งที่ใช้รู้สึกมีภาระทางความคิด
ประสิทธิภาพดีกว่ามากเมื่อไม่มี glue แต่ก็มีจุดเสี่ยงมากขึ้นตามไปด้วย
หวังว่า component model จะไม่เพิ่มความซับซ้อนแบบใหม่ แต่ดูจากตัวอย่างหลายภาษาแล้วก็ค่อนข้างชวนสับสนอยู่แล้ว
โดยเฉพาะ ตัวอย่าง Go ที่มีไฟล์ที่ถูกสร้างออกมาเยอะเกินไป และในมุมของนักพัฒนา สิ่งที่ต้องการมากคือ ทำให้ tooling เรียบง่ายขึ้น
ตอนนี้มันดูเหมือนไม่ได้กำจัดความซับซ้อน แต่แค่ ย้ายมันไปที่อื่น
สเปก wasm component เปลี่ยนอยู่เรื่อย ๆ เลยมี churn มาก
เป้าหมายคือทำให้นักพัฒนาเว็บไม่ต้องเขียน WIT เอง และสามารถใช้ Web API เหมือนใช้ไลบรารี
แต่ตอนนี้ก็ยังต้องไปอีกไกล
เช่น ถ้าแยกเป็นการแชร์ข้อความ การแชร์สื่อ การแชร์แอปพลิเคชัน ก็จะช่วยเรื่องความปลอดภัย และทำให้ทีมเล็ก ๆ สร้างทางเลือกแทนเบราว์เซอร์ได้ด้วย
แต่ขนาดของ Web API และ CSS ที่ใหญ่โตคือสิ่งที่ค้ำจุน การผูกขาดของเบราว์เซอร์ อยู่ จึงน่าจะทำแบบนั้นได้ยาก
น่าจะดีถ้ามีการทำมาตรฐาน WebAssembly registry เพื่อให้ประกอบคอมโพเนนต์เข้าด้วยกันได้ง่าย
สุดท้ายแล้วเว็บก็คือกระบวนการสร้าง นิยามของระบบปฏิบัติการแบบกระจายศูนย์
อธิบายได้ดีตั้งแต่แนวคิดไปจนถึงตัวอย่างโค้ด
ใน ecosystem ของ JS มีสามโปรเจ็กต์หลักคือ StarlingMonkey, ComponentizeJS, jco
ตอนนี้ toolchain ที่สุกงอมที่สุดคือ Rust แต่การรองรับภาษาในสาย LLVM (C/C++, Go, Python ฯลฯ) ก็ดีขึ้นเรื่อย ๆ
เป้าหมายของ WebAssembly คือการเป็น compile target ที่ผสานเข้ากับ local toolchain ได้อย่างเป็นธรรมชาติ
ถ้ายังต้องเข้าใจ glue code ของแต่ละภาษา หรือยังต้องทำความเข้าใจกับ runtime model สองแบบ WebAssembly ก็จะยังคงเป็น “เครื่องมือที่ใช้เฉพาะในสถานการณ์สุดขั้ว” อยู่ดี
ถ้าจะให้เกิดการเปลี่ยนแปลงจริง ๆ ต้อง ทำให้เส้นทางการ build แบบทั่วไปเรียบง่ายขึ้น
ยังไม่ชัดเจนว่าใน Component Model จะจัดการเรื่องนี้อย่างไร
DOM ต่างกันไปตามเบราว์เซอร์ และความสามารถก็เปลี่ยนไปในแต่ละครั้งที่โหลดหน้า
ในชั้น JS bridge สามารถใส่ polyfill ได้ง่าย แต่ในอินเทอร์เฟซ WIT การทำ runtime method detection หรือ polyfill ทำได้ยาก
นอกจากประสิทธิภาพแล้ว ความยืดหยุ่นของ ecosystem ก็สำคัญ
การต้องจัดการ JS glue code เองหรือพึ่งเครื่องมือสร้างอัตโนมัติ รู้สึกเหมือนถอยหลังครั้งใหญ่
การทดลอง Dodrio ที่ตัด glue ออกแล้วได้ ลด overhead ลง 45% น่าประทับใจมาก
แต่ก็สงสัยว่าเวลา WebAssembly Component Model โต้ตอบกับ Web API โดยตรงนั้น การจัดการหน่วยความจำ เป็นอย่างไร
อยากรู้ว่าข้อเสนอ Wasm GC ถูกใช้เพื่อคง DOM reference ไว้หรือไม่ หรือท้ายที่สุดแล้วยังต้องพึ่ง JS GC อยู่
หวังว่า Wasm จะกลายเป็น พลเมืองชั้นหนึ่ง อย่างแท้จริง
แต่ตอนนี้ IPC ก็ยังไม่มีประสิทธิภาพอยู่ดี และคิดว่าน่าจะต้องมีวิธีแบบ ส่งเป็นหน่วย memory page
การยอมให้ใครก็ได้รันโปรแกรมซับซ้อนบนคอมพิวเตอร์ของฉันเป็นความคิดที่ บ้าคลั่งในแง่ความปลอดภัย แต่เราก็ทำแบบนั้นกันมาจริง ๆ
ด้วย JS เราเจอบั๊กความปลอดภัยของเบราว์เซอร์มานับไม่ถ้วนตลอด 20 ปีที่ผ่านมา แต่ตอนนี้ก็มีหลักการออกแบบและมาตรการบรรเทาที่ลงตัวแล้ว
พอมาตอนนี้กลับจะพยายามเปลี่ยนไปสู่ กระบวนทัศน์การรันที่เสี่ยงอันตรายอีกแบบ มันทั้งย้อนแย้งและสวยงามในเวลาเดียวกัน
ระบบปฏิบัติการบนมือถือทำเรื่องนี้ได้ดีกว่าเดสก์ท็อปมาก
ออกแบบโดยมีวิศวกรเป็นศูนย์กลาง และไม่มี workflow เริ่มต้นที่เป็นมิตรกับผู้เขียน (author)
ถึงอย่างนั้นก็ดีที่ยังมีคนที่ใส่ใจปัญหาแบบนี้อยู่
ให้ความรู้สึกเหมือนกำลังทำวิศวกรรมเกินจำเป็นเพื่อให้ได้การประมวลผลสตริงเร็วขึ้น 2 เท่า เพียงเพื่อแมป DOM API แบบ 1:1
สำหรับ API อย่าง WebGL2, WebGPU, WebAudio ต้นทุนของ JS shim เล็กมากอยู่แล้ว
ปัญหาจริงคือเรื่องอย่างการคัดลอก GPU buffer ซึ่ง component model ก็ไม่ได้ช่วยตรงนั้น
อยากเห็น benchmark ที่ทดสอบ draw call หลายหมื่นครั้งบน WebGL2 หรือ WebGPU
นอกจากประสิทธิภาพแล้ว การปรับปรุง ประสบการณ์นักพัฒนา (DX) ก็สำคัญ
ตอนนี้มันเริ่มต้นยากเกินไป และมีแต่ผู้เชี่ยวชาญเท่านั้นที่ดึงประโยชน์ออกมาได้
ถ้าแข่งขันได้ด้วยประสิทธิภาพระดับแอปเนทีฟ นี่ก็เป็นการเปลี่ยนแปลงที่มีวิสัยทัศน์สำหรับอนาคตของเว็บ
ในยุคของ coding agent การปรับปรุง DX แบบที่พวกเขาพูดถึงอาจไม่สำคัญอีกต่อไป