1. Qiita
  2. 投稿
  3. Ruby

SinatraでReactを使うためのgemを書いた

  • 3
    いいね
  • 0
    コメント
に投稿

ReactJSが取り沙汰されるようになって久しい昨今、
Ruby界隈ではRails対応のgem、もしくはプラグインしか存在しておらず、
未だにSinatraやPadrinoでReactJSを(サーバサイドレンダリングも含め)使うためのgemがないことに絶望したので自分で書いた。

個人的にはサーバサイドレンダリングは筋が良い技術だとは到底思えないし、廃れゆくべき技術だと思っているが、
個人開発においてどうしても必要になったため、実装した次第である。

tl; dr;

namusyaka/react-sinatraというgemを書いた。
SinatraでReact.jsを用いたサーバサイドレンダリングができるようになる。

サンプルはnamusyaka/react-sinatra-sampleを参照。
このgemを動かすにあたって必要になるnamusyaka/react-sinatra-ujsというnpm packageも書いた。

考え方

やらないこと

react-railsが実装しているようないくつかの機能は、SinatraにSprocketsのようなデファクトスタンダードな実装がないことはもとより、Assetの運用はもはやwebpackでやらない理由がないため実装していない。

以下にやらないことを列挙する。

  • .jsxからjsへのtransformをしない
  • babelを用いるようなtranspileをしない
  • sprocketsやasset pipelineのサポートをしない

基本的にはwebpack(その実babelなんだろうけど)などを使ってコンパイルしたjavascriptをそのままサーバサイドのランタイムに食わせれば良い。

また、テンプレートファイルを用いるジェネレータも持っていない。

Sinatraにおけるアプリケーションの雛形はあってないようなものなので、不要。
Padrinoについてはpadrino recipesというものがあるので、そちらに今後追加する可能性はある。

使い方

基本的にreact_componentをどう使うか、という話になる。

:prerenderオプションなし (サーバサイドレンダリングをしない場合)

描画までの流れとしてはpadrino-helpersを使って、data-react-classdata-react-props属性を持つ要素を描画するだけである。
react-sinatraのconfigurationは全てがサーバサイドレンダリング向けの設定に終始するため、このケースだと小難しい設定は不要で、React::Sinatraregisterするだけでreact_componentヘルパーが使えるようになる。

class App < Sinatra::Base
  register React::Sinatra

  get ?/ do
    react_component('HelloApp', {})
  end
end

そうして描画した要素をクライアント側でnamusyaka/react-sinatra-ujsをロードすることで、ReactDOM.renderが該当要素を取得・描画していく。

:prerenderオプションあり (サーバサイドレンダリングをする場合)

こちらが本懐である。
あらかじめSinatraアプリケーションを起動するタイミングで、server上のJavaScript Runtime向けにビルドされたJavaScriptを読み込ませる必要があるなど、設定は多少面倒になる。
設定項目は次の通り。

設定項目

項目 内容 デフォルト
use_bundled_react react-sinatraにbundleされたreactjsソースコードをサーバサイドレンダリング時に使うかどうか false
env アプリケーションの環境。 ENV['RACK_ENV']
addon ReactJSのaddon機能を要するかどうか。これはuse_bundled_reacttrueの時以外関係ない。 false
asset_path サーバ上のJavaScript Runtimeが読み込むJavaScriptのコード。webpackなどでビルドしたファイルを読み込ませる。 nil
runtime サーバ上で動かすJavaScript Runtimeをsymbolで設定する。この記事を書いた時点では:execjsしか存在しない。 :execjs
pool_size JavaScript Runtimeを用いる際のConnectionPoolの設定値。 5
pool_timeout JavaScript Runtimeを用いる際のConnectionPoolの設定値。 10

ベースコードは次のようなイメージ。あくまでサンプルとして参考にしてほしい。

class App < Sinatra::Base
  register React::Sinatra

  configure do
    React::Sinatra.configure do |config|
      config.use_bundled_react = true
      config.env = ENV['RACK_ENV'] || :development
      config.addon = true

      config.asset_path = File.join('client', 'dist', 'server.js')
      config.runtime = :execjs
    end
  end

  get ?/ do
    react_component('HelloApp', {}, prerender: true)
  end
end

今後の展望

NodeJSをruntimeとして使えるようにする

therubyracermini_racerに代表されるruby界隈のJavaScript Runtimeはexecjs経由でとりあえずは使えるので、現状はexecjsしかサポートしていない。
が、フロントエンドエンジニア的にはサーバサイド上でもNodeでコードを実行できた方が何かと嬉しい気がしている。

例えばreact_on_railsではすでにそのような機能があり、サーバ上のnodeデーモンとunix domain socketを介して通信を行うことで、サーバサイドレンダリングを実現していたりする。
個人的にはnodeのデーモンをそのためだけに飼って、死活監視諸々の項目を抑えて面倒見るほどの嬉しさがあるのか、という点について懐疑的なので現状はサポートしていないが、後々そういった機能追加があるかもしれない。

運用実績を貯める

まだまだ個人サービスレベルでしか導入できていないが、今後は少し大きめのサービスのproductionにも突っ込んで様子を見てみたいと考えている。

おわりに

サーバサイドレンダリングにはいろいろと思うところがあるし、こういった技術が必要になるのは過渡期ならではなのではないか、とも思う。
なにはともあれ、せっかく作ったわけなので、Sinatra-erな方やPadrino Loverな方は是非、使ってみてほしい。

また、前職の同僚でJavaScripterである@jyane@about_hiroppyにはreact-sinatraを開発する上で多大な協力をいただいたので、この場を借りて感謝したい。
ありがとうございます。引き続きよろしくお願いします。