mrubyをWebAssemblyで動かす(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

mrubyをWebAssemblyで動かす(翻訳)

要約: WebAssemblyがやってきました!やってみたい方は本記事のwasm gemをお試しください

2018年の世界へようこそ。最も興奮を呼び起こすイノベーションがコンパイラ界隈にも巻き起こりつつあります。ARやVRや機械学習など先端を行く話題はいくつもありますが、ソースコードを機械が実際に理解できる形に変換する役割を担うコンパイラの歴史に比べればまだまだ新顔です。コンパイラの復権は主にLLVMのおかげです(LLVMはモジュール形式のコンパイラインフラストラクチャプロジェクトです)。LLVMの重要性については、Wiredの2013年の記事に的確な指摘がまとまっています。

その希望とは、LLVMがソフトウェア開発の新時代の水先案内人となり、アプリケーションがあるマシンから別のマシンに、そしてあるプロセッサから別のプロセッサにすら自由に移動できるようになることだ。

以来5年が経過し、これはかなりの部分まで実現しました。LLVMの導入とそれがもたらすモジュラーパラダイムによって、ソフトウェア開発が根底から変革されました。LLVMによって、移植性/最適化/言語実装/コンパイルライフサイクル全体がラディカルに考え直されたのです。詳しく知りたい方はoverview of LLVMをご覧ください。著者のChris LattnerはLLVMの主要な作者であり、しかも Swiftの創立者でもあるのです。Chrisの昨年のインタビューをAccidental Tech Podcastで聞くことができます。

LLVMがWebといったい何の関係があるのでしょうか?Webのプログラミングといえば、その草創期(1995年)からJavaScriptと相場が決まっていました。JavaScriptはその後23年間批判と論争の嵐をくぐり抜け、今もWebプログラミング言語として居座っています。JavaScriptを置き換える強力なライバルが不在であったにもかかわらず、言語としてよい感じに成熟と進化を繰り返してWebに関わり続けています。Webブラウザも、静的コンテンツを表示するだけの単なるビューアから、数十億ものユーザーやシステムの基盤を追い求める企業の要求に後押しされたフル装備のアプリケーションプラットフォームへと着実に進化しています。しかしながら、ブラウザが「正しい」アプリケーションプラットフォームになるにはまだひとつ足りないものがあります。Wired誌の同じ記事から再度引用します。

JavaScriptの難点は、言語としては比較的シンプルであることだ。他の言語(Javaや、誉れ高きCやC++など)ならできることでもJavaScriptでできるとは限らない。私たちに必要なのは、アプリケーションをどんな言語で書いてもあらゆるマシンで動かせる方法だ。それも大きな妥協を強いられずに。そしてLLVMはそれを目指している。少なくともそう思っている人々がいる。

ブラウザがデスクトップOSやモバイルOSに匹敵するには、ブラウザに欠かせない例のあの言語からブラウザ自身が解放されなければなりません。つまりブラウザのプログラミング環境は、他のプラットフォームと同様もっと低レベルになるべきです。ブラウザは独自のアセンブリ言語を備え、特定のハードウェアに依存しないよう抽象化しつつ、マシンそのものにできるだけ近いところに配置されるべきです。2012年に始まったasm.jsプロジェクトも同じものを目指していました。asm.jsは、JavaScriptのサブセットを定義することで、コンパイラにとって理想的なターゲット言語になるべくJavaScriptのコンセプトを絞り込みます。ここにLLVMとasm.jsの歴史がひとつとなり、Alon Zakaiが生み出したLLVMからJavaScriptへのコンパイラであるEmscriptenというツールがすべてを可能にしました。今や私たちは、CやC++で書かれたプログラムを次のようにJavaScriptにコンパイルできます。

C/C++ → LLVM → Emscripten → JavaScript (asm.js)

詳しくは、Alonのスピーチ「Compiling to JavaScript」やCppCon 2014でのEmscriptenやasm.jsについてのスピーチ↓をご覧ください。

asm.jsの登場によってWebのアセンブリ言語が産声を上げたのです。これはあらゆるブラウザで標準化および統合化される可能性を秘め、そして実際に2015年のWebAssemblyのクロスブラウザ化作業の開始でまさにそれが起きたのです。この作業では、移植性と効率の高いバイナリインストラクション形式や安全な仮想マシン(VM)の定義をWeb向けにさらに進めます(Brendan Eichの“From ASM.JS to WebAssembly”も参照)。わずか2年の間にコミュニティは仕様を定めてプロトタイプをいくつも作り、Webプラットフォームに責任を持つ最大のあの企業と激論を戦わせ、主要な全ブラウザでWebAssemblyをリリースしたのです。これは誇張抜きで歴史に残る偉業です。おそらくブラウザは最初からこうあるべきだったのでしょう。ブラウザは低レベルのコンパイルターゲットを提供して、多くのプログラミン言語もWebでプログラミングできるようにし、既存のネイティブツールやライブラリやアプリも使えるようにすべきだったのです。しかし、ブラウザが今日のような地位を占めるようになると一体誰が予測できたでしょうか。とはいうものの、今や私たちがハッピーになれる道が開けてきましたし、そこに秘められた能力にはいやが上にも期待します。MozillaがWebAssemblyはゲームを変革すると考えています。やってみたい方はWebAssembly Studioをご覧ください。

RubyをWebに持ち込む

多くの人がWebで他のさまざまな言語を何年も使っていますが、そのためにはJavaScriptを使わないわけにいきません。その最もよく知られた例はCoffeeScriptでしょう。CoffeeScriptは2009年に始まり、JavaScriptにうんざりした人(またはJavaScriptに戻ってきた人)に別の選択肢をもたらしました。RubyやPythonに似た拡張文法で書くことができ、純粋なJavaScriptにトランスパイルされます。素晴らしいじゃないですか!それから何年もの間source-to-sourceコンパイラが全盛でした。JavaScriptにコンパイルされる言語の長大なリストを見ると何とも言えない気持ちになります。Rubyも例外ではありません。その中でも優れものはOpalです。

このsource-to-sourceアプローチには、効率やパフォーマンス、低レベル最適化の喪失などいくつも短所があります。JavaScriptを歪めてまで需要を満たそうとする必要がそもそもあるのかという根本的な批判もあります。WebAssembly登場以前であれば、その答えは単純そのもので、他に手段がないのだからそうするしかなかったのです。それが今や他の手段があるのです。この新しいコンパイルターゲットを利用して、実にさまざまな言語をWebプラットフォームに暖かく迎えようという動きが始まっています。他の言語たちは正確にはどんな形でWebにやってくるのでしょうか?CやC++などのネイティブ言語は既にLLVMへのコンパイルが可能ですし、LLVMのその他の実装(Rustなど)も、コンパイラツールチェインのターゲットをWebAssemblyに設定するという素直な手順でできます。Rubyのように、インタプリタやVMを自前で持つ動的言語だと少々難しくなります。

その理由を知るために、Rubyの主要な実装であるMRI(CRubyとも呼ばれます)を見ていくことにしましょう。どんなプログラミング言語であっても、構文の仕様よりはるかに多くのものが含まれています。そうしたものは、言語の実装や利用状況によっても定義されます。Rubyの場合は伝統的に、Unix的な環境に依存する各種ツールの総合的なエコシステムを意味し、ファイルシステム、コンパイラツールチェイン、標準ライブラリ、ネットワーク、その他OSが提供することが期待されるあらゆるものが含まれます。MRIすなわちCRubyはそれらを期待していますし、そのことは別に問題ありません。Rubyの使われ方、たとえばRailsアプリを実行するWebサーバーなどは、このモデルに完全に適応していて、その利点をすべて享受できます。

しかし、利用状況が変わるとそうはいかなくなります。たとえばRubyアプリをエンドユーザーに配布するなどです。期待するRubyインタプリタをローカルにインストールし、どのアプリにも正しいバージョンのgemを提供するとき、一般人と開発者と正規ユーザーを同じに扱うわけにはいきません。Rubyインタプリタを丸ごとアプリに仕込んでリリースするという回避方法もなくはありませんが、どう考えても現実的ではないでしょう。鼻で笑わないでください。一応実際に可能なのですから。Traveling Rubyは、Windows/Linux/macOS向けにRubyを同梱したアプリパッケージを作成するという印象的なプロジェクトです。Minqi PanによるRubyConf 2017の秀逸なトークでは、Rubyアプリを単一の実行ファイルに固め、メモリ上に仮想のファイルシステムを作成するなどの裏技を駆使して無理やり動かすというデモを披露しました(動画コード)。どんな技を使ったのか私にはわかりませんが、私の好きなアプリの例(かつRubyデスクトップアプリとして最も知られているアプリ)はSonic Piというライブコーディング向けミュージックシンセサイザーです。

「インタプリタ同梱」的なアプローチは使えるは使えますが、サイズが相当巨大になるなど、いろいろ制約があります。参考までに、Ruby 2.5.1をmacOSに(rbenv install 2.5.1で)新規インストールしたときの典型的なサイズは20.7 MB、1,396ファイルにのぼります。Traveling Rubyでこれをtarball圧縮すると6.7 MB(未圧縮19 MB)、そしてMinqiのプロジェクトだと40.8 MB(34.1 MB圧縮)です。Sonic PIのようなデスクトップアプリなら大したことはありませんが、Webではそうはいきません。昨今だとどこかのWebサイトは10 MBをプッシュすることもあるようですが(某NYTimes.comとか)、これはあんまりです。それにまだ私たちはアプリのコード量を計算に入れていません。ファイルサイズだけではなく、MRI/CRubyの起動もかなり重く、メモリのフットプリントも相当な量になります。

MRI/CRubyインタプリタをブラウザに丸ごと同梱することもできなくはないかもしれませんが、現実的とは思えませんし、そもそも必要ですらないでしょう。とにかく、Rubyの実装だけの問題ではないことは明らかです。もっと効率的で軽量なRubyがどこかにあれば、インタプリタの必要な部分だけを取り出して、管理可能な小さいバイナリにコンパイルできるのではないでしょうか。おお、そういえばそんなものがあるじゃないですか!

mrubyのあらまし

mrubyのことをご存知ない方は、この機会にぜひお見知りおきを。個人的には、Rubyコミュニティで今アツいのはmrubyだと思います。mrubyプロジェクトを率いるのはRubyの作者としてお馴染みのMatzその人です。mrubyプロジェクトは2012年に組み込み(eMbedded)のユースケースに適した言語の実装としてスタートしました。組み込みの分野ではメモリやストレージといったリソースが限られています。ウォッシュレットのプログラミングも組み込みのひとつです。mrubyは完全にモジュール化されているので、MRI/CRubyのような巨大なインタプリタのモノリスをインストールするのではなく、好きなだけカスタマイズできます。ファイルシステムやネットワークアクセスが不要なら取っ払ってしまえばよいのです。ユーザーにできることを制限したいならそれもできます。mrubyはあなた好みのインタプリタをビルドできるのです。「完全なRubyとは違う」という制約はあるものの、利用状況に応じてmruby言語の主要な実装から本質的なメリットを得られます。mrubyについて詳しく知りたい方には、Zachary ScottのMRuby記事(訳注: サイトは現在ありません)とZacharyによるコミッターDaniel Bovensiepenへのインタビューがおすすめです。

mrubyは、インタプリタのアーキテクチャを考え直すことで新たな可能性の扉をも開きました。そのひとつが今私たちが興味津々の「Rubyスクリプトをネイティブコードにコンパイルする機能」です。プログラムを配布するのにありがたい機能ですね。アプリを単一の小さなバイナリで作成できるということは、Rubyの移植性が著しく高まるということです。マシンを問わずらゆる主要なプラットフォームで動く単独の実行ファイルを簡単に生成するmruby-cliのようなプロジェクトもあります。mrubyのバイトコードはCのプログラムに直接埋め込むこともできますので、clang(C言語ファミリーのLLVMフロントエンド)でコンパイルできます。コンパイルは次のような流れになります。

Rubyスクリプト → mrubyバイトコード → C → clang → LLVM → ネイティブ実行ファイル

ここまでご理解いただけましたでしょうか?

mrubyからWebAssemblyまで

コンパイラのターゲットをLLVMにできるなら、WebAssemblyへの道が見えてきます。先ほどのEmscriptenがLLVM-to-JavaScriptコンパイラだったことを思い出してください。EmscriptenはWebAssemblyにコンパイルする機能を獲得しつつあります。すなわち、Rubyは次のような旅路を辿ることになります。

Rubyスクリプト → mrubyバイトコード → C → emcc(Emscriptenコンパイラのフロントエンド) → LLVM → Binaryen → WebAssembly

ちょっと無理くりすぎな感もありますが、役者は揃いつつあります!フローの中にひとつ見慣れないものがありますが、BinaryenはWebAssembly向けのコンパイラ/ツールチェインインフラストラクチャです(名前はbinaryとEmscriptenのもじり)。AlonはBinaryenのコンパイルでWebAssemblyを生成するスピーチも行っています。

ぼちぼち実際に手を動かしてやってみましょう。まず必要なのはWebAssemblyツールチェインを取ってくることです(WebAssemblyの“Getting Started”を参照)。開発に必要なものを理解したら、Emscripten SDKをダウンロード/インストールして有効にします。

$ git clone https://github.com/juj/emsdk.git
$ cd emsdk
$ ./emsdk install latest
$ ./emsdk activate latest

SDKはgit cloneで取り、他の開発ツールと同じところに置くことをおすすめします。ツールの一部はまだGitHubの個人アカウントのリポジトリに置かれています。ほとんどの開発者はMozilaで作業しているようなので、今後WebAssembly GitHub orgのようなもっとオフィシャルな場所に統合されるのかなと思ったりもします。

次はPATHと環境変数を設定するスクリプトを実行しなければなりません(source ./emsdk_env.shのように実行)。Emscripten SDKでは独自のclangとnodeが提供されるので、通常の開発で使っているバージョンと衝突する可能性があります(Rubyをソースからコンパイル/インストールしてgemのネイティブ拡張をビルドするときにそのバージョンで失敗するなど)。そこで私は、常に新しいシェルで実行するのではなく、自分の.bash_profileへのエイリアスを追加して、WebAssemblyを使うシェルでのみwasm_initでSDKをアクティベートするようにしました。

alias wasm_init="source ~/Developer/emsdk/emsdk_env.sh"

以上でEmscripten/WebAssemblyツールチェインが使えるようになります。

次はmrubyをインストールする必要があります。mirbmrbcなどの便利なコマンドラインツールにアクセスできるよう、macOSならHomebrew(brew install mruby)でインストールすることをおすすめします。Linuxならsudo apt install mruby libmruby-devを使います。

いよいよWebAssembly向けのmrubyインタプリタをビルドします。まずはGitHubのmrubyリポジトリをcloneしましょう。

git clone https://github.com/mruby/mruby.git

mruby/ディレクトリにcdします。ここからいよいよ面白くなってきます。mrubyインタプリタの実際のビルド設定は、シンプルなRubyスクリプトそのものであるbuild_config.rbファイルを定義してrakeを実行することで行います。リポジトリにあるデフォルトの設定には便利なboilerplateがいくつも詰まっていますし、ドキュメントもかなり詳しく書かれています。今回のニーズに応じて、設定ファイルの内容を以下で完全に置き換えることにします。

MRuby::Build.new do |conf|
  toolchain :gcc
  conf.gembox 'default'
end

MRuby::CrossBuild.new('emscripten') do |conf|
  toolchain :clang
  conf.gembox 'default'
  conf.cc.command = 'emcc'
  conf.cc.flags = %W(-Os)
  conf.linker.command = 'emcc'
  conf.archiver.command = 'emar'
end

新しい設定ファイルは、gccコンパイラツールチェイン(自分のシステムで同等に近いものなら何でも構いません)を用いてホストシステム向けのネイティブコンパイルを作成するようmrubyのビルドシステムに指示します。macOSの場合はgcc --versionで確認できます。

Apple LLVM version 9.1.0 (clang-902.0.39.1)
Target: x86_64-apple-darwin17.5.0

これは今使っている64-bit x86ホストシステム向けのバイナリをビルドします。WebAssemblyをターゲットとしてクロスコンパイルしたいので、ここで上のCrossBuildブロックにご登場願うことになります。このブロックではclangのツールチェインを選択してemcc(Emscripten Compiler Frontend)とemar(Emscripten LLVM Archiver)を使うよう指定します。-Osコンパイラフラグは、コードサイズ削減などほとんどの最適化を有効にするオプションです。クロスコンパイルはかなり強力で、私が別プロジェクトで作っているmrubyのiOS/tvOSフレームワークでは、静的ライブラリやヘッダーがすべて単一パッケージに同梱されています。

rakeでmrubyのWebAssembly向け静的ライブラリをコンパイルする準備が整いました。build/ディレクトリが作成され、その下にhost/ディレクトリやemscripten/ディレクトリも作成されます。欲しい静的ライブラリはemscripten/lib/libmruby.aにあります。

cd ..mruby/ディレクトリを抜けます。今度はWebAssemblyをコンパイルする以下のシンプルなスクリプトを書いてhello_ruby.rbに保存します。

puts "Hello Ruby!"

コンパイラを起動して、このスクリプトからmrubyのバイトコードを生成します。

mrbc -Bhello_ruby hello_ruby.rb

ここでちょっと解説します。mrbcはmrubyコンパイラであり、-Bはバイトコードを生成してhello_rubyという名前のCの配列に置くよう指示します。最後のhello_ruby.rbは、コンパイルしたいファイルです。生成された新しいCファイルはhello_ruby.cという名前で、バイトコードを表す16進整数の配列を含んでいます。

hello_ruby[] = {
0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x34,0xcd,0x4c,0x00,0x00,0x00,0x65,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x47,0x30,0x30,
0x30,0x30,0x00,0x00,0x00,0x3f,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x04,
0x06,0x00,0x80,0x00,0x3d,0x00,0x00,0x01,0xa0,0x00,0x80,0x00,0x4a,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x00,0x00,0x0b,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x52,0x75,0x62,
0x79,0x21,0x00,0x00,0x00,0x01,0x00,0x04,0x70,0x75,0x74,0x73,0x00,0x45,0x4e,0x44,
0x00,0x00,0x00,0x00,0x08,
};

このままでは単なる静的データでしかありません。これを読み込んで実行するにはもうひと頑張りが必要です。バイトコードを含むhello_ruby.cファイルに以下のコードを追加しましょう。

#include <mruby.h>
#include <mruby/irep.h>

int main() {
  mrb_state *mrb = mrb_open();
  mrb_load_irep(mrb, hello_ruby);
  mrb_close(mrb);
  return 0;
}

このスニペットではいくつかの作業を行っています。まずコアmrubyと中間表現(バイトコード)ヘッダーをインクルードしています。main()関数ではmrb_open()でmrubyコンテキストを開き、mrb_load_irepでバイトコードを読み込んで実行し、最後にコンテキストを閉じます。シンプルですね!

いよいよhello_ruby.cファイルをEmscriptenでWebAssemblyにコンパイルする準備が整いました。次のようにemccコマンドを実行します。

emcc -s WASM=1 -Os -I mruby/include hello_ruby.c mruby/build/emscripten/lib/libmruby.a -o hello_ruby.js --closure 1

個別に説明します。

  • emcc: Emscripten Compiler Frontend(LLVMのフロントエンド)。
  • -s WASM=1: WebAssemblyをオンにする。
  • -Os: スペース削減などの最適化を行う(オプション)。
  • -I mruby/include: 必要なヘッダーを含むmruby includeディレクトリを探索する。
  • mruby/build/emscripten/lib/libmruby.a: 先ほどビルドしたmruby静的ライブラリへのパス
  • -o hello_ruby.js: 出力ファイル名。JavaScriptファイルの生成を指示するこの拡張子が重要です(後述)。
  • --closure 1: Google Closure Compilerを実行して、Emscriptenの生成したJavaScriptを最小化する。必須ではないがこれもスペース削減に有効な最適化。

hello_ruby.wasmhello_ruby.jsという2つのファイルが生成されます。完全に最適化および最小化され、全部で492 KBにしかなりません。「ここでJavaScriptを生成している理由って何?」と疑問に思うかもしれません。私もこれを最終的に置き換えるんだと思っていたのですが、それは時期尚早です。このちっぽけなJavaScriptが実はなかなか役に立つことがわかります。まず、.wasmファイルを読み込むときに<script>タグを単純に使うことはできません。そうではなく、生成されたJavaScriptコードでFetch APIを用いてwasmファイルを取ってきます(実際には、将来<script type="module">でWebAssemblyモジュールをES6モジュールのように読み込めるようにする計画があります)。生成されたJavaScriptは.wasmバイトコードをWebAssembly.Moduleにコンパイルしてインスタンス化します。関数のimportやexportも設定され、メモリが設定されてアロケーションされ、コンソール出力に使うTTYなどの他の共通サービスはJavaScriptでエミュレーションされます。やってみたい方は、先のemccコマンドに--closure 1を付けずに実行し、生成された誉れ高きJavaScriptコードを調べてみましょう。“Understanding the JS API”という記事が多少なりとも参考になるでしょう。Alonは単独のWebAssemblyをビルドするガイドも書いています。

これで先ほどのささやかなJavaScriptが必要な理由が理解できましたので、これをページに読み込むシンプルなHTMLテンプレートを書いてみましょう。このテンプレートで、私たちの苦心の成果であるhello_ruby.wasmバイナリファイルをフェッチして実行します。

<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on WebAssembly</title>
  </head>
  <body></body>
  <script src="hello_ruby.js"></script>
</html>

このファイルをhello_ruby.htmlという名前で保存します。このHTMLファイルをダブルクリックしただけでは期待どおりに動きません。cross-originの問題によってFetch APIリクエストがブロックされるからです。代わりにこのHTMLファイルをWebサーバーで公開しなければなりません。WebサーバーはRubyで簡単に実行できます。

ruby -run -ehttpd . -p8000

これでシンプルなWebサーバーが起動し、8000番ポートでカレントディレクトリの静的ファイルを公開します。http://localhost:8000/hello_ruby.htmlでHTMLファイルを表示し、ブラウザのコンソールを開けば、めでたく「Hello Ruby!」が表示されます🎉。

(いくつか質問をいただいたのでお答えします: 私はAtomエディタをCity Lights themeテーマで使っています😍)

そこそこ手のかかる作業なので、みなさんがもう少し簡単にこの実験を行える方法を編み出しました。

wasm Ruby gem

本記事のビルドプロセス全体を1個のgemに収めました。以下を実行してインストールします。

gem install wasm

このgemを使うには、まずWebAssembly toolchainをセットアップして、emccコマンドとemarコマンドを使える状態にしておかなければなりません(訳注: 上述の環境変数も設定する必要があります)。これで、ruby-wasmユーティリティでRubyスクリプトをWebAssemblyバイナリにコンパイルして上述のJavaScriptファイルやHTMLファイルを簡単に生成できるようになります。あとはruby-wasm serveでHTMLページを公開すればおしまいです。詳しくはGitHubのblacktm/ruby-wasmリポジトリのgemをご覧ください。

将来像

皆さんも私と同様、今後の行方が気になっていることでしょう。WebでRubyが走ることの意義とは一体何なのか、実際にはどんなことができるようになるのか、そうした可能性の追求は自己満足にとどまってしまうのか。その辺は私にも読みきれませんが、ひとつ言えるのは、現在の可能性の多くがWebAssemblyを強化するものであるということです。WebAssemblyの強みを雑に3つのカテゴリに分けると、パフォーマンスと効率性、ネイティブ世界とのやりとり、そして移植性になるかと思います。

パフォーマンスや効率に関しては、正しいコンパイル手順が使えるようになったことで分析や最適化が可能になっています。LLVMを用いることで、さまざまな最適化機能が既に使えるようになっていますし、今後の最適化機能の登場も期待できます。WebAssemblyは既に高速かつ高効率ですが、WebAssemblyのVMやランタイムコンポーネントも長年改良を重ねてきています。これは、サイズの小さい高速かつ高効率なRubyインタプリタがブラウザで使えるということであり、私たちが選ぶアプリがWebで使えるようになるというメリットを得られるということです。

ネイティブ世界とのやりとりについても素晴らしい可能性が秘められています。つまり、元々ネイティブコードにコンパイルするために書かれたソフトウェアにインターフェイスを持たせられるということです。それらの言語で書かれた既存のライブラリやツールは膨大な数にのぼり、WebAssemblyを用いればそれらをWebに持ち込めるようになります。そうしたライブラリが既にブラウザをターゲットとしているよい例のひとつが、Simple DirectMedia Layer(SDL)です。SDLは、グラフィックス/音声/コントローラ入力への低レベルアクセスを提供する、クロスプラットフォームのゲーム開発ライブラリです。SDLはブラウザAPIをサポートしただけでなく、今では従来のネイティブプラットフォーム向けに書かれたゲームを移植することが可能になりました。既にEpic Games’ Zen Gardenなどのクールなデモがいくつか公開されています。私たちRubyistにとっての可能性は、最もよい結果を得られる低レベルコンポーネントを引き続き使えることと、それらをRubyでより高度かつより概念的なレベルでオーケストレーションできるということです。たとえば、私が今関わっているRuby 2Dというプロジェクトがまさにそのよい例です。 これは、私がCで書いた小さなグラフィックエンジンであるSimple 2Dの上に構築したものですが、今やSDL上で構築されています。私の次のチャレンジは、Ruby 2DをWebAssemblyで動かすことです。Ruby 2Dのアーキテクチャに興味がありましたら、RubyConf 2017の私のトークをご覧ください。

最後は移植性、つまりRubyアプリやRubyインタプリタをどこにでも持ち込める機能についてです。mrubyは移植を完全に可能にするためのマジックに満ち満ちています。mrubyは、clangやLLVM、そしてあらゆるCコンパイラツールチェインをコンパイルターゲットにできるので、Rubyアプリはあらゆるプラットフォーム向けにコンパイル可能です。mrubyはこれまで小規模な組み込みコンピュータ(つまりIoTです: 私としてはAdafruitのCircuit Playgroundが早く使えるようになる日を心待ちにしていますし、Arduboyも面白そうです)に注力してきましたが、将来はずっと広がっていくでしょう。Rubyアプリの.wasmバイナリは信じられないほど移植性が高く、サーバーサイドでもクライアントサイドでも動かせます。たとえばAWS Lambdaは未だにRubyをサポートしていません(残念!)が、代わりにRubyのWebAssemblyバイナリをデプロイすればよいのです。RubyのWebAssemblyバイナリは移植性が高いだけでなく、MRI/CRubyで動くRubyスクリプトより安全かつ高速です。

行く手には興味の尽きない膨大な可能性が広がっていることは間違いありません。ただし、Ruby(や他の言語)がJavaScriptに取って代わることはないだろうと睨んでいます。既にWebブラウザには、非常に高い能力と何十年にもわたって最適化されてきたVMを持つプログラミング言語があるのです。特にES6がもたらす現代的な特典をもれなく利用できますので、JavaScriptで書くことは今や悪いことでも何でもありません。RubyをあえてWebに参入させたいのであれば、Opalという実に手堅いオプションを用いれば、RubyのJavaScriptへの統合とやりとりを信じられないほど簡単に行えます。

最後に、「ネイティブアプリ」と「Webアプリ」の定義の境界線は今後もあいまいになるだろうとだけ申し上げておきます。Progressive Web Apps(PWA)などのアプローチは、ネイティブ/Webを問わず最良の結果を開発者にもたらし、エンドユーザーが楽しむWebエクスペリエンスをさらに向上させ、Webの技術をデスクトップアプリとモバイルアプリのどちらにももたらします。「本当にそんなことが可能なのか?」という疑問はじきに無意味になるだろうと想像しています(ほぼほぼ「イエス」なのですから)。むしろ「それは本当によいものなのか、そしてそれをどう確かめたらよいのか?」という懸念点を解消する方が、ずっと困難で時間もかかるでしょう。最終的にどうなるのかは誰にもわかりませんが、この旅路は私たちを惹きつけて止まないことは確かです。

将来のWeb開発をよりよいものにしたいと考える人々には「今こそコンパイラの仕組みを学ぶべき」とアドバイスしておく —  Tom Dale

お読みいただきありがとうございます!

本記事でご紹介した、WebAssemblyでRubyを動かす方法が皆さまにとってお役に立つ情報となり、wasm gemでどしどし遊ぶようになることを願っています。ご意見/ご感想がございましたら、本記事のツイートに返信いただけます。このgemで何か面白いアイデアを思いついた方や、そうしたアイデアを知りたい方は、GitHubのissueを自由にオープンいただけます。いつものようにメールでお問い合わせいただくこともできます。

👋

関連記事

[インタビュー] Aaron Patterson(前編): GitHubとRails、日本語学習、バーベキュー(翻訳)

WebサイトをPWA(Progressive Web App)に変える簡単な手順(翻訳)

ライブコーディング入門:Sonic Pi篇パート1

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ