瞭解如何使用最新的 WebAssembly 功能,同時支援所有瀏覽器的使用者。
WebAssembly 1.0 雖然四年前推出,但開發並未停下腳步。透過提案標準化程序加入新功能。如果是在網路上推出新功能的情況,不同引擎的導入順序和時程也可能有顯著差異。如要使用這些新功能,請務必確保沒有使用者會流失。本文將說明達成這個目標的方法。
部分新功能可增加常見作業的新指示,藉此縮減程式碼大小,並加入強大的效能基元,以及改善開發人員體驗,並與其他網路整合。
您可以在官方存放區查看提案的完整清單和相應階段,也可以前往官方的功能藍圖頁面,在引擎中追蹤提案的實作狀態。
如要確保所有瀏覽器的使用者都能使用您的應用程式,您必須找出要使用的功能。然後根據瀏覽器支援功能分成不同群組。然後分別編譯每個群組的程式碼集。最後,您必須在瀏覽器端偵測支援的功能,並載入對應的 JavaScript 和 Wasm 套件。
選擇和分組特徵
以下將挑選任意功能集做為範例,逐步說明這些步驟。假設您是基於大小和效能考量,決定在程式庫中使用 SIMD、執行緒和例外狀況處理功能。以下是該公司的瀏覽器支援:
您可以將瀏覽器分成下列同類群組,以確保每位使用者都能享有最優質的體驗:
- Chrome 式瀏覽器:支援執行緒、SIM 卡和例外狀況處理功能。
- Firefox:支援 Thread 和 SIMD,不支援例外狀況處理。
- Safari:支援執行緒,不支援 SIMD 和例外狀況處理。
- 其他瀏覽器:僅假設 WebAssembly 支援基準。
這項細目會根據每個瀏覽器最新版本的功能支援細分。新世代瀏覽器不會退流行,且會自動更新,因此在大多數情況下,您只需擔心最新版本的瀏覽器。不過,只要納入基準 WebAssembly 做為備用同類群組,即可提供正常運作的應用程式,就算使用者使用過舊的瀏覽器也一樣。
編譯不同的特徵集
WebAssembly 的內建功能無法偵測執行階段中支援的功能,因此模組中的所有指示都必須在目標上獲得支援。因此,您必須針對每個不同的功能集,分別將原始碼編譯到 Wasm 中。
每個工具鍊和建構系統都不相同,您必須參閱自己的編譯器的說明文件,瞭解如何調整這些功能。為求簡單起見,我會在以下範例中使用單一檔案 C++ 程式庫,並示範如何使用 Emscripten 進行編譯。
我將透過 SSE2 模擬功能使用 SIMD、透過 Pthread 程式庫支援的執行緒,然後選擇 Wasm 例外狀況處理或 備用 JavaScript 實作:
# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions
C++ 程式碼本身可以使用 #ifdef __EMSCRIPTEN_PTHREADS__
和 #ifdef __SSE2__
,在編譯期間有條件地選擇相同函式的平行 (執行緒和 SIMD) 實作項目。程式碼應會如下所示:
void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
// …implementation using threads and SIMD for max speed
#else
// …implementation using threads but not SIMD
#endif
#else
// …fallback implementation for browsers without those features
#endif
}
例外狀況處理作業不需要使用 #ifdef
指令,因為無論透過編譯旗標選擇的基礎實作方式為何,都可以透過 C++ 以相同方式使用例外狀況處理功能。
正在載入正確的套件
為所有功能同類群組建立套裝組合後,您必須從主要的 JavaScript 應用程式載入正確的套件。為此,請先偵測目前瀏覽器支援的功能。您可以使用 wasm-feature-detect 程式庫來進行這項操作。只要結合動態匯入功能,即可在任何瀏覽器中載入最佳化程度最高的套件:
import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';
let initModule;
if (await threads()) {
if (await simd()) {
if (await exceptions()) {
initModule = import('./main.threads-simd-exceptions.mjs');
} else {
initModule = import('./main.threads-simd.mjs');
}
} else {
initModule = import('./main.threads.mjs');
}
} else {
initModule = import('./main.basic.mjs');
}
const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would
最後幾個字
這篇文章將說明如何選擇、建構及切換不同功能集的套件。
隨著功能數量增加,特徵同類群組的數量可能會無法維護。為減輕這個問題的影響,您可以根據實際使用者資料選擇功能同類群組,略過較不熱門的瀏覽器,改為使用較不理想的同類群組。只要應用程式仍能供所有使用者使用,這種方法就能在漸進強化與執行階段效能之間提供合理的平衡。
在未來,WebAssembly 可能會取得內建方法來偵測支援的功能,並在模組中切換相同函式的不同實作。不過,這類機製本身就是一種 MVP 後的功能,您需要使用上述方法有條件地偵測及載入。在此之前,在所有瀏覽器中使用新的 WebAssembly 功能建構及載入程式碼時,仍只能使用這個方法。