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

Wasm 讓一份程式跑多個 runtime

我拆解 WebAssembly 的實戰方法,整理成 browser、edge、plugin 都能直接套用的模板。

分享 LinkedIn
Wasm 讓一份程式跑多個 runtime

這篇拆 WebAssembly 的實戰套路,最後給你一份能直接拿去跑 browser、edge、plugin 的模板。

我玩 WebAssembly 有一段時間了。老實說,前幾年我一直覺得它很像一個「看起來很對、用起來很煩」的東西。demo 很漂亮,簡報很會講,但我真的要把同一份邏輯塞進瀏覽器、server、edge 的時候,整個流程就開始冒煙。build 步驟不一樣、runtime 假設不一樣、包裝方式也不一樣,最後我不是在寫功能,是在跟環境打架。

最煩的是,大家都愛講「一次編譯,到處執行」,但我實際碰到的是「一次編譯,三種接法,四種坑」。我試過把 JS 的熱路徑搬出去,也試過拿它做 plugin sandbox,結果不是 boundary 太醜,就是 host 整合太碎。那時候我就知道,問題不是 WASM 不行,是我還在用老的腦袋看它。

後來我看到這篇 ZNY 在 DEV Community 的 WebAssembly in 2026,才把很多零散的感覺串起來。它不是在吹 WASM 多神,而是在講它現在到底適合拿來幹嘛。我就順著這個方向,把能直接抄的部分拆成一份給台灣開發者看的版本。

先別把 WASM 當語言,這個坑我踩過

訂閱 AI 趨勢週報

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

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

WebAssembly is a binary instruction format, not a language. You compile Rust, C++, Go, or other languages to WASM.

翻譯一下就是:WASM 不是你平常拿來寫業務邏輯的那個東西,它是目標格式。你真正寫的通常是 Rust、C、C++、Go 這些語言,然後再編譯成 WASM。這個差別很重要,因為很多人一開始就把它當成另一種程式語言,後面設計整個歪掉。

Wasm 讓一份程式跑多個 runtime

我以前也很愛問錯問題,像是「要不要用 WASM?」這種問法其實很空。比較實際的問法應該是:我要用什麼語言寫、哪個 runtime 來載、邊界怎麼切、哪些資料要跨過去。你一旦把問題改成這樣,很多決策就會自己浮出來,不用再靠想像。

我之前做過一個數值處理模組,原本想直接把整包邏輯搬到 WASM。結果寫到一半才發現,真正貴的是 boundary,不是算式本身。資料序列化、呼叫成本、錯誤回傳,這些東西才是你花時間的地方。你如果只是為了「看起來比較先進」就搬,最後只會得到更複雜的系統。

實操寫法很簡單:把 WASM 當成可部署的計算核心,不要當成 app 全家桶。先挑一塊最明確的熱點,例如 parser、encoder、crypto、壓縮、轉換,然後把它包成一個 API 很窄的模組。UI、狀態管理、網路協調,這些都留在 host。這樣你才不會把自己搞死。

  • 先決定 authoring language,再決定 runtime,不要反過來。
  • API 越小越好,跨 boundary 的資料越單純越好。
  • 先量測呼叫成本,再決定要不要搬。

browser 不再是唯一舞台,這點我現在很買單

WASM is the runtime for edge computing, plugin systems, server-side apps, and the browser.

這句話我看完是有點停住的。以前我腦中對 WASM 的定位,真的就是「瀏覽器裡的效能補丁」。但現在這個想法太小了。它比較像一個可攜的執行層,只是剛好 browser 先把它養大,後來 edge、server、plugin 系統也都開始能用。

這就是為什麼現在大家會把 Cloudflare Workers、Fastly Compute、AWS Lambda@Edge、Node.js、Deno、Bun 一起提。重點不是這些名字本身,而是同一份邏輯可以被不同 host 載入。對我來說,這才是 WASM 真正值錢的地方:你不用為每個環境重寫一遍核心邏輯。

我以前做過一個前後端都要用的驗證與轉換流程,最討厭的就是 browser 跟 server 版本慢慢長歪。今天前端先修一個 bug,明天後端又補一個 workaround,三個月後兩邊根本不是同一套規則。WASM 的價值不在「跑得很快」而已,它更像是幫你把邏輯收斂成單一來源。

實操寫法:如果你已經知道某段邏輯要同時出現在 browser、edge、server,先不要寫兩份三份。把共享邏輯抽成 WASM module,host 只做薄薄的 adapter。你可以把它想成「一份核心,多個外殼」。這樣版本控管、測試、回歸都會比較乾淨。

  • 適合:驗證、解析、轉換、壓縮、加解密、推論。
  • 不適合:DOM 操作、密集 I/O、非常 chatty 的流程。
  • 核心原則:共享邏輯進 WASM,環境差異留在 host。

WASI 不是口號,它是 WASM 真的能出門的原因

WASI provides a standardized way for WASM modules to interact with system resources like files, network, and clocks.

白話一點講,WASI 就是在幫 WASM 補上「跟系統講話」這件事。沒有這層東西,WASM 很容易只剩下瀏覽器裡的玩具感;有了它,module 才比較像一個真的能跑工作負載的東西,而不是只能展示的 demo。

Wasm 讓一份程式跑多個 runtime

我很討厭那種「標準化」三個字講得很大聲、落地卻很虛的技術。不過 WASI 這次算是比較務實。它不是要把 OS 全部重做一遍,而是只給 module 需要的那幾種能力:檔案、時間、基本系統資源。這種收斂反而比較像工程,不像行銷。

我自己最能接受的切法是:把 WASI 拿去處理輸入、輸出、批次轉換這種工作。像是讀檔、清洗、轉格式、產出結果,這些都很適合。你不需要讓 module 直接理解整個 host 的內部世界,只要它拿得到必要資源就夠了。

實操寫法:先從 deterministic 的任務開始,不要一開始就碰複雜服務協調。你可以把它想成一個「有標準接口的工作箱」。host 負責把資料塞進去,WASM 負責算完吐出來。這樣你才會真的感受到 portability 的好處,而不是卡在整合細節裡。

如果你想追這條線,我會先看 WASIWebAssembly,還有 Bytecode Alliance。這三個地方比較接近規格、runtime、工具鏈的核心,不是只會講故事的頁面。

Rust + WASM 不是唯一解,但它真的最少讓人後悔

cargo add wasm-bindgen serde serde_json --target wasm32-wasip1

這行我看了會點頭,因為它很像真正能進 production 的寫法,不是那種只適合 demo 的東西。Rust 現在做 WASM,最大的優點不是「潮」,而是工具鏈夠穩。你大概知道編譯會怎麼走、型別會幫你擋掉什麼、runtime 的邊界會在哪裡出事。

翻譯一下就是:Rust 幫你把很多本來要自己補的坑先補起來。你不用手刻 binary,不用猜 export 長什麼樣子,也不用自己發明一套很醜的 glue code。像 wasm-bindgenwasm-pack 這些工具,目的就是讓 module 看起來像正常 artifact,而不是某種神秘副產品。

我之前做過一個 browser 和 Node 都要共用的資料轉換器,第一版用 JS 包來包去,資料複製超多,整個很煩。後來換成 Rust module,boundary 收斂之後,整體反而比較好維護。這不是因為 Rust 比較高級,是因為它剛好適合做這種「純計算核心」的東西。

實操寫法:如果團隊本來就會 Rust,那就直接用,別裝沒事。如果團隊完全不熟,也不要假裝學習成本不存在。它確實有成本,但當你的模組是 CPU-heavy、security-sensitive、或需要跨 runtime 重用時,這筆成本通常值得。反過來說,如果只是薄薄一層 app state wrapper,我會直接待在 host language。

我自己的判斷很簡單:能不能把邏輯寫成純函式、能不能把輸入輸出講清楚、能不能把 host 的雜訊隔離掉。三個都能,Rust + WASM 很值得;兩個以上不行,我就先不要自找麻煩。

component model 才是把多語言拼起來的正經方法

The component model means you can mix Rust, Go, and C++ components in one application.

這句我覺得很值得拆。早期 WASM 比較像是「單一模組輸出一個東西」,你可以跑,但不好組。component model 的方向比較像是把它變成可組裝的系統,讓不同語言寫出來的東西能用比較乾淨的接口接在一起。

也就是說,WASM 正在從「一顆 module」往「一組有型別邊界的 component」移動。這對 production 很重要,因為真實世界的系統本來就不是單體。你一定會遇到不同團隊、不同版本、不同責任邊界,然後還要互相配合。沒有這種組裝能力,WASM 就只能當單點工具。

我看過太多 plugin 架構最後變成 host 自己扛所有髒活。插件一多,依賴一亂,host 就開始像垃圾場。component model 的吸引力就在這裡:它讓你保留隔離,又不必把整個系統切成互不相干的碎片。

實操寫法:如果你有多個團隊各自負責不同能力,或是你想把某些功能做成可替換、可獨立版本化的單元,就該考慮 component model。它很適合 plugin、extension、內部平台、跨語言協作。若你只是要把一個小功能搬上 WASM,現在先不用把架構搞太大。

  • 適合:插件、擴充、跨團隊協作、可替換能力。
  • 適合:語言不該決定架構的地方。
  • 不適合:只有一個小模組卻硬上複雜組裝流程。

插件系統是 WASM 最快讓人有感的地方

Extism is the framework for building plugin systems with WASM.

這段我很買單,因為我真的做過不少 plugin 系統,知道傳統做法有多容易翻車。你要處理 untrusted code、依賴隔離、版本衝突,還要避免 host 被插件拖垮。這種時候,WASM 的 sandbox 邊界就很有感。

如果你想看具體工具,Extism 是個很好的例子。它把 plugin 當成 WASM module 來載入,host 只需要呼叫函式、傳資料、收結果,不用把整個系統開給對方。這種安全感是我會想要的,不是那種「我們有做保護」的口頭保證。

翻譯一下就是:你可以讓別人擴充你的產品,但不用把整個 process 的控制權交出去。這對 SaaS、內部平台、資料轉換工具、內容處理管線都很實際。比起自己發明一套 scripting language,我會先看 WASM plugin 層,通常比較省事。

實操寫法:如果你的產品需要自訂 filter、importer、policy hook、transform,先別急著造語言。先把 plugin API 壓到最小,資料進出都用 plain JSON 或 typed value,插件不要直接碰 host internals。你越克制,後面越好維護。

如果你想看 runtime 端的落地,Wasmtime 也值得看。它不是拿來唬人的名詞,而是很實際地把 WASM 跑起來,讓你知道這東西不是只能在簡報裡活著。

別把 WASM 神化,我只會拿它處理難纏的那幾塊

Use WASM when you need performance-critical computations, portability across environments, sandboxed plugin systems, or existing C/Rust code in the browser.

這句話我覺得很誠實。它沒有在騙你說 WASM 什麼都能取代,也沒有硬把 JavaScript 拉下來踩。這就對了。技術選型最怕的就是把工具當信仰,結果把整個系統越搞越重。

白話講,WASM 是專門工具,不是萬用刀。它最有價值的地方是:算得重、要跨環境、要隔離、要重用既有 C/Rust 程式。這些條件只要中一兩個,就值得認真評估;如果都沒有,那我通常會勸自己冷靜一點。

我自己的習慣是先問三個問題:這裡真的有瓶頸嗎?這段邏輯真的要跑在多個 runtime 嗎?sandbox 真的有幫助嗎?三題都答不出來,我就不搬。這樣可以少掉很多不必要的架構成本,也比較不會在半年後回頭罵自己。

實操寫法:把 WASM 留給 image transform、compression、parser、crypto、simulation、inference 這類工作。UI、DOM、網路協調、狀態流轉,留在 host 語言就好。這種分工通常是最穩的,也最不容易讓團隊失控。

我也會提醒自己一件事:越是想把所有東西都塞進 WASM,越容易忽略 host 的優勢。你不是在重寫世界,你只是在挑一塊最值得搬的地方。

可抄的模板

# WASM adoption template for 2026

## 我什麼時候該用 WASM
- 我已經量到明確的 CPU 瓶頸。
- 同一段邏輯要跑 browser、edge、server,或 plugin host。
- 我需要 sandbox untrusted code。
- 我想重用既有的 Rust / C / C++ 核心邏輯。

## 我什麼時候不該用 WASM
- 功能主要是 UI、DOM、state orchestration。
- 問題本質是 I/O,不是運算。
- 這段程式強依賴 host framework。
- 團隊沒有餘裕多養一層 build 與 runtime boundary。

## 預設技術組合
- Authoring language: Rust
- Browser packaging: wasm-bindgen + wasm-pack
- Runtime: Wasmtime / Node.js / Deno / Bun / host-native WASM support
- System interface: WASI
- Plugin layer: Extism

## 專案結構
my-wasm-project/
  src/
    lib.rs
  pkg/
  Cargo.toml
  README.md

## 最小 Rust module
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn process_data(input: &str) -> String {
    let reversed: String = input.chars().rev().collect();
    format!("Processed: {}", reversed)
}

#[wasm_bindgen]
pub fn calculate_stats(numbers: &[f64]) -> JsValue {
    let sum: f64 = numbers.iter().sum();
    let count = numbers.len() as f64;
    let mean = if count > 0.0 { sum / count } else { 0.0 };

    serde_wasm_bindgen::to_value(&serde_json::json!({
        "sum": sum,
        "mean": mean,
        "count": count
    }))
    .unwrap()
}

## Build commands
rustup target add wasm32-wasip1
cargo install wasm-pack
cargo add wasm-bindgen serde serde_json --target wasm32-wasip1
wasm-pack build --target web --release
wasm-pack build --target nodejs --release

## Browser usage
import init, { process_data, calculate_stats } from './pkg/my_wasm_project.js';

await init();

const result = process_data('Hello WebAssembly!');
const stats = calculate_stats([1,2,3,4,5,6,7,8,9,10]);

console.log(result);
console.log(stats.mean);

## Host integration checklist
- 保持 WASM API 小而明確。
- boundary 只傳 plain data。
- 量測序列化與呼叫成本。
- DOM 與 UI 留在 host。
- module 版本與 host app 分開管理。
- module 與 host adapter 都要測。

## Plugin checklist
- 預設把 plugin 當 untrusted。
- 只暴露必要 functions。
- 不要讓 plugin 直接碰 host internals。
- 在 boundary 驗證輸入。
- 回傳 plain JSON 或 typed values。

## 決策規則
如果程式是 compute-heavy、portable、sandbox-worthy,就搬到 WASM。
如果程式主要是 UI、orchestration、network chatter,就留在 host。

這份我自己會真的拿去改。它沒有裝神秘,也沒有把 WASM 說成萬靈丹。重點就是幫你少踩幾個我踩過的坑:別過度使用、別把邊界搞太肥、別假裝 runtime 不存在。

我這篇的拆解來源是 ZNY 的原文。上面的架構、判斷方式、可抄模板是我重新整理過的版本;原始觀點有借力,但寫法和落點是我自己的。