如何將 WebAssembly 整合至這項設定中?在本文中,我們會以 C/C++ 和 Emscripten 做為範例。
WebAssembly (wasm) 通常經常出現 設計成效能入門版本,或執行現有 C++ 的方法 網路程式碼集我們希望透過 squoosh.app 至少還有第三種觀點 生態系統取代為 Emscripten 可用於使用 C/C++ 程式碼。 內建的 Rust 支援以及 Go 團隊也會著手處理我 也有許多語言會跟著推出
在這些情境中,不是應用程式的核心,而是謎題 另一個單元您的應用程式含有 JavaScript、CSS、圖片素材資源、 以網站為中心的建構系統,甚至是 React 這類架構。如何 要將 WebAssembly 整合至這個設定中嗎?本文將進行這項操作 C/C++ 和 Emscripten 以做為範例。
Docker
我發現 Docker 在使用 Emscripten 時非常寶貴。C 與 C++ 通常編寫的程式庫可與建構的作業系統搭配使用。 能有一致的環境也非常有幫助。Docker 可讓您 可與 Emscripten 搭配使用的虛擬化 Linux 系統,並且 所有工具和依附元件的安裝作業如果缺少任何項目 安裝程式,不必擔心它會如何影響自己的機器或 和其他專案若發生問題,請丟棄容器並開始啟動 。如果這個方法只運作一次,就能確保系統能繼續運作 會產生相同的結果
Docker Registry 有 Emscripten 圖片,者: trzeci 是我廣泛使用的產品。
與 npm 整合
在大多數情況下,網路專案的進入點為 npm 的
package.json
。按照慣例,大多數專案都能使用 npm install &&
npm run build
建構。
一般來說,由 Emscripten 產生的建構構件 (.js
和 .wasm
)
檔案) 應視為另一個 JavaScript 模組,
資產。JavaScript 檔案可透過 webpack 或 Rollup 等整合工具處理、
和 wasm 檔案應視為任何其他較大的二進位資產,例如
所以映像檔較小
因此,您必須在「正常」工作之前建立 Emscripten 版本構件。 建構程序開始於:
{
"name": "my-worldchanging-project",
"scripts": {
"build:emscripten": "docker run --rm -v $(pwd):/src trzeci/emscripten
./build.sh",
"build:app": "<the old build command>",
"build": "npm run build:emscripten && npm run build:app",
// ...
},
// ...
}
新的 build:emscripten
工作可以直接叫用 Emscripten,但如
之前所述,建議您使用 Docker 確保建構環境是在
保持一致
docker run ... trzeci/emscripten ./build.sh
會要求 Docker 啟動新的
容器中執行 trzeci/emscripten
映像檔,然後執行 ./build.sh
指令。
build.sh
是您接下來要編寫的殼層指令碼!--rm
分享
Docker 會在容器執行完畢後刪除該容器。這樣一來
持續收集到一組過時的機器映像檔-v $(pwd):/src
表示
您希望 Docker 「鏡像」目前目錄 ($(pwd)
) 到 /src
中的
您對目錄內 /src
目錄中的檔案所做的任何變更
就會反映到實際專案這些鏡像的目錄
稱為「繫結掛接」
讓我們看看 build.sh
:
#!/bin/bash
set -e
export OPTIMIZE="-Os"
export LDFLAGS="${OPTIMIZE}"
export CFLAGS="${OPTIMIZE}"
export CXXFLAGS="${OPTIMIZE}"
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
(
# Compile C/C++ code
emcc \
${OPTIMIZE} \
--bind \
-s STRICT=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s MALLOC=emmalloc \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-o ./my-module.js \
src/my-module.cpp
# Create output folder
mkdir -p dist
# Move artifacts
mv my-module.{js,wasm} dist
)
echo "============================================="
echo "Compiling wasm bindings done"
echo "============================================="
這裡有許多需要深入剖析的項目!
set -e
可讓殼層進入「失敗快速」模式。如果指令碼中的任何指令
傳回錯誤,就會立即取消整個指令碼。可用的值包括
非常實用,因為指令碼的最後輸出
永遠都是成功的
訊息或導致建構失敗的錯誤。
使用 export
陳述式,您可以定義幾個環境的值
變數。這樣您就能將額外的指令列參數傳送給 C
編譯器 (CFLAGS
)、C++ 編譯器 (CXXFLAGS
) 和連接器 (LDFLAGS
)。
所有人都會透過「OPTIMIZE
」接收最佳化工具設定,以確保
系統會一律以相同方式最佳化所有內容有幾個可能的值
用於 OPTIMIZE
變數:
-O0
:不進行任何最佳化作業。不會刪除無效程式碼,且 Emscripten 也不會壓縮其產生的 JavaScript 程式碼。適合偵錯。-O3
:積極進行最佳化以提升成效。-Os
:以次要方式為效能和大小積極進行最佳化 條件。-Oz
:針對大小積極最佳化,視需要犧牲效能。
關於網頁版,我通常會建議使用 -Os
。
emcc
指令有好幾種選項。請注意,密件副本是
應該是「GCC 或 clang 等編譯器的直接替換」所有
你可能知道的 GCC 最可能是
-s
標記的特別之處,在於讓我們設定 Emscripten
。你可以在 Emscripten 的
settings.js
、
但這個檔案可能會顯得不堪負荷以下是 Emscripten 標記的清單
我認為對網頁程式開發人員來說最重要的是:
--bind
啟用 embind:-s STRICT=1
停止支援所有已淘汰的建構選項。這可以確保 能以前瞻相容性的方式建構程式碼-s ALLOW_MEMORY_GROWTH=1
允許在下列情況下自動增加記憶體: 無從得知撰寫文字時,Emscripten 會分配 16 MB 的記憶體 最初當程式碼配置了記憶體區塊時,這個選項會判斷 這些作業會導致整個 wasm 模組失敗 或黏土程式碼已用盡,或允許將記憶體總量擴大至 也就是系統必須因應配置 才能自動調整資源配置-s MALLOC=...
會選擇要使用哪個malloc()
實作項目。emmalloc
是 專為 Emscripten 而可快速執行的小型malloc()
實作。 其次是dlmalloc
,這是一種完善的malloc()
實作方式。您只需要 如要分配大量小型物件,則需要切換至dlmalloc
或您想使用執行緒-s EXPORT_ES6=1
會將 JavaScript 程式碼轉換為 ES6 模組,並加上 可與任何 Bundler 搭配使用的預設匯出功能一併規定-s MODULARIZE=1
才能 。
下列標記並非一律必要,或僅供偵錯時使用 用途:
-s FILESYSTEM=0
是與 Emscripten 相關的標記,可以 在 C/C++ 程式碼使用檔案系統作業時,為您模擬檔案系統。 它會對所編譯的程式碼執行一些分析,以判定是否要在系統中納入 會模擬 glue 程式碼中的檔案系統。不過,有時這個做法 如果分析結果出錯,您支付了高達 70 KB 的額外固體 執行不需要的檔案系統模擬程式碼。使用-s FILESYSTEM=0
時,你可以強制 Emscripten 不要加入這段程式碼。-g4
可讓 Emscripten 在.wasm
中加入偵錯資訊, 也會發出 wasm 模組的來源對應檔案如要瞭解詳情,請前往 使用 Emscripten 在偵錯 專區。
就是這樣!為測試這項設定,讓我們建立新的 my-module.cpp
:
#include <emscripten/bind.h>
using namespace emscripten;
int say_hello() {
printf("Hello from your wasm module\n");
return 0;
}
EMSCRIPTEN_BINDINGS(my_module) {
function("sayHello", &say_hello);
}
以及 index.html
:
<!doctype html>
<title>Emscripten + npm example</title>
Open the console to see the output from the wasm module.
<script type="module">
import wasmModule from "./my-module.js";
const instance = wasmModule({
onRuntimeInitialized() {
instance.sayHello();
}
});
</script>
(這是 gist 。)
如要建構所有內容,請執行
$ npm install
$ npm run build
$ npm run serve
前往 localhost:8080 應會顯示下列輸出內容 開發人員工具控制台:
![開發人員工具顯示透過 C++ 和 Emscripten 列印的訊息。](https://web.dev/static/articles/emscripten-npm/image/devtools-showing-message-6e40fef465a41.png?authuser=6&hl=zh-tw)
新增 C/C++ 程式碼做為依附元件
如要為網頁應用程式建構 C/C++ 程式庫,必須採用
您可以手動將程式碼新增至專案的存放區
或使用 npm 管理這些依附元件類型假設
想在我的網頁應用程式中使用 libvpx。libvpx
是透過 VP8 編碼圖片的 C++ 程式庫,VP8 是 .webm
檔案中所使用的轉碼器。
不過,libvpx 並非採用 npm 且沒有 package.json
,因此我無法
請直接使用 npm 進行安裝
可以解決這種難題
napa:napa 可讓您安裝
存放區網址做為 node_modules
資料夾中的依附元件。
安裝 napa 做為依附元件:
$ npm install --save napa
並確認將 napa
做為安裝指令碼執行:
{
// ...
"scripts": {
"install": "napa",
// ...
},
"napa": {
"libvpx": "git+https://github.com/webmproject/libvpx"
}
// ...
}
執行 npm install
時,napa 會負責複製 libvpx GitHub
在您的 node_modules
中建立名為 libvpx
的存放區。
您現在可以擴充建構指令碼來建構 libvpx。libvpx 使用 configure
和 make
。幸好,Escripten 可協助確保configure
和
make
使用 Emscripten 的編譯器。為此,我們使用包裝函式
指令 emconfigure
和 emmake
:
# ... above is unchanged ...
echo "============================================="
echo "Compiling libvpx"
echo "============================================="
(
rm -rf build-vpx || true
mkdir build-vpx
cd build-vpx
emconfigure ../node_modules/libvpx/configure \
--target=generic-gnu
emmake make
)
echo "============================================="
echo "Compiling libvpx done"
echo "============================================="
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
# ... below is unchanged ...
C/C++ 程式庫分為兩個部分:標頭 (傳統為 .h
或
.hpp
檔案),定義資料結構、類別、常數等。
程式庫會公開發布,以及實際的程式庫 (傳統的 .so
或 .a
檔案)。目的地:
請使用程式庫的 VPX_CODEC_ABI_VERSION
常數,
使用 #include
陳述式加入程式庫的標頭檔案:
#include "vpxenc.h"
#include <emscripten/bind.h>
int say_hello() {
printf("Hello from your wasm module with libvpx %d\n", VPX_CODEC_ABI_VERSION);
return 0;
}
問題在於編譯器不知道要尋找 vpxenc.h
的「位置」。
這就是 -I
標記的用途。會告知編譯器要對哪些目錄
檢查是否有標頭檔案此外,您還需要向編譯器提供
實際程式庫檔案:
# ... above is unchanged ...
echo "============================================="
echo "Compiling wasm bindings"
echo "============================================="
(
# Compile C/C++ code
emcc \
${OPTIMIZE} \
--bind \
-s STRICT=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s ASSERTIONS=0 \
-s MALLOC=emmalloc \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-o ./my-module.js \
-I ./node_modules/libvpx \
src/my-module.cpp \
build-vpx/libvpx.a
# ... below is unchanged ...
如果現在執行 npm run build
,您會發現該程序會建構新的 .js
及新的 .wasm
檔案,示範網頁會確實輸出常數:
![DevTools
透過文字框顯示 libvpx 的 ABI 版本。](https://web.dev/static/articles/emscripten-npm/image/devtools-showing-the-abi-ab9a0f24d2c5c.png?authuser=6&hl=zh-tw)
您也會發現建構程序花費的時間較長。理由
建構時間可能不同以 libvpx 為例
您每次執行應用程式時
都會為 VP8 和 VP9 編譯編碼器和解碼器
。即使只是
變更 my-module.cpp
需要很長的時間才能完成。如果能
隨後保留 libvpx 的建構構件
。
而其中一種做法就是使用環境變數。
# ... above is unchanged ...
eval $@
echo "============================================="
echo "Compiling libvpx"
echo "============================================="
test -n "$SKIP_LIBVPX" || (
rm -rf build-vpx || true
mkdir build-vpx
cd build-vpx
emconfigure ../node_modules/libvpx/configure \
--target=generic-gnu
emmake make
)
echo "============================================="
echo "Compiling libvpx done"
echo "============================================="
# ... below is unchanged ...
(您可在此查閱要點 。)
eval
指令可透過傳送參數來設定環境變數
傳送至建構指令碼如果發生下列情況,test
指令會略過建構 libvpx
$SKIP_LIBVPX
已設為任何值。
您現在可以編譯模組,但略過重新建構 libvpx:
$ npm run build:emscripten -- SKIP_LIBVPX=1
自訂建構環境
有時程式庫必須仰賴額外工具進行建構。如果這些依附元件
Docker 映像檔提供的建構環境缺少,您需要
自行新增舉例來說,假設您想建立
說明文件。Doxygen 不是
,但您可以使用 apt
安裝。
如果在「build.sh
」中這麼做,請重新下載再重新安裝
doxygen 即可。不僅如此
可免除不必要的麻煩,但也會妨礙您離線製作專案。
在這裡建構自己的 Docker 映像檔是很合理的。Docker 映像檔的建構基礎是
編寫說明建構步驟的 Dockerfile
。Dockerfile
而且強大的
指令,但大部分
你可以使用 FROM
、RUN
和 ADD
離開。在這種情況下:
FROM trzeci/emscripten
RUN apt-get update && \
apt-get install -qqy doxygen
透過 FROM
,您可以宣告要用來啟動範本的 Docker 映像檔
點。我選擇「trzeci/emscripten
」做為現有圖片
使用 RUN
即可指示 Docker 在
都會在 Docker 容器中執行現在這些指令對容器所做的變更
以及 Docker 映像檔確保您的 Docker 映像檔
執行build.sh
前,請務必調整你的 package.json
位元:
{
// ...
"scripts": {
"build:dockerimage": "docker image inspect -f '.' mydockerimage || docker build -t mydockerimage .",
"build:emscripten": "docker run --rm -v $(pwd):/src mydockerimage ./build.sh",
"build": "npm run build:dockerimage && npm run build:emscripten && npm run build:app",
// ...
},
// ...
}
(您可在此查閱要點 。)
這會建構 Docker 映像檔,但僅限尚未建構的映像檔。接著
一切都像之前一樣執行,但現在建構環境具有 doxygen
指令,系統將在建構 libvpx 的說明文件
結論
C/C++ 程式碼和 npm 並不令人意外,但你可以 同時搭配一些額外的工具和隔離機制 儲存空間抽象化機制類型這項設定並不適用於所有專案 您可以視需要調整。如果 敬請分享!
附錄:使用 Docker 映像檔層
另一種替代方法是使用 Docker 和 Docker 的智慧型快取方法。Docker 會逐步執行 Dockerfile 並將每個步驟的結果指派給自身專屬的圖片。這些中繼圖片 通常稱為「圖層」如果 Dockerfile 中的指令未變更,Docker 因此在重新建構 Dockerfile 時不會實際重新執行該步驟。改為 並重複使用上次建構映像檔時建立的圖層
先前您必須花些功夫,不要每次都重新建構 libvpx
建構應用程式您可以改為移動 libvpx 的建構指示
從 build.sh
到 Dockerfile
,以便使用 Docker 的快取
機制:
FROM trzeci/emscripten
RUN apt-get update && \
apt-get install -qqy doxygen git && \
mkdir -p /opt/libvpx/build && \
git clone https://github.com/webmproject/libvpx /opt/libvpx/src
RUN cd /opt/libvpx/build && \
emconfigure ../src/configure --target=generic-gnu && \
emmake make
(您可在此查閱要點 。)
請注意,您需要手動安裝 Git 並複製 libvpx
執行 docker build
時繫結掛接。基本上,您不需要使用
小睡片刻。