Flutter WebのWasmバイナリをFirebase Hostingで動かしてみた
CHANGELOG
- 2024.05.14
- Google I/O 2024にてFlutter stable 3.22リリースとともにWasmサポートもStableとなりました。
背景
2023年5月10日の Google I/O 2023 にて、Flutter 3.10/Dart 3 の正式リリースが発表されたのと同時に、Flutter の Web Assembly(以下、Wasm)対応もプレビュー版ですが公開されました。
Flutter の Wasm に関するドキュメントは「Support for WebAssembly (Wasm)」というタイトルで、今年の3月辺りから少しずつ更新されておりましたが、今回からビルド方法などの手順が詳細に記載されるようになりました。
こちらは Flutter 3.10 における Flutter Web のアップデート内容の紹介ですが、Wasm は 「What's next for Web」 というセクションで紹介されています。現在の Flutter Web サポートの全体像が見えるので非常にオススメな動画です。
今回は、この Flutter Web の Wasm ビルドを Firebase Hosting 上で動かしてみて、CanvasKit や HTML レンダラーの従来形式と比較しながら、その特徴について気づいたことなど記載します。
Dart と Wasm
これまで、Wasm バイナリにコンパイルできる言語は C や Rust のようなガベージコレクション(GC)を利用しない低級言語のみであり、GC を要する言語についても プロポーザル などで標準化に向けた仕様策定が進められていました。
その後、今年1月末に開催された Flutter Forward 2023 にて、Flutter/Dart の Wasm サポートが発表され、GC を必要とする言語での初のサポートとなりました。これには Chromium V8 の開発チームとの連携などが不可欠で、Chrome の開発チームと協力して開発したことでいち早いサポートができたのだと思っています。現在、この WasmGC は Google Chrome や Firefox で安定版として提供されており、Safariも今年の4月に開発着手 しており、各ブラウザが出揃ってきた感が伺えます。
Flutter/Dart/WasmGC の詳細について今回は割愛しますが、今年3月に開催された WASM I/O で説明されているので参考にしてみると良いと思います(Flutter, Dart, Wasm-GC - Wasm I/O Conference - March 23, 2023 - [shared publicly] - Google スライド)。
なにが嬉しいのか
Wasm 対応する一般的なメリットは処理の高速化です。本来 JavaScript で処理しないといけなかったものがバイナリで処理されるため、それだけでもイメージしやすいと思います。身近な例では、Google Meetsの背景ぼかし処理 や Figmaの読み込み速度3倍 などが挙げられます。
最近公開された Tim 氏によるこちらの記事においても、「初期のベンチマークでは、実行速度が 3 倍ほど向上」と記載されており、高い性能で処理できていることが伺えます。
In some early benchmarks, we’ve seen a boost of 3× for execution speeds, which translates into yet richer web-based experiences. And Wasm couples this with easier integration with code written in other languages like Kotlin and C++.
またユーザビリティ観点では、Wasm はネイティブコードに近い性能なよりリッチで快適なウェブベースの体験を提供してくれると記載されており、これまで Flutter のビルド対象となる Platform の中で個人的に唯一微妙だなと感じていた Web アプリが、Wasm によって一気に普及しそうだなと思っています。
“WebAssembly excites us with its potential to bring the performance of native code to the web.”
Wasm にビルドする
ビルドは至って簡単です。以上です。
flutter build web --wasm
ただし、実行するには少し制約があります。
端的に言うと「最新の master
チャンネル」と「最新の Google Chrome ブラウザ」が必要です。
注意事項
master
チャンネルのみ- Google Chrome(Chromium ベースのブラウザ)のみで、Version 112 以降のみが対応
- Google Chrome ブラウザの以下 2 つの flags(
chrome://flags/
)の有効化が必要enable-experimental-webassembly-stack-switching
enable-webassembly-garbage-collection
- サポートはビルドのみ(
flutter run
や DevTools は非対応) - ブラウザと JavaScript API をターゲットにするため、
dart:html
やpackage:js
を使用している場合はコンパイルできない- url_launcher | Flutter Package など有名パッケージも該当
私はこちらの環境で実行しました。
❯ fvm flutter --version
Flutter 3.8.0-16.0.pre.18 • channel master • https://github.com/flutter/flutter.git
Framework • revision 0c1ed75915 (3 months ago) • 2023-02-23 10:39:41 +0000
Engine • revision d4bae2887e
Tools • Dart 3.0.0 (build 3.0.0-266.0.dev) • DevTools 2.22.1
正常にビルド完了し、後述の Firebase Hostingへデプロイ などを参考にホスティングした後、Developer Console の Network で確認すると、JavaScript に加え Wasm バイナリが実行されていることが確認できます。
WasmビルドされたFlutter Web |
---|
従来のビルド形式との比較
今回の Wasm 対応が追加されることで、flutter build web --xx
で指定できるオプションが計 3 つになります。改めて以下の表にまとめます。
Build options | Compiled | Renderer | /build |
---|---|---|---|
--wasm [1]
|
Wasm、JavaScript | CanvasKit | /web_wasm |
--web-renderer canvaskit |
JavaScript | CanvasKit | /web |
--web-renderer html |
JavaScript | html | /web |
ちなみに、--wasm
が Wasm だけでなく JavaScript にもコンパイルされるのは、Wasm では DOM を直接触れず JavaScript を介して利用するなど、Web 周辺の技術を扱うには JavaScript が必要なためです。Wasm 自体も JavaScript を代替するものではなく、共存していく形が業界標準のはずです。
Web Renderer の特徴や使用用途の違いについてはこちらをご参照ください。
--wasm
を使用したビルドでは build/web_wasm
というディレクトリに格納されます。下記の図が出力結果の比較です。差分はごく僅かで、以下の 2 ファイルが Wasm ビルド時には生成されていることがわかります。
/build/web |
/build/web_wasm |
---|---|
やや理解に自信がない部分もありますが、それぞれの役割はざっと以下だと思います。
-
main.dart.wasm
- ロジックなどのアプリケーション処理
- 従来のビルド形式では
main.dart.js
で実装されていた処理
-
main.dart.mjs
-
main.dart.wasm
からコンパイルしたWebAssembly.Module
を実行できるようにしているランタイム(?) - 内部で
dart2wasm
を使っているぽい
-
Wasm ファイルが読み込まれるまで
前述の通り、Wasm の違いは .mjs
と .wasm
ファイルが存在している程度です。他にも canvaskit
の skwasm
系のファイルがなくなっていりしていますが今回は割愛します。
従来のビルド形式同様、index.html
から flutter.js
を読み込み、FlutterEntrypointLoader
クラスで main.dart.js
をエントリーポイントとして読み込みます。ここまでは一緒です。flutter.js
の initialize については Customizing web app initialization | Flutter が参考になります。
async loadEntrypoint(options) {
const { entrypointUrl = `${baseUri}main.dart.js`, onEntrypointLoaded } =
options || {};
return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded);
}
大きな違いは main.dart.js
の中身です。従来のビルド形式の場合は minified された JavaScript としてそのまま実行されていましたが、Wasm ビルドの場合は以下のコードとなっており、内部で .mjs
の import と .wasm
のコンパイルをしていることがわかります。
// 読みやすさのため、try-catchのエラーハンドリング部分を削っています
(async function () {
let dart2wasm_runtime;
let moduleInstance;
// Wasmをストリームして`WebAssembly.Module`へコンパイル
// ref. https://developer.mozilla.org/ja/docs/WebAssembly/JavaScript_interface/compileStreaming
const dartModulePromise = WebAssembly.compileStreaming(fetch("main.dart.wasm"));
dart2wasm_runtime = await import('./main.dart.mjs');
moduleInstance = await dart2wasm_runtime.instantiate(dartModulePromise, {});
if (moduleInstance) {
await dart2wasm_runtime.invoke(moduleInstance);
}
})();
以上をまとめるとこちらの図のイメージです。
Firebase Hostingへデプロイ
Google I/O 2023 では、Firebase Hosting の Flutter Web Wasm 対応も同時に発表されました。
Today, we’re announcing official Firebase Hosting support for WebAssembly Flutter web, which includes new step-by-step instructions to help you get set up quickly.
https://firebase.blog/posts/2023/05/whats-new-at-google-io#support-for-flutter-web
Flutter Web の Wasm ビルドは、前述の通り現時点では build
しかサポートしていないためデバッグ実行では確認できません。そのため、Firebase Hosting にデプロイして動作確認します。
手順は 公式ドキュメント の通り進めれば、とくに難しいポイントも少ないので割愛します。躓いた場合や Firebase をそもそも使ったことがないという方は、以下の記事がキャプチャ付きで丁寧に解説されているので参考にしてみると良さそうです。
上記の記事にも記載されていますが、Firebase CLI でセットアップするだけで、裏でよしなに Firebase Hosting へのデプロイ権限を持つサービスアカウントを作成し、GitHub Actions のシークレットキーに登録してくれます。また、以下を利用した GitHub Actions のワークフローファイルまで作成してくれるので、ほぼセットアップ不要でデプロイ環境が整います。
複数サイトをホストする
やや脱線しますが、Firebase Hosting は 1 つのプロジェクトで複数のサイトをホスティングできます。
今回のサンプルリポジトリでは、ビルド方法による違いを比較するために 3 つのサイトそれぞれにデプロイしています。hosting:sites:create
コマンドを叩くか、あるいは GUI コンソールからポチポチしても作成できます。
# hostingするサイトを新規で作成
❯ firebase hosting:sites:create flutter-web-assembly-sandbox
こんな形で、ビルド形式によってホスティングする URL を 3 つ用意しました。
❯ firebase hosting:sites:list
Sites for project flutter-web-assembly-sandbox
┌───────────────────────────────────┬───────────────────────────────────────────────────┬─────────────────┐
│ Site ID │ Default URL │ App ID (if set) │
├───────────────────────────────────┼───────────────────────────────────────────────────┼─────────────────┤
│ flutter-web-assembly-sandbox │ https://flutter-web-assembly-sandbox.web.app │ -- │
├───────────────────────────────────┼───────────────────────────────────────────────────┼─────────────────┤
│ flutter-web-assembly-sandbox-html │ https://flutter-web-assembly-sandbox-html.web.app │ -- │
├───────────────────────────────────┼───────────────────────────────────────────────────┼─────────────────┤
│ flutter-web-assembly-sandbox-js │ https://flutter-web-assembly-sandbox-js.web.app │ -- │
└───────────────────────────────────┴───────────────────────────────────────────────────┴─────────────────┘
デプロイターゲットを設定する
作成したサイトの Site ID をそのまま使うのはやや大変なので、デプロイターゲットを設定して分かりやすい名前を付けます。コマンドが直感的に理解できるようになります。
以下の例では flutter-web-assembly-sandbox
という Site ID を wasm
というターゲットでデプロイできるようになります。
# サイトのデプロイターゲットを設定する
❯ firebase target:apply hosting wasm flutter-web-assembly-sandbox
✔ Applied hosting target wasm to flutter-web-assembly-sandbox
Updated: wasm (flutter-web-assembly-sandbox)
# これでデプロイできるようになる
❯ firebase deploy --only hosting:wasm
target:apply
コマンドを実行すると、.firebaserc
ファイルに以下の形式で追記されます。
.firebaserc
"hosting": {
"js": [
"flutter-web-assembly-sandbox-js"
],
"wasm": [
"flutter-web-assembly-sandbox"
],
"html": [
"flutter-web-assembly-sandbox-html"
]
}
最後に firebase.json
の hosting
を配列にし、さきほど作成したターゲットを指定すれば準備完了です。
firebase.json
"hosting": [
{
"target": "wasm",
"public": "build/web_wasm"
},
{
"target": "js",
"public": "build/web"
},
{
"target": "html",
"public": "build/web"
}
],
これで、以下コマンドでデプロイできるようになります。
firebase deploy --only hosting:wasm
最終的には、Melos を使ってビルドとデプロイをまとめて行うスクリプトにしました。
hosting:wasm:
run: |
flutter build web --wasm
cd ./firebase
firebase deploy --only hosting:wasm
以上を構成するリポジトリはこちらになります。
以下が、3つのビルド形式でホスティングした URL です。単なるカウンターアプリなので目立った違いはないですが、触って挙動をご確認いただけます。
BuildOptions | Hosting URL |
---|---|
--wasm |
https://flutter-web-assembly-sandbox.firebaseapp.com/#/ |
--web-renderer canvaskit |
https://flutter-web-assembly-sandbox-js.firebaseapp.com/#/ |
--web-renderer html |
https://flutter-web-assembly-sandbox-html.firebaseapp.com/#/ |
まとめ
今回は、Flutter Web の Wasm ビルドを中心に、Firebase Hosting へのデプロイや従来のビルド形式(CanvasKit, HTML レンダラー)と比較しながらその挙動を確認してきました。
iOS/Android/macOS などと比べて Web プラットフォームでの Flutter の利用は、やはりまだ物足りなさ(SEO や OGP なども)がありますが、Wasm の正式サポートで利用機会がさらに増えていきそうに思いました。
今回はブラウザでの実行を扱いましたが、Wasm の真骨頂はエッジサーバやコンテナなど汎用的な場面で高速な処理ができる、いわゆる Run Anywhere なケイパビリティを持っている点にあると思いますので、今後あらゆる場面でさらに一層 Flutter の活躍が見られそうだなと期待しています。
参考
- (8) Evolving Flutter's support for the web - YouTube
- WebAssembly の GC Proposal とは何か / どこに向かおうとしてるのか
- DartをWebAssemblyにコンパイルする(2023年3月版) - Qiita
- .mjs とはなんぞや?!
- Flutter, Dart, Wasm-GC - Wasm I/O Conference - March 23, 2023 - [shared publicly] - Google スライド
- (8) Flutter, Dart, and WASM-GC: A new model for Web applications by Kevin Moore @ Wasm I/O 2023 - YouTube
- [web] Flutter web using WebAssembly (WASM) · Issue #41062 · flutter/flutter
-
--wasm
が利用できるのは master チャンネルのみです。 ↩︎
Discussion