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

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

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

# tl; dr;

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

サンプルは[namusyaka/react-sinatra-sample](https://github.com/namusyaka/react-sinatra-sample)を参照。
このgemを動かすにあたって必要になる[namusyaka/react-sinatra-ujs](https://github.com/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](https://github.com/padrino/padrino-recipes)というものがあるので、そちらに今後追加する可能性はある。


# 使い方

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

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

描画までの流れとしては[padrino-helpers](https://github.com/padrino/padrino-framework/tree/master/padrino-helpers)を使って、`data-react-class`・`data-react-props`属性を持つ要素を描画するだけである。
`react-sinatra`のconfigurationは全てがサーバサイドレンダリング向けの設定に終始するため、このケースだと小難しい設定は不要で、`React::Sinatra`を`register`するだけで`react_component`ヘルパーが使えるようになる。

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

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

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

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

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

### 設定項目

|項目  |内容 |デフォルト  |
|---|---|---|
|use_bundled_react  |`react-sinatra`にbundleされたreactjsソースコードをサーバサイドレンダリング時に使うかどうか  |`false`  |
|env  | アプリケーションの環境。 | ENV['RACK_ENV'] || :development |
|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 |


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

```ruby
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](https://github.com/shakacode/react_on_rails)ではすでに[そのような機能](https://github.com/shakacode/react_on_rails/blob/master/docs/additional-reading/node-server-rendering.md)があり、サーバ上のnodeデーモンとunix domain socketを介して通信を行うことで、サーバサイドレンダリングを実現していたりする。
個人的にはnodeのデーモンをそのためだけに飼って、死活監視諸々の項目を抑えて面倒見るほどの嬉しさがあるのか、という点について懐疑的なので現状はサポートしていないが、後々そういった機能追加があるかもしれない。

## 運用実績を貯める

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

# おわりに

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

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