RailsとReact + Webpackでサーバサイドレンダリング

WebpackでReactをビルドしたものをRailsでサーバサイドレンダリングする方法をまとめました。

## Webpack側

```bash
$ npm i -D webpack babel babel-loader babel-core
$ npm i -D babel-preset-react babel-preset-es2015
```

Reactのコンポーネントのテストを書く場合は`react`と`react-dom`をインストールする必要があります。
そうでない場合はインストールしなくても、react-railsにこれらが含まれているので問題ありません。

### Webpackの設定
Reactのコンポーネントを`react`ディレクトリに配置して、ビルドしたものは`app/assets/javascripts/components`に出力されるようにします。
`react`と`react-dom`はreact-railsのものを利用するので、externalsに設定しておきます。

```js:webpack.config.js
const DEBUG = !process.argv.includes('--release');

module.exports = {
  cache: DEBUG,
  debug: DEBUG,

  entry: {
    Hoge: './react/Hoge.jsx',
  },
  output: { path: './app/assets/javascripts/components', filename: '[name].js' },
  devtool: DEBUG ? 'inline-source-map' : false,
  module: {
    loaders: [
      {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      }
    ]
  },
  resolve: {
    extensions: ['', '.js', '.jsx'],
  },
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  },
}
```

### Reactのコンポーネント
Reactのコンポーネントは以下のように、サーバサイドレンダリングするオブジェクトをwindowのプロパティとして出力します。

```js:react/Hoge.jsx
import React from 'react';

class Hoge extends React.Component {
  render() {
    return (
      <div>
        hogehoge
      </div>
    )
  }
}

window.Hoge = Hoge;
```

### package.json
package.jsonにはscriptを設定します。
開発する時は`npm run build`や`npm run watch`コマンドを使うといいと思います。

```json:package.json
{
  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch",
    "release": "webpack --release"
  },
}
```

## Rails側

### react-railsをインストール
```:Gemfile
gem 'react-rails'
```

```bash
$ bundle install
$ bundle exec rails g react:install
```

`config.react.variant`を設定

```diff:config/environments/development.rb
 Rails.application.configure do
+  config.react.variant = :development
 end
```

```diff:config/environments/production.rb
 Rails.application.configure do
+  config.react.variant = :production
 end
```

#### production環境の設定

server_rendering.jsを新たに作成します。

```js:app/assets/javascripts/server_rendering.js
//= require react-server
//= require components
```

それぞれのファイルに追記します。

```ruby:config/initializers/assets.rb
Rails.application.config.assets.precompile += %w( server_rendering.js )
```

```diff:config/environments/production.rb
 Rails.application.configure do
   config.react.variant = :production
+  config.react.server_renderer_options = {
+    files: ["server_rendering.js"],
+  }
 end
```

Precompile時にWebpackでビルドするために、以下のファイルを新たに作成します。

```ruby:lib/tasks/before_precompile.rake
task :build_react do
  sh "npm run release"
end

Rake::Task["assets:precompile"].enhance(%i(build_react))
```

### Reactのコンポーネントのレンダリング
react_componentに表示したいコンポーネントと、引数を渡します。
題3引数に`{prerender: true}`を渡してサーバサイドレンダリングを有効にしています。

```erb:index.html.erb
<%= react_component('Hoge', {}, {prerender: true}) %>
```

## おわりに

今回編集および追加したファイルは以下のようになっています。

```
rails_root
├── app/
│   └── assets
│       └── javascripts
│           ├── application.js
│           ├── components
│           ├── components.js
│           └── server_rendering.js
├── config/
│   ├── environments
│   │   ├── development.rb
│   │   └── production.rb
│   └── initializers
│       └── assets.rb
├── lib/
│   └── tasks
│       └── before_precompile.rake
├── react/
│   └── Hoge.jsx
├── Gemfile
├── package.json
└── webpack.config.js
```

`app/assets/javascripts/components`はビルドするたびに変更が発生するので、gitを使って複数人で開発しているとコンフリクトしやすいです。
gitignoreに`app/assets/javascripts/components`以下のファイルを無視するように設定しておくべきかもしれません。

## 参考
* [RailsでReactを使ってメッセージボックスアプリを作成 - Rails Webook](http://ruby-rails.hatenadiary.com/entry/20151122/1448118932)
* [react-railsでサーバーレンダリングを使ってproduction環境で起動するとTemplate::Errorになる](http://qiita.com/akirakudo/items/90d7ffa5726083e1db60)
* [WebPackを使ってRailsからJavaScriptを楽に良い感じに分離する](http://qiita.com/necojackarc/items/afa674ab10aafa9784eb)