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導入
+gem "react_on_rails", "~> 6"
$ bundle
git commit
ここでの注意点は、次に実行するrails g react_on_rails:install
は、git status
の結果が綺麗になっていないとこける仕様になっていること。なので一旦commitする必要がある。
- 参考: react_on_rails実装
- この中の
--porcelain
オプションについては先述
- この中の
$ 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
..
シーケンスは次の通り。
-
package.json
からforemanを起動- foremanは
Procfile
ベースでアプリケーション管理するRuby製ツール- JSのコードベースの変更を監視して文法エラーなどの警告も行う
- foremanは
- foremanが
Procfile.dev
を読みRailsサーバーを起動、同時にclient/package.json
をたどってwebpackを起動 - webpackが、
webpack.config.js
に書かれたHelloWorldApp.jsx
をエントリポイントにJSをバンドルし、
./app/assets/webpack/webpack-bundle.js
を生成
- 生成されたファイルは
.gitignore
されている
- Railsが、生成された
webpack-bundle.js
を使う
- Railsとのインターフェースに使われる(=
react_component
で呼ばれる)全てのReactコンポーネントが、ReactOnRails.register
を使って一意な名前で公開されている必要がある
動作確認
http://localhost:3000/hello_world
で動作確認する。
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