esbuildでWebAssemblyを無理矢理埋め込む
公開日:
カテゴリ: Javascript
ちょっと思い立ってやってみたくなったのでやってみた。
TL;DR
- esbuildの
loader
でwasmファイルをバイナリとして埋め込むことが可能 - emscripten製のwasmは呼び出し時の引数の
wasmBinary
プロパティにwasm自体のArrayBuffer
を入れることで無理矢理動かせる - 基本的にメリットは少ない
- 1ファイルにバンドルすることでローカル実行時にCORSに引っかからない利点はある
.wasm
ファイルがBase64形式でバンドルされるため、ファイルサイズは通常よりも大幅に増加する
esbuildでwasmをバンドルする
esbuildではloader
オプションを使用することで、各種ファイルをimport
でロードすることができます。
今回は.wasm
ファイルをbinary
として読み込むように設定します。binary
として読み込んだファイルはimport
するとUint8Array
として扱うことができます。
{
bundle: true,
loader: {
".wasm": "binary"
}
}
読み込む側は以下のようなコードになります。
import wasm_binary from "./example.wasm";
WebAssembly.instantiate(wasm_binary.buffer).then(results => {
// 処理
});
(オプション)型定義
TypeScriptでimportするにはwasmの型定義が必要です。
declare module "*.wasm" {
export default Uint8Array;
}
Emscripten製のwasmをバンドルする
自分で作成したwasmファイルであればここまでの方法で解決しますが、世の中の多くのWebAssemblyはEmscriptenで作成されており、wasmをロードするJavaScriptまでがセットでビルドされます。
このビルド成果物は一般的なwasmと同様にfetch()
でwasmファイルをロードするので、そのままではバンドルできません。かといって、このJavaScriptを使用せずに実装するのは難しめです。
ということで、色々いじって無理矢理バンドルさせてみます。
無理矢理ロードさせる
今回は@sqlite.org/sqlite-wasm
をバンドルしてみます。
emscriptenでビルドされたwasmは、設定オブジェクトのwasmBinary
プロパティからwasmをロードしています。wasmBinary
プロパティが無い場合はwasmファイルをfetch()
しに行くので、ArrayBuffer
を無理矢理差し込んでしまうという魂胆です。
wasmBinary
は内部用のプロパティで、大抵の場合は型定義ファイルには存在しないため、TypeScriptでコーディングする場合は自力で型定義ファイルを書くか// @ts-ignore
で型チェックを強制的に対象外にする必要があります。
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
import sqlite_wasm from "@sqlite.org/sqlite-wasm/sqlite3.wasm";
sqlite3InitModule({
// @ts-ignore
wasmBinary: sqlite_wasm.buffer
}).then(sqlite => {
// ここに処理
});
import.metaの対策
esbuildでバンドルすると、import.meta
が空オブジェクトになってしまうので、適当な文字列に置換します。あまりこのような書き換えはしたくないですが、どうにもならなさそうなので今回は目を瞑ります。
{
bundle: true,
loader: {
".wasm": "binary"
},
define: {
"import.meta.url": "location.href"
}
}
結論
非公開のプロパティを使用しているので、将来のアップデートでは使えなくなる可能性が高いです。また既存コードのimport.meta.url
を置き換えたりなど、かなり無理をした対応をしているので、保守性はかなり悪めです。
esbuildのバイナリローダーは、JSファイルにバイナリファイルをバンドルする都合上base64
形式でファイルをバンドルするため、ファイルサイズは大きく増大します。小さなwasmファイルであればそれほど気になりませんが、大抵はそれなりのファイルサイズがあることが多いので、転送量は無視できないくらい大きくなりそうです(base64
は8bitで1Byte→6bitで1Byteに変換するため約1.3倍)。
メリットとしてはfetch()
無しでwasmを使えるため、CORSに引っかからないことでしょうか。ローカルにサーバを立てずにHTMLファイルだけで完結するアプリを作成できそうです。とても限定的ですが、使い方によってはありかも・・・?
カテゴリ: Javascript