瞭解如何使用最新的 WebAssembly 功能,同時支援各種瀏覽器的使用者。
WebAssembly 1.0 已於 4 年前推出,但開發並未止步於此。我們會透過提案標準化程序加入新功能。和一般的網頁一樣,網頁版新功能就是這樣,每種引擎的導入順序和時程可能有明顯差異。如要使用這些新功能,您必須確保所有使用者都不會遺漏任何使用者。本文將說明如何達成這個目標。
有些新功能可新增常用作業的操作說明,藉此改善程式碼大小,有些則新增強大的效能基本功能,其他部分則能改善開發人員體驗,並與網路上的其他內容整合。
您可以在官方存放區中查看完整的提案清單和每個階段的階段,或是前往官方的功能藍圖網頁,追蹤提案在引擎中的實作狀態。
為確保所有瀏覽器的使用者都能使用您的應用程式,您必須找出要使用的功能。然後根據瀏覽器支援的內容分組。然後針對這些群組分別編譯程式碼集。最後,您必須在瀏覽器端偵測支援的功能,並載入對應的 JavaScript 和 Wasm 套件。
挑選及分組功能
讓我們一起挑選任意功能組合做為範例,逐步說明這些步驟。假設我就大小和效能考量,希望在程式庫中使用 SIMD、執行緒和例外狀況處理。他們的瀏覽器支援如下:
您可以將瀏覽器分為下列同類群組,確保每位使用者都能獲得最佳體驗:
- Chrome 瀏覽器:支援 Threads、SIMD 和例外狀況處理。
- Firefox:支援 Thread 和 SIMD,不支援例外狀況處理。
- Safari:支援執行緒,不支援 SIMD 和例外狀況處理。
- 其他瀏覽器:假設只支援基準 WebAssembly 支援。
此細目會根據各瀏覽器的最新版本支援的功能支援細分資料。新式瀏覽器會不斷更新且會自動更新,在大多數情況下,您只需考慮最新版本。不過,只要您將基準 WebAssembly 新增為備用同類群組,即使瀏覽器版本過舊,您還是可以提供可正常運作的應用程式。
編譯不同的功能集
WebAssembly 沒有內建在執行階段中偵測支援的功能的方式,因此模組中的所有指示都必須支援目標。因此,您必須針對各個不同的功能集,將原始碼分別編譯至 Wasm。
每個工具鍊和建構系統都不相同,您必須參閱自己的編譯器的說明文件,瞭解如何調整這些功能。為了方便起見,以下範例將使用單一檔案 C++ 程式庫,示範如何使用 Emscripten 進行編譯。
我將透過 SSE2 模擬使用 SIMD,透過 Pthreads 程式庫支援使用執行緒,並選擇 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 程式庫來執行此操作。結合這個 API 與動態匯入功能,您就能在任何瀏覽器中載入成效最佳的套件:
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 功能來建構及載入程式碼。