Emscripten + Vite で Worker を使ったサービスを作ると、リリースビルドできない問題が発生することがあります。大変分かりにくいので、原因と対策のメモです。
確認したバージョン: Vite:4.3.4, Emscripten:3.1.34
背景
Viteから参照するnpmパッケージが commonjs 形式だと、開発中にパッケージをローカル参照(file:..やnpm link)した際に正しく動きません。
オプションで解決する方法もあるのですが副作用があるため、できれば参照するライブラリは module 形式にしたいです。
Emscriptenでも module 形式のライブラリを生成することは可能で、EXPORT_ES6=1オプションを付けてビルドします。こうして作ったライブラリは Vite での開発中に npm run dev している限りは、メインでも Worker でもまったく問題なく利用できます。
問題
しかし、Emscripten で作ったライブラリを Worker から使った場合、npm run build ができません。
具体的には、まず、次のような警告が出ます。
[plugin:vite:resolve] Module "module" has been externalized for browser compatibility, imported by "/Users/username/dev/myapp/node_modules/mylib/dist/mymodule.js"
さらに、次のエラーでビルドが失敗します。
RollupError: Invalid value "iife" for option "output.format" - UMD and IIFE output formats are not supported for code-splitting builds.
原因
まず ES6_MODULE=1 付きでビルドした場合、生成されるコードに await import
が含まれていることが要因です。
... if(ENVIRONMENT_IS_NODE){const{createRequire:createRequire}=await import("module"); ...
実はこれ、node で実行した場合に通るパスなので、ブラウザではデッドコードであり、実行されません。しかし、そんなことは Vite のバンドラ(Rollup)には分かりません。Rollup はここで発見した await import
先のパッケージをコードスプリット対象にして、警告を出力しています。
これだけなら警告だけで済むのですが、問題になるのは Worker で import していることです。
Vite は Worker のビルド形式をデフォルトで iife にしていますが、iife 形式はコードスプリットに対応していません。むしろコードスプリットしないためのビルド形式です。
このため、コードスプリットしたい Rollup と、そうさせたくない Vite 本体の設定が矛盾して、ビルドエラーとなります。
対策
対策1: 最も簡単な対策(FireFox非対応)
Worker で import が使えるブラウザであれば、Vite のコンフィグで Worker ビルド時のモジュール形式を es module にすることで解決できます。設定値としては次のように es
を指定します。
export default defineConfig({
plugins: [react()],
worker: {
format: 'es', // ←これを追加
}
});
この対策は簡単ですが、現時点では FireFox が Worker で import をサポートしていないため、FireFox では動作しません。
対策2: 諦めて CommonJS 形式に戻す
諦めて Emscripten のビルド形式を commonjs に戻します。
冒頭で説明した、commonjs のパッケージをローカル参照した場合のエラーは Vite の設定で、resolve.preserveSymlinks オプションを有効にすれば、回避できます。
ただし、ローカル参照したパッケージのソースの動的変更に追従できなくなります。
コードを変更した場合は、キャッシュを消してビルド(npm run dev -- --force)が必要になります。
上記以外のトラブルもあるかもしれませんが、手元では今のところ遭遇していません。
対策3 Emscripten 側で ENVIRONMENT=web オプションを付けてビルドする
Emscriptenでのビルド時に、-sENVIRONMENT=web オプションを付けると、上記の await import を含むコードが削除されるため、問題が発生しなくなります。同じコードを node.jsでも使いたい場合は、node 用のビルドが必要になってしまう可能性はありますが、node 専用の機能(NODEFSとか)を使っていなければ、web 用にビルドしたものも普通に node で動かせるので、悪くない対策だと思います。
おわりに
Vite + Emscripten + Worker + AudioWorklet の組み合わせで使っています。開発中は簡単に動くなぁという感じだったのですが、リリースビルドするとこのようにまだ一癖ある感じです。FireFox もそろそろ正式に Worker での import をサポートしてほしいところですね。