react_on_rails導入



react_on_rails

- https://github.com/shakacode/react_on_rails

RailsでReactを使う場合に検討するgemとして、導入が容易なのは`react-rails`。一方、`react_on_rails`はWebpack, Babel, React, Redux, React-RouterなどのモダンJSスタックをRailsと密結合にならない形で導入したい状況を想定しているようだ。


`react_on_rails`を使った新規アプリをHerokuへデプロイするフローを確認する。

# 環境
- Ruby 2.2.3
- Rails 4.2




# ローカルで動かす



## 空のアプリ作成
```
$ rails new APP_NAME --database=postgresql
$ cd APP_NAME
$ rake db:create
$ rails s
```

```
$ git init
$ git add .
$ git commit -m'initial'
```

## react_on_rails導入
```Gemfile
+gem "react_on_rails", "~> 6"
```

```
$ bundle
```

## git commit
ここでの注意点は、次に実行する`rails g react_on_rails:install`は、`git status`の結果が綺麗になっていないとこける仕様になっていること。なので一旦commitする必要がある。

- 参考: [react_on_rails実装](https://github.com/shakacode/react_on_rails/blob/0ca17c9074835406ba0c329b7312ab18aff05ab0/lib/react_on_rails/git_utils.rb)
	- この中の`--porcelain`オプションについては[先述](http://qiita.com/satzz/items/f75dd2e096b4c1755227)

```
$ git status
$ git add .
$ git commit -m'install react_on_rails'
```
  
## react_on_rails起動
```
$ rails g react_on_rails:install
```

サンプルコードを含めていろいろファイルが自動修正・作成される。

```
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   .gitignore
        modified:   Gemfile
        modified:   Gemfile.lock
        modified:   app/assets/javascripts/application.js
        modified:   config/initializers/assets.rb
        modified:   config/routes.rb

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        Procfile.dev
        app/controllers/hello_world_controller.rb
        app/views/hello_world/
        client/
        config/initializers/react_on_rails.rb
        package.json
```

少し複雑なので、主なファイルを追っておく。


### Webpackでコンポーネントをバンドルするための変更

```package.json
{
  "name": "react-webpack-rails-tutorial",
  "version": "0.0.1",
  "engines": {
    "node": "5.10.0",
    "npm": "3.5.0"
  },
  "scripts": {
    "postinstall": "cd client && npm install",
    "rails-server": "echo 'visit http://localhost:3000/hello_world' && foreman start -f Procfile.dev",
    "test": "rspec"
  }
}
```

```Procfile.dev 
web: rails s -p 3000
client: sh -c 'rm app/assets/webpack/* || true && cd client && npm run build:development'
```


```client/package.json
{
  "name": "react-webpack-rails-tutorial",
  "version": "0.0.1",
  "engines": {
    "node": "5.10.0",
    "npm": "3.5.0"
  },
  "scripts": {
    "build:test": "webpack --config webpack.config.js",
    "build:production": "NODE_ENV=production webpack --config webpack.config.js",
    "build:development": "webpack -w --config webpack.config.js"
  },
..
```

```client/webpack.config.js
const webpack = require('webpack');
const path = require('path');

const devBuild = process.env.NODE_ENV !== 'production';
const nodeEnv = devBuild ? 'development' : 'production';

const config = {
  entry: [
    'es5-shim/es5-shim',
    'es5-shim/es5-sham',
    'babel-polyfill',
    './app/bundles/HelloWorld/startup/HelloWorldApp',
  ],

  output: {
    filename: 'webpack-bundle.js',
    path: '../app/assets/webpack',
  },

..
```

### Reactコンポーネントとエントリポイント
```client/app/bundles/HelloWorld/startup/HelloWorldApp.jsx 
import React from 'react';
import ReactOnRails from 'react-on-rails';

import HelloWorld from '../containers/HelloWorld';

const HelloWorldApp = (props) => (
  <HelloWorld {...props} />
);

// This is how react_on_rails can see the HelloWorldApp in the browser.
ReactOnRails.register({ HelloWorldApp });
```
```client/app/bundles/HelloWorld/containers/HelloWorld.jsx 
import React, { PropTypes } from 'react';
import HelloWorldWidget from '../components/HelloWorldWidget';

// Simple example of a React "smart" component
export default class HelloWorld extends React.Component {
  static propTypes = {
    name: PropTypes.string.isRequired, // this is passed from the Rails view
  };
  ..
}
```

### WebpackでバンドルしたJSを使うための変更

```config/initializers/react_on_rails.rb
ReactOnRails.configure do |config|
  config.generated_assets_dir = File.join(%w(app assets webpack))
  config.webpack_generated_files = %w( webpack-bundle.js )
  config.server_bundle_js_file = "webpack-bundle.js"
  config.npm_build_test_command = "npm run build:test"
  config.npm_build_production_command = "npm run build:production"
  config.prerender = false
  config.trace = Rails.env.development?
  config.development_mode = Rails.env.development?
  config.replay_console = true
  config.logging_on_server = true
  config.raise_on_prerender_error = false # change to true to raise exception on server if the JS code throws
  config.server_renderer_pool_size = 1 # increase if you're on JRuby
  config.server_renderer_timeout = 20 # seconds
  config.skip_display_none = false
  config.server_render_method = "ExecJS"
  config.symlink_non_digested_assets_regex = /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg|map)/
end
```


```config/initializers/assets.rb
+Rails.application.config.assets.paths << Rails.root.join("app", "assets", "webpack")
```


```app/assets/javascripts/application.js
+//= require webpack-bundle
```

### Railsアプリケーションの変更

```config/routes.rb
+  get 'hello_world', to: 'hello_world#index'
```

```app/controllers/hello_world_controller.rb
class HelloWorldController < ApplicationController
  def index
    @hello_world_props = { name: "Stranger" }
  end
end
```
```app/views/hello_world/index.html.erb 
<h1 class="alert alert-info this-works">Hello World</h1>
<%= react_component("HelloWorldApp", props: @hello_world_props, prerender: false) %>
```



## 起動

さらに必要なライブラリをインストールする。

```
$ bundle && npm i
```
起動

```
$ npm run rails-server

> react-webpack-rails-tutorial@0.0.1 rails-server /Users/satzz/APP_NAME
> echo 'visit http://localhost:3000/hello_world' && foreman start -f Procfile.dev

visit http://localhost:3000/hello_world
..
```
シーケンスは次の通り。

1. `package.json`からforemanを起動
	- foremanは`Procfile`ベースでアプリケーション管理するRuby製ツール
		- JSのコードベースの変更を監視して文法エラーなどの警告も行う
1. foremanが`Procfile.dev`を読みRailsサーバーを起動、同時に`client/package.json`をたどってwebpackを起動 
1. webpackが、`webpack.config.js`に書かれた`HelloWorldApp.jsx`をエントリポイントにJSをバンドルし、
`./app/assets/webpack/webpack-bundle.js`を生成
 - 生成されたファイルは`.gitignore`されている
1. Railsが、生成された`webpack-bundle.js`を使う
 - Railsとのインターフェースに使われる(=`react_component`で呼ばれる)全てのReactコンポーネントが、`ReactOnRails.register`を使って一意な名前で公開されている必要がある

## 動作確認

http://localhost:3000/hello_world
で動作確認する。

![Screen Shot 2016-11-01 at 11.13.56.png](https://qiita-image-store.s3.amazonaws.com/0/281/5e11f03f-a342-599a-21e1-798bc2f850cf.png "Screen Shot 2016-11-01 at 11.13.56.png")






# Herokuで動かす
```
$ git add .
$ git commit -m'make react work'

$ heroku create
```

Buildpackの順番調整

```
$ heroku buildpacks:clear --app dark-wizard-97318
$ heroku buildpacks:add heroku/nodejs --app dark-wizard-97318
$ heroku buildpacks:add heroku/ruby --app dark-wizard-97318
```

デプロイ

```
$ git push https://git.heroku.com/dark-wizard-97318.git master
```