Rails
react.js
reactjs
React
hypernova

RailsでReactをサーバサイドレンダリング(use Hypernova by airbnb)

More than 1 year has passed since last update.

はじめに

Railsにも対応しているJavaScriptのサーバサイドレンダリング用ライブラリairbnb/hypernovaが、airbnbからリリースされました。

この記事では、シンプルなRailsアプリを構築し、これまたシンプルなReactコンポーネントをサーバサイドレンダリングで表示するまでのチュートリアルを解説します。

ReactによるSPA(シングルページアプリケーション)も増え、Google等の検索エンジンでもある程度はページ内容を解釈してくれるらしいですが、サーバ側でHTMLをレンダリングして出力するサーバサイドレンダリングを利用すると、より正しくページ内容を解釈してもらえます。

Hypernovaとは

A service for server-side rendering your JavaScript views

Hypernovaは、JavaScriptコンポーネントをサーバサイドでレンダリングするためのサービス(ライブラリ群)です。

Hypernovaのアーキテクチャ

公式の図が分かりづらいので、シーケンス図にしてみました。

図中のHypernova関連の登場人物と、代表的な役割は以下の3つです。

登場人物 ライブラリ 役割
Hypernova/ruby airbnb/hypernova-ruby HypernovaのRubyクライアントでRails用のFilterやHelper methodを提供します
Hypernova/server airbnb/hypernova Hypernovaのサーバサイドレンダリングを担うエンジンで、コンポーネントを読み込んでHTML化します
Hypernova/react airbnb/hypernova-react Reactコンポーネント用のバインディングで、Hypernova/server用のインタフェースを提供するとともに直接ブラウザで読み込まれても大丈夫な機構(fall-back)を備えます

HypernovaのコアはHypernova/serverHypernova/reactの2つで、前者がサーバサイドレンダリングを担い、後者がReactDOM.renderのラッパーでReactコンポーネントをUniversal化(サーバ側でもブラウザ側でも実行可能にすること)します。

また、サーバサイドレンダリングを利用して表示したページでも、ユーザの操作によるReactコンポーネントの変化が正しく動作するよう設計・実装されています。

Hypernovaを使ったサーバサイドレンダリングのチュートリアル

それでは、実際にHypernovaによるReactコンポーネントをサーバサイドレンダリングするRailsアプリを作成していきます。

環境・バージョン

# mac
MacBook Air (13-inch, Mid 2012) - OS X El Capitan (10.11.5)

# ruby/rails
% ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
% gem -v
2.5.1
% rails -v
Rails 4.2.6

# node/npm
% node -v
v5.5.0
% npm -v
3.7.1

1. Railsアプリの作成

特別なオプションなどは不要です。

% rails new hypernova-getting-started-with-rails

2. Controllerの作成

/welcome/indexにアクセスしたときに動作するようにします。

% bin/rails generate controller welcome index

3. browserify-railsの導入

Railsアプリ上でCommonJSを動作させるためにbrowserify-railsを導入します。
browserify-railsに限らず、Reactコンポーネントがブラウザで動作するようにできれば何でも良いですが、今回はもっとも簡単なbrowserify-railsを選びました。

Gemfileに以下1行を追加し、bundle installを実行してインストールします。

Gemfile
gem "browserify-rails"
% bundle install
Installing browserify-rails 3.1.0

4. JavaScriptの環境設定 (package.json)

以下のコマンドでpackage.jsonを作成し、必要なパッケージ情報などを追記します。

% npm init -y
package.json
{
  "name": "hypernova-getting-started-with-rails",
  "version": "1.0.0",
  "description": "A sample Rails application that uses Hypernova to server render.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\""
  },
  "author": "Noriaki Uchiyama",
  "license": "MIT",
  "dependencies": {
    "hypernova": "^1.0.0",
    "hypernova-react": "^1.0.0",
    "react": "^15.1.0",
    "react-dom": "^15.1.0"
  },
  "devDependencies": {
    "browserify": "^13.0.1",
    "browserify-incremental": "^3.1.1"
  }
}

package.jsonが用意できたら以下のコマンドを実行しJavaScriptのパッケージをインストールします。

% npm install

5. Hypernova/serverの設定

Rails.rootディレクトリにhypernova.jsを作ります。
これは、Hypernova/serverにあたるもので、コンポーネント名を受け取りレンダリングしたHTMLを返すサーバです。内部ではexpressが読み込まれています。

hypernova.js
const hypernova = require('hypernova/server');

hypernova({
  devMode: true,

  getComponent(name) {
    if (name === 'MyComponent.js') {
      return require('./app/assets/javascripts/MyComponent.js');
    }
    return null;
  },

  port: 3030,
});

6. Hypernova/rubyの設定

Ruby(Rails)用のHypernovaクライアントを読み込みます。

Gemfileに以下1行を追加し、bundle installを実行してインストールします。

Gemfile
gem "hypernova"

続いて、Railsアプリから先ほどのHypernova/serverへアクセスするための設定を追加します。

#{Rails.root}/config/initializers/hypernova.rbとして以下を作成します。

config/initializers/hypernova.rb
require 'hypernova'

Hypernova.configure do |config|
  config.host = 'localhost'
  config.port = 3030
end

config.portには先ほどhypernova.jsに記載したポート番号を指定します。

その他、これは必須ではありませんが、エラー発生時にブラウザ上でスタックトレースが表示される設定も行います。下記をRailsアプリの設定ファイルに追記します。今回は開発環境用のconfig/environments/development.rbの末尾に追記します。

config/environments/development.rb
require 'hypernova'
require 'hypernova/plugins/development_mode_plugin'

Hypernova.add_plugin!(DevelopmentModePlugin.new)

これは、Hypernova/rubyに同梱されているDevelopment mode pluginを利用しています。

7. Hypernova/reactを使用したReactコンポーネント

props.nameを受け取って表示し、クリックするとアラートを表示するだけのシンプルなReactコンポーネントを作成します。ファイルは#{Rails.root}/app/assets/javascripts/MyComponent.jsというファイル名としています。

CommonJSで動作するように関数型のReactコンポーネントを使っていますが、React.createClassでも、(トランスパイラを利用すれば)ES2015のクラス構文を利用したclass MyComponent exntends React.Component {の書き方でもHypernovaで動作します。

app/assets/javascripts/MyComponent.js
var React = require('react');
var renderReact = require('hypernova-react').renderReact;

function MyComponent(props) {
  return React.createElement('div', {
    onClick: function () {
      alert('Click handlers work.');
    },
  }, 'Hello, ' + props.name + '!');
}

module.exports = renderReact('MyComponent.js', MyComponent);

ここで重要なのはrenderReact関数で、これがHypernova/reactの提供する関数です。第一引数にコンポーネント名(ユニークな文字列ならなんでもOK)と、第二引数にコンポーネントを受け取り、Hypernova用のラッパー関数を返します。

ここで第一引数に指定しているコンポーネント名が、先ほどのHypernova/serverが受け取るコンポーネント名と一致している必要があり、後ほど出てくるRailsのViewで呼び出される際にも指定されます。

8. RailsアプリからHypernova呼び出し

Hypernova/rubyは2つのmethodを提供します。Hypernovaによるサーバサイドレンダリングをサポートするaround_filterhypernova_render_supportと、Reactコンポーネントをpropsを指定してViewに読み込むrender_react_componentです。

以下のように、使用するControllerとViewで以下のように呼び出します。

app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
  around_filter :hypernova_render_support
  def index
  end
end
app/views/welcome/index.html.erb
<%= render_react_component('MyComponent.js', name: 'Hypernova') %>

render_react_componentの第一引数は、Hypernova/reactrenderReact関数の第一引数に指定したコンポーネント名を指定します。第二引数はpropsなので、RailsからJS(Reactコンポーネント)への値の引き渡しはここで行います。

さらに、Viewで読み込むJavaScriptファイルを指定するために、app/views/layouts/application.html.erbを修正します。javascript_include_tagの位置を<head>内から</body>の直前へ移動しています。

app/views/layouts/application.html.erb
 <!DOCTYPE html>
 <html>
   <head>
     <title>HypernovaGettingStartedWithRails</title>
     <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
-    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
     <%= csrf_meta_tags %>
   </head>
   <body>

   <%= yield %>

+  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
   </body>
 </html>

9. 動作を確認してみる

Railsアプリを起動します。

% bin/rails server

起動したら、ブラウザで http://localhost:3000/welcome/index へアクセスし、Hello, Hypernova!と表示され、文字をクリックするとブラウザアラートボックスが表示されることが確認できるでしょう。

ページのソースを確認してみると、該当部分は以下のようになっていると思います。

<div data-hypernova-key="MyComponentjs"></div>
<script type="application/json" data-hypernova-key="MyComponentjs"><!--{"name":"Hypernova"}--></script>

これは、 サーバサイドレンダリングを利用していない のですが、正しくアプリは動作しています。

通常、ReactコンポーネントをDOMに描画する場合は、ReactDOM.render関数などを利用しますが、(7)のHypernova/reactrenderReact関数も同様の機能を備えていることが分かります。

サーバサイドレンダリング機能を有効にする

Hypernova/serverサービスを起動すると、他のコードは変更しなくてもサーバサイドレンダリングが有効になります。サービスの起動はnode hypernova.jsをコマンドラインから実行します。

% node hypernova.js
2016-07-02T12:18:23.437Z - info: Connected
{ port: 3030 }

正常にサービスが起動すると上記のようにConnectedと表示されます。

この状態で再度Railsアプリ http://localhost:3000/welcome/index へアクセスすると、表示内容やクリック時の動作はサーバサイドレンダリングを利用しないときと同じですが、ページのソースを確認すると以下のように直接HTMLにコンポーネントが出力され サーバサイドレンダリングを利用している ことが分かります。

<div data-hypernova-key="MyComponentjs"><div data-reactroot="" data-reactid="1" data-react-checksum="-1229122185">Hello, Hypernova!</div></div>
<script type="application/json" data-hypernova-key="MyComponentjs"><!--{"name":"Hypernova"}--></script>

という感じで、簡単にサーバサイドレンダリングを利用できるようになりました。

さいごに

本記事では、airbnb/hypernovaを使ってRailsアプリでReactコンポーネントのサーバサイドレンダリングを実現するチュートリアルを説明しました。

Hypernovaを使うと、通常じぶんで実装する必要のある「サーバ側で呼び出されたときのレンダリング処理」と「ブラウザ側で呼び出されたときのレンダリング処理」と「その分岐」をライブラリが肩代わりしてくれるため、意識せずともユニバーサルなReactコンポーネントを実装することができ大変楽ちんです。

ぜひ試してみてください。
もしお役に立ちそうなら「ストック」してもらえると嬉しいです。

次回は、HypernovaによるサーバサイドレンダリングをReduxコンテナに適用する方法を書く予定です。お楽しみに。

書きました:RailsでReduxコンテナをサーバサイドレンダリング (use Hypernova by airbnb) - Qiita

参考