はじめに
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/server
とHypernova/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
を実行してインストールします。
gem "browserify-rails"
% bundle install
Installing browserify-rails 3.1.0
4. JavaScriptの環境設定 (package.json)
以下のコマンドでpackage.json
を作成し、必要なパッケージ情報などを追記します。
% npm init -y
{
"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
が読み込まれています。
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
を実行してインストールします。
gem "hypernova"
続いて、Railsアプリから先ほどのHypernova/server
へアクセスするための設定を追加します。
#{Rails.root}/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
の末尾に追記します。
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
で動作します。
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_filter
のhypernova_render_support
と、Reactコンポーネントをprops
を指定してViewに読み込むrender_react_component
です。
以下のように、使用するControllerとViewで以下のように呼び出します。
class WelcomeController < ApplicationController
around_filter :hypernova_render_support
def index
end
end
<%= render_react_component('MyComponent.js', name: 'Hypernova') %>
render_react_component
の第一引数は、Hypernova/react
のrenderReact
関数の第一引数に指定したコンポーネント名を指定します。第二引数はprops
なので、RailsからJS(Reactコンポーネント)への値の引き渡しはここで行います。
さらに、Viewで読み込むJavaScriptファイルを指定するために、app/views/layouts/application.html.erb
を修正します。javascript_include_tag
の位置を<head>
内から</body>
の直前へ移動しています。
<!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/react
のrenderReact
関数も同様の機能を備えていることが分かります。
サーバサイドレンダリング機能を有効にする
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
#airbnb 製のRailsにも対応しているJSサーバサイドレンダリング用ライブラリのHypernovaには #React 用のバインディングはあるんだけど #Redux (react-redux)用のものが無かったので書いた https://t.co/55eKymLGD4
— 内山紀明(N.UCHIYAMA) (@noriaki) June 30, 2016