WebAssembly から逃げない

こんにちは。 システム開発部の芦刈です。

今回はWebAssemblyについてお話します。

WebAssembly とは

各所で種々の表現をされていますが、ひとことでいうならブラウザで実行するバイナリコードです。
コードを逐次解釈するインタプリタ形式のJavaScriptと異なり、コンパイル済みのバイナリコードを利用することで、コードサイズを小さく、処理速度を速くすることができます。

既に LLVM 8.0.0 から正式対応されているので、LLVM ベースのコンパイラを利用できる言語であればどんな言語からでも WebAssembly のバイナリコードを生成できます。
よく挙げられる言語は、C, C#, Rust, Go, Kotlin などです。

2015年の登場から、2019年に正式なWeb標準になるまで4年とそこそこ時間がかかっており、その間にJavaScript の対抗馬としてどうなのかという様々な議論がなされておりました。当時はWebAssemblyは3Dレンダリングやストリーミング配信、暗号化など計算量が大きい処理について力を発揮するものの、その他一般的なWebサイトに使われているコンテンツレベルではJavaScriptとそこまで大差ないという話もあり、個人的には古くにサーバサイドから戦いを挑んだ ASP.NET や HTML5 の登場で当時淘汰されつつあったSilverlight と同じ立ち位置に落ち着くのかなという思いでした。
しかしながら、この記事を書いてる現在、Silverlight のサポート終了や移行先としての Blazor WebAssembly の登場に始まり、移植性の高さを売りにした WebAssembly の盛り上がりを見ると、全くの無視をするというわけにはいかないのかなと考えさせられております。

WebAssembly を触ってみる

という上記のような前フリをしたものの、私は .NET 系エンジニアではないので C# じゃなくていいかなーと。
Go もコンパイル後のファイルが最低 2MB になるという、なんとも本末転倒感の問題があると耳にしたので、今回は Vue.js + WebAssembly でやってみようと思います。

WebAssembly についての記事はたくさんあるものの、情報の新旧でやり方が大きく異なったり、開発者ごとに違う手法を取っていたりしたため、スタートアップが難しい印象があります。
今回はいろいろ試した中でも一番簡単な方法をご紹介します。
以降、下記を導入済みという前提でお話ししますので、まだの方は先にそちらを導入してください。

  • vue-cli
  • Node.js
  • npm
  • libssl-dev
  • pkg-config

Rust によるライブラリ開発環境の構築

Rust 開発環境の構築にはrustupを使います。
各々環境に応じて、このページ に記載されているインストーラまたはコマンドを利用して導入します。
私の場合は Ubuntu を使っていたので、記載にある通り下記コマンドを使いました。
インストール中に選択肢がでますが、1) Proceed with installation (default) を選べば問題ありません。

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

なお、インストールの最後にも表示されますが、下記を忘れずに。

source $HOME/.cargo/env

上記が終わったら、Rust から WebAssembly のパッケージを作るためのライブラリである wasm-pack をインストールします。

cargo install wasm-pack

ここまで終わったら、下記コマンドを実行すればライブラリ作成用のプロジェクトが作成されます。
カレントディレクトリにプロジェクト名と同名のディレクトリが作成されるので、今後はそちらで作業します。

cargo new プロジェクト名 --lib

Hello, World 用ライブラリのビルド

まずは Cargo.toml にて本プロジェクトが他言語用の動的ライブラリを明示する必要があります。
また、今回はJavaScript - Rust 間で値をやり取りするため、 wasm-bindgen を利用することも明記します。
Cargo.toml の末尾に下記を追加してください。
記事によっては description や license、repository なんかも追記しろと書いてあるものもありますが、今回は気にしなくていいです。

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.70"

次に、lib.rs を編集します。
上記手順通りにプロジェクトを作成した段階だと単純なテスト用関数が1つあるだけなので、下記で丸々上書きしてください。

extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}

ここまで終わったら後はビルドするだけです。
下記コマンドでビルドしてください。
上記でも挙げた、description や license、repository がないと警告が出ますが気にしなくて結構です。

wasm-pack build

プロジェクトディレクトリ内に pkg フォルダと下記ファイルが作成されていれば完了です。

pkg
├── package.json
├── rust_test.d.ts
├── rust_test.js
├── rust_test_bg.js
├── rust_test_bg.wasm
└── rust_test_bg.wasm.d.ts

Hello, World 用ライブラリを実際に使ってみる

vue-cli を使ってサクッと vue プロジェクトを作成してください。

その後、前章の手順でできた pkg フォルダ毎を vue プロジェクト内にコピーしましょう。
こんな感じでOKです。

.
├── package.json
├── pkg
│   ├── package.json
│   ├── rust_test.d.ts
│   ├── rust_test.js
│   ├── rust_test_bg.js
│   ├── rust_test_bg.wasm
│   └── rust_test_bg.wasm.d.ts
├── public
│   ├── favicon.ico
│   └── index.html
└── src
    ├── App.vue
    ├── assets
    │   └── logo.png
    ├── components
    │   └── HelloWorld.vue
    └── main.js

よく npm レジストリを経由して取り込む手法が紹介されてますが、社内開発とかだとちょっと問題かと思うので、今回は物理的にファイルを移動します。
npm link を使って node_modlues 越しに参照するのも全然アリですが、vue-cli を使った場合はデフォルトで入ってる ESLint をシンボリックリンク先でも適用するために Webpack 使え~などと言われて手順が煩雑になるので、ここでは記載しません。

後は vue ファイル内でサクッと呼んであげてください。
単一ファイルコンポーネントを利用する場合は、下記のような感じでいいと思います。
画面に表示された TEST ボタンを押せば、ブラウザの alert で「Hello, World!」と表示されます。

<template>
  <div>
    <button @click="testRust">TEST</button>
  </div>
</template>

<script>
const rust = import("/pkg/rust_test_bg.js");
export default {
  methods: {
    testRust: function () {
      rust.then((rust) => rust.greet("World"));
    },
  },
};
</script>

ここまで来たら、後は自由に開発を進めるだけですね!

最後に

WebAssembly について情報を取捨選択するのは本当に大変でした。
wasm-pack は Rust 1.3 以上が必要だから nightly 版じゃないといけないという情報を見てわざわざ手動で導入したり、全部 wasm-pack がターゲットも依存ライブラリも全部設定・インストールしてくれるのに wasm-bindgen を個別に導入・設定しようとしたり、WebAssembly.instantiate() を使って JavaScript から直接 wasm ファイルをロードする手法を使って application/wasm を MIME Type に追加しろと言われて四苦八苦したり、挙げたらきりがないです。

この記事を読んでいる皆様はとりあえず上記手順でやってみて、うまく行かなかったら rustup ごとアンインストールして最初からやり直してみてください。
私も wasm-pack のインストール時に open-ssl エラーが出ているのに気づいてなくて、うんうん唸ってましたが、再インストールしたら上手くいったなんてこともありました。

これから今年の10月の Silverlight サポート終了が近づくにつれて WebAssembly についての記事がどんどん出てくるとは思いますが、本記事が「とにかくまずは触ってみたい!」という方のお役に立てれば幸いです。