マイクロソフトの「Blazor」に見るWebAssemblyの利用例
前述の通りWebAssemblyでは、さまざまな言語での開発が想定されています。ここではWebAssemblyの利用例として、マイクロソフトのWebページフレームワーク「Blazor」でWebAssemblyを利用する図5のサンプルを紹介します。画面左の「Counter」をクリックすると、「Click me」ボタンを押すごとにカウントが増えていくサンプルが表示されます。
このサンプルは、Visual Studio 2019で「Blazor WebAssemblyアプリ」テンプレートで生成したプロジェクト(p002-blazor-wasm)をそのままビルドしたものです。
図5のサンプルは、リスト2の通り記述されています。この記法は「Razor コンポーネント」と呼ばれるもので、表示内容を表すHTMLと、動作を表すC#のコードを同じファイルに記述します。文法の細かい説明は省略しますが、ボタン押下時に(2)のIncrementCountメソッドが実行されて(1)のcurrentCount変数が1増え、それが画面に表示されます。
@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; // カウンター ...(1) private void IncrementCount() // カウンターを増やすメソッド ...(2) { currentCount++; } }
p002-blazor-wasmプロジェクトはVisual Studioでそのまま実行できるほか、「発行」してWebサーバーにホストすることもできます。発行するには、ソリューション エクスプローラーでプロジェクトを右クリックして「発行」を選択し、表示された図7の発行先から「フォルダー」を選択します。
p002-blazor-wasm-webサンプルコードは、p002-blazor-wasmプロジェクトからフォルダーに発行した成果物をNode.jsのWebサーバーで表示できるようにしたものです。wwwroot/_framework/_binフォルダーを見ると、プロジェクト内容や.NETライブラリーのアセンブリファイル(*.dll)が確認できます。
Blazor WebAssemblyアプリでは、これらの.NETアセンブリを、サーバー側の.NETランタイムではなく、WebAssemblyで動作するWebブラウザー上の.NETランタイムで動作させます。.NETランタイムの実体は、wwwroot/_framework/wasmフォルダーに存在する「dotnet.wasm」です(WebAssemblyファイルの拡張子は「wasm」となります)。
このようにWebAssemblyには、.NETランタイムそのものをWebブラウザー上で動作させられるほどの性能を備えています。
[補足]dotnet.wasmなどの圧縮ファイルフォーマット
図8、9では、ファイル名の末尾に「.br」と「.gz」がついたファイルが存在します。これらは各ファイルを圧縮したもので、「.br」のファイルは「Brotli」、「.gz」のファイルは「Gzip」という方法で圧縮されています。Webサーバーの設定によっては、これらの圧縮ファイルを利用して通信量を低減できます。
いよいよRustでWebAssembly実装にチャレンジ
WebAssemblyで.NETランタイムを実現する図9のdotnet.wasmファイルは、サイズが約1.8MBと大きく、また内部的にガベージコレクションを行うため速度が落ちる懸念もあります。Rustを使えば、ランタイムやガベージコレクションのない、パフォーマンスやサイズが最適化されたWebAssemblyが作れます。
ここからはいよいよ本連載の主題である、RustでWebAssemblyの実装を試していきます。今回は環境を作って最低限のサンプルコードを生成・実行し、Rustを利用したWebAssembly実装のイメージをつかんでいきます。
まずは環境作成
RustとWebAssemblyの環境作成方法を説明します。まずRustの環境一式を整備できる「rustup」をインストールします。公式ページで、図10の赤枠部ボタンからインストーラーをダウンロードして実行します。
次に、RustでWebAssemblyを作成する「wasm-pack」をインストールします。公式ページで、図11の赤枠部リンクから「wasm-pack-init.exe」をダウンロードして実行します。
WebAssembly対応のプロジェクトを生成するため、テンプレートからプロジェクトを生成するツール「cargo-generate」をインストールします。インストールするには、リスト3のコマンドを実行してください。なお「cargo」は、Rustのプロジェクト作成やビルド、コードチェックなどを行える、Rust開発の必須ツールです。詳細な利用法は次回以降説明します。
cargo install cargo-generate
さらに、Node.jsをインストールして、npmコマンドが利用できるようにします。
RustのWebAssemblyプロジェクトを生成してビルド
環境作成ができたら、いよいよプロジェクトを生成します。リスト4のコマンドを実行してください。ここではリスト3でインストールしたcargo-generateを利用して、--gitオプションに指定したGitHubリポジトリからプロジェクトを生成します。コマンドライン上でプロジェクト名の入力が求められるので、任意の名前を指定します(サンプルコードでは「p003-rust-wasm」としています)。
cargo generate --git https://github.com/rustwasm/wasm-pack-template
生成したプロジェクト内のsrc/lib.rsがWebAssemblyのRustソースコードファイルです(Rustソースコードの拡張子は「rs」となります)。
mod utils; // utils.rsを参照 ...(1) use wasm_bindgen::prelude::*; // wasm_bindgen::preludeモジュールを利用 ...(2) // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global // allocator.→メモリアロケーター ...(3) #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; // JavaScriptのalertメソッドを参照 ...(4) #[wasm_bindgen] extern { fn alert(s: &str); } // greetメソッドの実装 ...(5) #[wasm_bindgen] pub fn greet() { alert("Hello, p003-rust-wasm!"); }
(1)は、同じフォルダーに存在するutils.rsを参照する記述です。utils.rsにはRustのエラー発生時に詳細情報をJavaScriptのconsole.errorに表示する指定があります。(2)は、RustでWebAssemblyとJavaScriptを結び付ける「wasm_bindgen」のモジュールを利用する記述です。(3)は、Rustでメモリを割り当てるのに利用するメモリアロケーターの記述ですが、今回は利用しません(利用するには追加設定が必要です)。
(4)と(5)が処理の実装です。(4)はRustからJavaScriptのalertメソッドを参照する記述で、(5)でそのalertメソッドでメッセージを表示するgreetメソッドを実装しています。「fn」は関数で、「pub」は関数を外部に公開することを表します。
このプロジェクトをビルドするには、リスト6のコマンドを実行します。
wasm-pack build
ビルド実行後、プロジェクトのpkgフォルダーに、ビルドされたWebAssemblyファイル「p003_rust_wasm_bg.wasm」と、それをJavaScriptから利用するためのファイル群が生成されます。これらはnpmで公開されているパッケージと同じ形式となっており、ビルド成果物をnpmに公開することもできます(詳細は次回以降で紹介します)。
ビルドしたWebAssemblyのパッケージを参照して実行
それでは、ビルドしたWebAssemblyのパッケージを実際に実行してみます。リスト7のコマンドを実行すると、npmでWebAssembly用のプロジェクトを自動生成できます。
npm init wasm-app <プロジェクト名>
生成したプロジェクトのルートフォルダーに図12のpkgフォルダーをコピー後、package.jsonに以下の通り記述して参照します。ここではpkgフォルダーのパッケージを「p003-rust-wasm」という名前で参照しています。
"dependencies": { "p003-rust-wasm": "file:./pkg" },
Webページ表示時に実行されるindex.jsに、以下の通り記述します。(1)でパッケージを参照して、(2)でそのgreetメソッドを実行しています。
import * as wasm from 'p003-rust-wasm'; // WebAssemblyパッケージを参照 ...(1) wasm.greet(); // パッケージから参照したgreetメソッドを実行 ...(2)
ここまで実装後「npm install」コマンドでライブラリーをインストールして「npm run start」メソッドを実行すると、リスト5(5)で実装したgreetメソッドにより、図13の通りダイアログが表示されます。
今回は単にダイアログを表示しただけですが、Rustの実装からWebAssemblyを生成して、JavaScriptから利用できました。今後、WebAssemblyで提供したい機能をRustファイルlib.rs(リスト5)に実装していくことになります。
[補足]index.jsを読み込むbootstrap.jsの役割
リスト7のコマンドで生成するプロジェクトでは、index.jsを直接読み込まず、リスト10のbootstrap.jsで間接的に読み込んでいます。
import("./index.js") .catch(e => console.error("Error importing `index.js`:", e));
これは、WebAssemblyをインポートするモジュールは非同期で読み込む必要があるためで、動的インポートにより、importメソッドでindex.jsを読み込んでいます。
まとめ
本記事では、RustでWebAssemblyを実装する方法を説明する連載の1回目として、WebAssemblyの概要と活用例を紹介しました。WebAssemblyはWebブラウザー上で動作するバイナリファイルで、記事の前半ではWebAssemblyで.NETランタイムを実行する例を紹介しました。後半では、実際にRustで簡単なWebAssemblyをビルドして実行するまでの手順を説明しました。
次回は、実用的なWebAssemblyをRustで実装するために必要となる、基本的な開発方法や文法を説明していきます。