WebAssembly 如何重塑瀏覽器端運算:從 FFmpeg.wasm 到 OxiPNG 的技術革命

技术架构(更新於 2026年5月15日)

瀏覽器端運算的困境

長期以來,JavaScript 是瀏覽器中唯一的程式語言。對於運算密集型任務,JavaScript 的效能瓶頸十分明顯:

  • 影片轉碼:1080p 影片轉碼需要數十億次運算
  • 圖片壓縮:PNG 無損最佳化涉及複雜的 DEFLATE + 熵編碼
  • OCR 識別:Tesseract 需要 LSTM 神經網路推論
  • 加密運算:RSA/SM2 等公鑰加密需要大數運算

純 JavaScript 實作這些功能,效能通常比原生程式碼慢 10-100 倍


WebAssembly:瀏覽器的「第二引擎」

核心概念

WebAssembly(Wasm)是一種低階二進位指令格式,設計為堆疊式虛擬機的編譯目標。它的核心價值:

C/C++/Rust/Zig 原始碼
     ↓ 編譯
Wasm 二進位 (.wasm)
     ↓ 下載到瀏覽器
Wasm 執行環境解碼 + 執行
     ↓ 沙箱中執行
接近原生的執行速度

效能對比

基準測試 JavaScript WebAssembly 倍率
Fibonacci(45) 8.2s 1.1s 7.5x
影像高斯模糊 320ms 45ms 7.1x
PDF 解析 580ms 95ms 6.1x
正規表示式引擎 210ms 38ms 5.5x

資料基於 Chrome 120,M2 MacBook Pro。實際倍率受記憶體配置、GC 干擾等因素影響。

關鍵技術特性

  1. 線性記憶體模型:Wasm 使用 ArrayBuffer 作為線性記憶體,JS 和 Wasm 共享同一塊記憶體,零拷貝傳遞資料
  2. 無 GC 停頓:Wasm 沒有垃圾回收,適合長時間運算不被中斷
  3. 沙箱隔離:Wasm 程式碼只能存取明確匯入的函式與記憶體,天然安全
  4. 串流編譯:瀏覽器可以在下載 Wasm 二進位的同時進行編譯

實戰案例一:FFmpeg.wasm 影片轉碼

工具庫的影片工具(格式轉換、剪輯、壓縮、轉 GIF)全部基於 FFmpeg.wasm。

架構設計

使用者選擇影片檔案
     ↓
FileReader 讀取到 ArrayBuffer
     ↓
ffmpeg.writeFile('input.mp4', arrayBuffer)  ← 寫入 Wasm 虛擬檔案系統
     ↓
ffmpeg.exec('-i input.mp4 -c:v libx264 output.mp4')  ← 執行轉碼
     ↓
ffmpeg.readFile('output.mp4')  ← 讀取結果
     ↓
Blob → URL.createObjectURL → 下載

核心程式碼解析

import { FFmpeg } from '@ffmpeg/ffmpeg';
import { toBlobURL } from '@ffmpeg/util';

const ffmpeg = new FFmpeg();

// 載入 Wasm 核心(從 CDN 串流下載)
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
await ffmpeg.load({
  coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
  wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});

// 寫入輸入檔案
await ffmpeg.writeFile('input.mp4', await fetchFile(file));

// 執行轉碼
await ffmpeg.exec(['-i', 'input.mp4', '-c:v', 'libx264', '-preset', 'fast', 'output.mp4']);

// 讀取輸出
const data = await ffmpeg.readFile('output.mp4');

效能表現

操作 輸入大小 耗時 對比桌面 FFmpeg
MP4 → WebM 50MB ~45s ~8s (5.6x)
影片剪輯 50MB ~12s ~3s (4x)
轉 GIF 10MB ~25s ~4s (6.3x)
擷取音訊 50MB ~8s ~2s (4x)

瀏覽器端 Wasm 比桌面原生慢 4-6 倍,但考慮到零上傳零安裝的優勢,這個折衷完全值得。

技術挑戰

  1. SharedArrayBuffer 限制:FFmpeg.wasm 多執行緒版本需要 Cross-Origin-Isolation 標頭,部分 CDN 不支援
  2. 記憶體佔用:處理大影片時 Wasm 線性記憶體可達數百 MB,需要關注 OOM
  3. 首屏載入:ffmpeg-core.wasm 約 30MB,首次載入較慢(後續瀏覽器快取)

實戰案例二:OxiPNG PNG 無損壓縮

工具庫的 PNG 壓縮使用 OxiPNG,這是用 Rust 撰寫的高效能 PNG 最佳化器,編譯為 Wasm 執行。

為什麼選擇 Rust + Wasm?

  • 零開銷抽象:Rust 的所有權系統 + Wasm 的線性記憶體,無 GC 開銷
  • 小體積:Rust → Wasm 不需要帶執行環境(不像 Go 的 Wasm 需要 8MB runtime)
  • 記憶體安全:Rust 編譯時保證無記憶體洩漏、無緩衝區溢位

@jsquash/oxipng 整合

import { oxipng } from '@jsquash/oxipng';

// 讀取 PNG 檔案為 ArrayBuffer
const inputBuffer = await file.arrayBuffer();

// 執行無損最佳化(OxiPNG Wasm 內部執行)
const outputBuffer = await oxipng(inputBuffer, {
  level: 2,        // 最佳化級別 0-6
  interlace: false, // 是否交錯
});

// outputBuffer 是最佳化後的 PNG
const blob = new Blob([outputBuffer], { type: 'image/png' });

壓縮效果

圖片類型 原始大小 最佳化後 減少比例 耗時
螢幕截圖 (1920x1080) 2.1 MB 1.5 MB 29% 0.3s
UI 圖示 (256x256) 45 KB 28 KB 38% 0.05s
照片 (PNG) 5.2 MB 4.1 MB 21% 0.8s
帶透明通道 800 KB 520 KB 35% 0.15s

Wasm 的侷限與因應

侷限 1:DOM 存取

Wasm 不能直接操作 DOM。必須透過 JS 橋接:

Wasm 運算 → JS 膠水層 → DOM 更新

因應:將運算密集部分放在 Wasm,UI 互動部分放在 JS,各司其職。

侷限 2:首屏載入

大型 Wasm 檔案(如 ffmpeg-core.wasm 30MB)首次載入慢。

因應

  • 使用 Streaming compilation:邊下載邊編譯
  • 按需載入:只有進入影片工具頁才載入 FFmpeg Wasm
  • Cache-Control 長快取:Wasm 檔案穩定,可快取 1 年

侷限 3:除錯困難

Wasm 二進位難以直接除錯。

因應

  • 開發時產生 DWARF 除錯資訊
  • 使用 Chrome DevTools 的 Wasm 除錯支援
  • 核心邏輯在 Rust/C 側單元測試,Wasm 側只做膠水

工具庫的 Wasm 技術棧總結

功能 Wasm 函式庫 源語言 Wasm 大小 效能
影片轉碼 FFmpeg.wasm C ~30MB 桌面的 1/5
PNG 壓縮 OxiPNG Rust ~1.2MB 接近桌面
OCR 識別 Tesseract.js C++ ~2.5MB 可用
PDF 渲染 pdfjs-dist C++ (已編譯) ~2MB 流暢

所有 Wasm 模組均按需懶載入,不影響其他工具頁的載入速度。


未來展望

  1. Wasm GC:即將標準化,支援 Java/Kotlin/Go 直接編譯到 Wasm
  2. Wasm Components:模組化 Wasm,可像 npm 套件一樣複用
  3. WASI:WebAssembly System Interface,讓 Wasm 在伺服器端也能執行
  4. Threads + SIMD:多執行緒 + 向量指令,效能逼近原生

WebAssembly 正在重新定義瀏覽器端運算的邊界。工具庫將繼續深耕 Wasm 技術棧,將更多桌面級能力帶到瀏覽器中——無需安裝、無需上傳、保護隱私。

本站提供瀏覽器本地工具,免註冊即可試用 →

#WebAssembly#FFmpeg#Rust#性能#WASM