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を開発する上で多大な協力をいただいたので、この場を借りて感謝したい。
ありがとうございます。引き続きよろしくお願いします。