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-class・data-react-props属性を持つ要素を描画するだけである。
react-sinatraのconfigurationは全てがサーバサイドレンダリング向けの設定に終始するため、このケースだと小難しい設定は不要で、React::Sinatraをregisterするだけで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_reactがtrueの時以外関係ない。 |
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として使えるようにする
therubyracer・mini_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を開発する上で多大な協力をいただいたので、この場を借りて感謝したい。
ありがとうございます。引き続きよろしくお願いします。