こんにちは、 @t0yohei です。
ruby.wasm は、Ruby のコードを Wasm に変換しブラウザー上で実行できるようにする技術です。 今回は、この ruby.wasm を使用してちょっとした web アプリを作成したので、そのことについて記載してみようと思います。
※ この記事は、RubyKaigi 2024 のクラウドワークス社ブースにて展示するクイズアプリの解説記事です。
目次
作ったもの
Ruby に関するクイズを出題して、それに答えてもらうクイズアプリを作成しました。 RubyKaigi でのクラウドワークス社ブースにてこのクイズアプリに挑戦してもらい、正答数に応じてノベルティをお渡しするという目論見です。
- アプリページ: Ruby Quize at RubyKaigi 2024
- リポジトリ:
クイズアプリの解説
大まかな仕組み
今回は初めての ruby.wasm を使用したアプリケーション実装ということで、複雑さ回避のためにバックエンドは用意せず、フロントエンドのみのアプリケーションとして作成しました。
問題文・解答は html ページに埋め込まれており、開発者ツールを使用すると解答が丸分かりという形になっています。 そのため、ブース訪問者自身のデバイスでクイズに挑戦してもらう形式ではなく、こちらで用意した PC で挑戦してもらう形式にしました。
使わせてもらったライブラリ
今回は ruby-wasm-vdom を使用させていただきました。 普段の業務では Vue.js を使用してフロントエンド開発をすることが多く、それと近い宣言的なスタイルでの UI 実装を行いたかったためです。
ruby-wasm-vdom
を使用することで、完璧にとは行かないものの基本的な宣言的 UI の実装ができたのでとても助かりました。
i18n 対応
RubyKaigi は海外の方もたくさんいらっしゃるということで、i18n 対応も行いました。 仕組みとしては簡素なもので、以下のような hash を用意して使用時に読み取るだけです。
I18N = { ja: { question: '問題', explanation: '解説', }, en: { question: 'Question', explanation: 'Explanation', } }
問題について
問題については、同僚の @slowhand221b さんに作成いただきました。 可能な限り Ruby 初心者から上級者まで楽しめる問題にしたい、という自分の意向を組んで問題を用意して下さりとても助かりました。ありがとうございました!
デプロイ先
今回は Netlify を使用しました。 個人の private リポジトリで開発を進めていた関係で、GitHub Pages が使用できず、代替の手段として利用させて頂きました。
難しかったこと
タイマーの部分の実装
1 秒毎に画面に表示されている数字を 1 ずつ減らしていくという部分で、ruby の sleep や ruby.wasm の JS.global.call
を使用したコードだけではどうしても実現できず、JavaScript で実装しました。
ruby-wasm-vdom
で管理している状態の更新も絡んできていることも難しさの一因でした。
Ruby のコード規約、JavaScript のコード規約の使い分け
キャメルケース・パスカルケースの使い分けや、引数を持たないメソッド呼び出しに ()
をつけるかなどといった Ruby・JavaScript のコード規約の制御が悩みどころでした。
Ruby のコードの中で JavaScript のようなコードを実装することがあり、このような形になってしまったのだと予想しています。
例えば、以下は画面表示用の Ruby コードなのですが、 getElementById
を使用した DOM の操作の部分は JavaScript のようなコードを実装しています。この部分は JavaScript のようなコードなので、パスカルケースではなくキャメルケースになります。
戻り値の格納先である変数 appEl
は ruby の変数ですが、DOM から取得した値の格納先なのでキャメルケースにしています。
このような使い分けの積み重ねにより、Ruby のコード内にキャメルケースの登場シーンが増えていき、段々制御が上手く出来なくなっていきました...(結果として ruby のメソッド名である renderApp
も気づいたら何故かキャメルケースになっちゃってました)。
def renderApp(state:, view:, actions:) appEl = JS.global[:document].getElementById('app'); children = appEl[:children] if children[:length].to_i > 0 children[0].remove() end render = lambda { RubyWasmVdom::App.new( el: "#app", state:, view:, actions: ) } JS.global.call(:setTimeout, JS.try_convert(render)) end
サンプルコード探し
ruby.wasm はまだ web 上に存在する実装サンプルも少なく、この部分はどう実装すれば良いんだろうと迷う部分も多々ありました。 ruby.wasm のリポジトリ内の Community Showcaseにあるコードを実際に見て、参考にさせてもらいました。
感想
今回、ブースコンテンツとして ruby.wasm を使用したクイズアプリを作ってみましたが、Ruby を使ってフロントエンドを書くのはとても楽しかったです! その分、もっと不自由なく宣言的 UI が実現できるようなフレームワークがあるといいなという気持ちが高まりました。
また、今回のようなクイズアプリだとそこまで ruby.wasm を利用する直接的なメリットは大きくないのかなと感じました。 コードゴルフのような Ruby のコードを入力してもらって、そのコードを評価するみたいな方向性だと ruby.wasm の真価が発揮されそうな予感がします。 次にまたブースコンテンツを作成する機会があれば、そのようなアプリ作成に挑戦してみたいと思います。
以上です。お読み頂きありがとうございました。