[TOOLS] 13 分鐘閱讀OraCore 編輯部

WebAssembly 讓原生碼變模組

我把 WebAssembly 拆成可落地的使用框架,最後附一份可直接複製的採用模板。

分享 LinkedIn
WebAssembly 讓原生碼變模組

我把 WebAssembly 拆成可落地的使用框架,最後附一份可直接複製的採用模板。

我用 WebAssembly 一陣子了,老實說,一開始我也被那套話術帶歪:把原生碼丟進瀏覽器,速度就會飛起來。結果呢?工具鏈一堆洞、除錯像在考古、host API 又一層一層卡住。你以為自己在做效能優化,實際上常常是在跟邊界條件搏鬥。最煩的是,大家嘴上都說「只要編成 Wasm 就行」,但真正上手後,才發現你得先回答一堆很現實的問題:誰來載入、誰來管記憶體、誰來暴露能力、誰來負責安全。這東西不是魔法,頂多是個很有用、但很挑場合的模組格式。

我這篇是看了 WebAssembly 的條目後,重新把它拆成工程師能用的版本。那篇文章很乾,乾到像規格文件摘要,但它把骨架講得很清楚:二進位格式、文字格式、host interface、browser 與 non-browser runtime、編譯路徑,還有一堆限制。這些細節才是重點,不是行銷詞。

WebAssembly 不是「更快的 JavaScript」

訂閱 AI 趨勢週報

每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。

不會寄垃圾信,隨時可取消。

“WebAssembly (Wasm) defines a portable binary code format and a corresponding text format for executable programs and software interfaces for facilitating communication between such programs and their host environment.”

翻譯一下就是:Wasm 是一種模組格式,外加一個跟 host 溝通的契約。它不是框架,不是 UI 層,也不是瀏覽器替代品。它比較像「把編譯後的程式,放進一個受控的執行邊界裡」。重點在 host environment。Wasm 不會自己長出 DOM、檔案系統或網路權限,它只能拿到 host 願意給的能力。

WebAssembly 讓原生碼變模組

我第一次把 Wasm 當成小型 app 來用時,就踩到這個坑。模組能編、能載入,然後一碰到 DOM 就卡死。不是 Wasm 壞掉,是我想太多。瀏覽器還是瀏覽器,Wasm 只是裡面一個很守規矩的客人。

文章也提到兩種格式:.wasm 是二進位,.wat 是文字格式。這個差異平常很容易被忽略,但你一旦開始 debug,就會很在意。二進位是部署用,文字格式是給人看的。你不會拿 minified JS 當除錯主力,Wasm 也一樣。

實操寫法很簡單:每次要不要用 Wasm,我都先把邊界寫清楚:

  • 哪些程式碼要編進 Wasm?
  • 是誰在載入這個模組?
  • 資料怎麼跨邊界?
  • 哪些 API 留在 host 外面?

這四題答不出來,你不是在設計 Wasm,你只是在收集名詞。

瀏覽器能跑,不代表只適合瀏覽器

WebAssembly 最早是為了網頁高效能應用,但文章也明講了,它是給 non-web 環境一起用的。這句話很重要,因為很多人還卡在「Wasm = 瀏覽器技巧」的舊腦袋。現在真正有意思的地方,常常在 browser 外面。

它的時間線也很能說明問題:2015 年宣布、2017 年 3 月首次釋出、2019 年 12 月成為 W3C recommendation、2021 年拿到 ACM SIGPLAN Programming Languages Software Award。這不是什麼神蹟,就是一群人把規格磨到夠穩,穩到大家敢拿來賭產品。

文章還提到,到 2024 年 3 月,追蹤到的瀏覽器有 99% 支援 WebAssembly 1.0。這種數字很現實:它把問題從「能不能用」推進到「到底該放在哪裡用」。

我看過不少團隊把 Wasm 當成瀏覽器專用玩具,結果錯過很多場景。像 plugin 系統、sandbox 執行、edge compute、host process 裡跑語言 runtime,這些地方常常比前端頁面更需要 Wasm。因為你要的是受控執行,不是 UI 魔法。

實操寫法:先選 runtime,再選 compiler。文章列的像 WasmtimeWasmerWasmEdgewazeroWAMR,每個都代表不同部署路線。別反過來,先編再說,最後才發現 host 根本不對。

Wasm 的價值是可攜,但它靠限制換來

文章有一句我覺得很實在:WebAssembly 想支援任何作業系統上的任何語言,很多主流語言也已經有某種程度的支援。這句話不是在吹「一次編譯,到處執行」那種空話,而是在講一個更務實的事:如果你的語言能 target Wasm,你就能把核心邏輯搬到不同 host,而不用整個重寫。

WebAssembly 讓原生碼變模組

這也是 Wasm 常被拿去做 sandbox、plugin、跨環境 compute 的原因。它給你一個小而明確的執行目標,然後把互動方式收斂起來。這很煩,但也正因為煩,才有一致性。

但代價也很明確:Wasm 能 portable,是因為它被限制住了。它是 stack-based execution model,不能亂摸底層硬體,不能假裝 host 不存在。你得到的是可預期的 runtime 形狀,失去的是自由亂搞的空間。

我自己是接受這個交換的,但前提是團隊真的懂它在交換什麼。我看過太多人一聽到「跨平台」就眼睛發亮,然後在不能直接碰 DOM、不能隨便吃大記憶體、不能任意操作 host internals 的時候開始抱怨。那不是 Wasm 爛,是你一開始就把它當成另一種原生程式在期待。

實操寫法:只有在下面這些情境,我才會把 Wasm 列進方案裡:

  • 要跨 host 執行
  • 要多語言共用核心邏輯
  • 要 sandbox / constrained execution
  • 要 plugin 式擴充
  • 要處理計算密集工作

如果你要的是 UI、路由、直接呼叫 browser API,那還是先用 JavaScript 或 TypeScript,別硬拗。

工具鏈才是決定成敗的那個爛地方

文章對編譯路徑講得很直接:Wasm 不是你「寫」出來的,是你 target 出來的。可能是 AOT,也可能是 JIT,可能有 interpreter,通常還得靠一整套 toolchain。這裡才是真正開始做工程,不是做夢。

文章點名了幾條常見路線。Emscripten 會用 Clang 和 Binaryen 把 C/C++ 編成 Wasm;Clang 本身也能直接 target Wasm;LLVM 系的流程還能接 Rust、AssemblyScript。意思很簡單:你的 Wasm 體驗,幾乎完全被你現有的語言生態決定。

這也解釋了為什麼 Wasm 的採用體感很不平均。runtime 也許標準化了,但 build path 沒那麼一致。某些語言已經很順,某些語言則是「能跑,但別問太多」。文章提到 2021 年大約有 40 種程式語言支援 Wasm 作為編譯目標,數量不少,但支援品質差很多。

我自己最怕的就是這種情況:runtime 看起來漂亮,build pipeline 卻醜到不行。因為 build 不是一次性的,它每天都會跑。你如果把除錯、最佳化、產物大小、載入時間都丟給未來的自己,最後通常都是未來的自己在罵你。

實操寫法:在正式採用前,我會先做完整 pipeline 試跑:

  • source language 到 Wasm 的 build 是否順
  • 最佳化後 module 多大
  • 在 target host 的 instantiate 時間
  • debug 流程是否可用
  • 資料如何進出模組

只要其中一項很痛,我就先把痛點記下來,別假裝它不存在。

Wasm 最適合當 host 控制的 sandbox

文章的 implementation 章節其實已經把答案講出來了:WebAssembly runtime 是一個低階 virtual stack machine,可以嵌進任何 host application。這代表規則是 host 定的,模組只是在規則內跑。你如果要 plugin system、受控 extension point、或安全隔離的執行環境,這就是它真正有價值的地方。

Proxy-Wasm 這類系統,就是把 Wasm 拿來當 host 管控的擴充邊界。host 只暴露剛剛好的能力,模組做自己該做的事,不會一上來就拿到整個流程的控制權。比起直接塞 native shared library,這種做法安全感高很多。

我看過這個模式在需要客製邏輯的團隊裡很好用。你不想讓外部邏輯直接碰核心服務,但又要讓它有一定彈性,Wasm 就很適合。只是別天真,policy、metering、ABI 這些東西還是要做,不然 sandbox 只是漂亮口號。

實操寫法:當你要做 plugin boundary 時,我會優先選 Wasm,前提是你要的是:

  • host 可控
  • 跨語言 plugin
  • 比 native plugin 更小的信任面
  • 跨環境可攜的執行方式

如果 plugin 需要深度 OS 整合、直接玩檔案系統、或碰 host internals,別鬧了,Wasm 只會讓你更痛。

限制不是附註,是你上線前就要面對的現實

文章最有用的地方之一,就是它不幫 Wasm 亂吹。先講最基本的:在瀏覽器裡,Wasm 不能直接操作 DOM,得透過 JavaScript。這一條就足夠把一半不成熟的想像打回去。

再來是 feature support 不等於 browser support。文章提到 multithreading 當時還在 draft 階段,其他 extension 的支援也不齊。記憶體限制更現實,文章甚至點出在行動瀏覽器上,大約 300MB 就可能開始危險,像 Chrome on Android 和 Safari on iOS 都得注意。這不是小細節,這是部署邊界。

還有 CSP。文章說,如果沒有設定 Content-Security-Policy,或用了 unsafe-eval,瀏覽器通常允許 Wasm;但在其他政策下就不一定。Chrome 要 unsafe-eval 這種事,真的很容易把 demo 變成 production 災難。我看過團隊以為 module 到哪都能載,結果被 CSP 打臉,問題根本不是 Wasm 本身,而是你沒先把政策看清楚。

實操寫法:上碼前先列出這張清單:

  • browser policy 是什麼?
  • module 需要多少記憶體?
  • 需不需要 threads?
  • 需不需要 browser APIs 或 JS glue?
  • 不同 client 的 feature support 會不會不一致?

如果答案是「先做再說」,那你只是把除錯延後到 production。

WASI 和新規格,才是 Wasm 往外長的地方

文章提到 WASI,也就是 WebAssembly System Interface。這是 Wasm 從「瀏覽器二進位格式」往「可攜應用 runtime」走的橋。對我來說,這才是 Wasm 真正值得關注的方向之一,因為它開始碰系統能力,而不是只在網頁裡打轉。

文章也整理了後續規格:WebAssembly 2.0 加了 SIMD、multiple return values、mass memory init/copy、reference types;WebAssembly 3.0 則加了 64-bit address space、multiple address spaces、exception handling、以及 GC 相關的 struct 和 array types。這些東西看起來很規格控,但它們其實是在替高階語言鋪路。

這段最值得注意的是:即使 spec 往前走,你的 runtime 不一定跟得上。文章也提到,WasmGC 對某些 runtime 仍不夠用。這句話很重要,因為它提醒你別把「規格有」誤當成「你能用」。

實操寫法:如果你要做的不只是 MVP,就先確認 host 和 compiler 到底支援哪些 extension:

  • WASI:要不要系統式能力
  • SIMD:要不要向量化
  • threads:要不要並行
  • reference types:要不要更豐富的 host interop
  • GC features:你的語言 runtime 需不需要

這張表可以少掉很多「做了六週才發現不支援」的冤枉路。

可抄的模板

# WebAssembly 採用模板

## 1) 我要跑什麼
- 語言:
- 原始碼位置:
- 模組責任:
- 為什麼要放進 Wasm,而不是 JS / native / plugin runtime:

## 2) 它要跑在哪裡
- Browser:
- Non-browser runtime:
- Host application:
- 目標作業系統:
- 限制條件(CSP、threads、memory、WASI、GC):

## 3) 邊界契約
### Inputs
- 資料格式:
- 序列化方式:
- 單次 payload 上限:

### Outputs
- 回傳值:
- events / callbacks:
- 錯誤格式:

### Host 暴露的 API
- 允許的能力:
- 禁止的能力:
- 需要 JS bridge 嗎:yes / no

## 4) Toolchain
- Compiler:
- Optimizer:
- Runtime:
- Debugger:
- Build command:
- Release build command:

## 5) 最小可行驗證
- 模組能不能 instantiate?
- 能不能跟 host 交換一個值?
- 失敗時能不能一路 trace 到底?
- 能不能在真實 target 環境部署?

## 6) 風險清單
- 需要 DOM?如果要,邏輯留在 JS。
- 行動端記憶體超過 300MB?先別硬上 browser。
- CSP 擋 Wasm?先修 policy,再談上線。
- 需要 threads?確認 host 支援。
- 需要 WASI?確認 runtime 相容。
- 需要 GC / reference types / SIMD?確認 exact version support。

## 7) 決策規則
只有在下面情況才用 WebAssembly:
- 程式碼必須跨 host 可攜,
- host 要控制 sandbox,
- compiler / runtime 路徑可接受,
- 而且它的限制比你要解的問題小。

如果不是,留在 native code、JavaScript,或更簡單的 plugin interface。

這份模板我會直接拿去開題會用。它逼你回答的不是「能不能編」,而是「這個東西到底該不該放在 Wasm 邊界裡」。這才是省時間的地方。

原始拆解主要來自 WebAssembly Wikipedia article,我另外補了自己的實務判斷與採用順序。工具與規格連結也分別參考了 EmscriptenWASIWasmtime 等官方或權威來源;我有保留原文事實,沒有亂加數字。