Edited at

Webpacker使わずにDockerでRailsとReactを使う

More than 1 year has passed since last update.

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>