Rails 5.1からwebpacker gemを使う事で、フロント部分にReactやAngularを手軽に使えるようになりました。

しかし、開発速度やリリース頻度、文化の違うシステムをラッピングして他のシステムに同梱するというのは非常に難しく、バージョンアップについて行けないとか、新しい機能が使えないという状況になりがちです。

2017年末現在、フロントエンドのビルドはWebpackerが主流ですが、ちょっと前までGulpやGruntsが主流でした。RailsからWebpackが簡単に呼べるようになりましたが、Webpackに依存しないためDockerを使ってRailsとReact(Node)を使う方法を紹介します。

Dockerを使ってRails環境を作る

これは色々なページで紹介されているので、詳細は割愛して設定ファイルだけ載せます。

Dockerfile

FROM ruby:2.4.2
ENV LANG C.UTF-8

RUN apt-get update && \
    apt-get install -y nodejs mysql-client --no-install-recommends && \
    rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app

COPY Gemfile /usr/src/app/
COPY Gemfile.lock /usr/src/app/
RUN bundle install

COPY . /usr/src/app

EXPOSE 3000
CMD ["./bin/rails", "server", "-b", "0.0.0.0"]

docker-compose.yml

docker-compose.yml
rails:
  build: .
  command:
    [ "bash", "-c", "rm -f tmp/pids/server.pid; ./bin/rails server -b 0.0.0.0" ]
  environment:
    DISABLE_SPRING: "1"
  ports:
    - "3000:3000"
  volumes:
    - ".:/usr/src/app"
  links:
    - "mysql"

mysql:
  image: mysql:5.6
  environment:
    MYSQL_ROOT_PASSWORD: "passwd"

プロジェクト作成

echo "source 'https://rubygems.org'" > Gemfile
echo "gem 'rails'" >> Gemfile
touch Gemfile.lock
docker-compose build
docker-compose run rails rails new . --force --database=mysql --skip-bundle
docker-compose build

database設定

config/database.ymlを書き換えます

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: "passwd"
  host: mysql

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: <%= ENV['DATABASE_NAME'] %>
  username: <%= ENV['DATABASE_USERNAME'] %>
  password: <%= ENV['DATABASE_PASSWORD'] %>
  host: <%= ENV['DATABASE_HOST'] %>

初期化して起動

docker-compose run rails rails db:setup
docker-compose run rails server

これで http://localhost:3000/ を開くといつもの画面が表示されるはずです。

ReactのためにNodeの環境を追加する

ReactをビルドするためにNodeの環境を追加します。

今回はWebpackやBrowserifyの代わりにParcel bundlerを使ってみたいと思います。「webpack時代の終わりとparcel時代のはじまり - Qiita」に触発されました。

まずはRAILS_ROOT直下にreactディレクトリを作成し下記の様なファイルを設置します。

react/Dockerfile

FROM node:8

WORKDIR /usr/src/app

ADD yarn.lock /usr/src/app/yarn.lock
ADD package.json /usr/src/app/package.json
RUN yarn install

COPY . /usr/src/app

react/.babelrc

react/.babelrc
{
  "presets": ["env", "react"]
}

docker-compose.ymlに下記を追加

docker-compose.yml
react:
  build: react
  volumes:
    - "/usr/src/app/node_modules"
    - "./react:/usr/src/app"
    - "./app/assets/javascripts/react:/usr/src/app/dist"
  command:
    [ "./node_modules/.bin/parcel", "watch", "src/index.js" ]

コマンドで空ファイルなどを作成

mkdir react/dist react/src
touch react/yarn.lock
echo '{"name": "app","version": "1.0.0"}' > react/package.json
echo 'console.log("Hello React")' > react/src/index.js

Reactのモジュールをインストール

docker-compose build
docker-compose run react /bin/bash -c "yarn add react react-dom && yarn add babel-preset-react babel-preset-env parcel-bundler --dev"
docker-compose build

これで、react/src/index.jsを監視して変更があればビルドしてapp/javascripts/react/index.jsが生成されます。

RailsからReactを呼び出す

上に書いたようにapp/javascripts/react/index.jsにReactを含んだJavascriptが生成されるので、app/javascripts/application.jsから=requireで読み込みます。

これで全てのページでReactが読み込まれています。次はReact側のコードから読み込まれているページを知る為にmetaタグをlayoutに組み込みます。
同時に、meta_tagsgemをGemfileに追加してください。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Sample page</title>
  <%= display_meta_tags(
    controller_name: controller_name,
    action_name: action_name,
    params: "{}"
  ) %>
  <%= stylesheet_link_tag "application", media: "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>

これで全てのページでcontrollerとaction名が取得できます。

次に。これを元に読み込むJavascriptを振り分けるコードを一番最初に読み込まれるJavascriptに書きます。

react/src/index.js
function metaValue(name) {
  return document.querySelector(`meta[name="${name}"]`).getAttribute("content");
}
var action = `${metaValue('controller_name')}/${metaValue('action_name')}`;

if("welcomes/show" == action) {
  require('./welcomes/show/index.js')
}
// else if("welcomes/index" == action) {
//   require('./welcomes/index/index.js')
// }

erbからReactに値を渡す

例えば、@foo_, @bar_を渡す場合には下記の様にmeta_tagにjsonを入れて渡して、これをJavascriptで復号してください。

index.html.erb
<%= set_meta_tags(
  params: {
    foo: @foo_,
    bar: @bar_
  }.to_json
) %>
<div id="container">Loading</div>
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.