January 22, 2018
Rails チュートリアルのサンプルアプリを題材に自分だけの Rails on Docker な開発環境を作ることが、最近のマイブームになりつつある。
cheezenaan-sandbox/sample_app_rev4: Sample application forked from https://railstutorial.jp/
もともと Rails チュートリアル自体はこの 1〜2 年で 3 周前後していたのだけど、ただ繰り返すだけなのもまぁ飽きるので、自分なりに工夫を入れることが多くなった。書きはじめると長くなるけど、例えばこんなかんじ:
docker-compose up
のコマンド一発で開発環境が整うようにした今回は、最後の項目について少しだけまとめることにしてみる。
↑ の URL からコミットを追っていくのがいいと思う。ハイライトは以下の通り。
javascript_include_tag
で読み込めるよう専用のヘルパーを用意frontend
ディレクトリをルートに掘ってまとめて管理している。
$ tree frontend -I node_modules
frontend
├── config
│ └── webpack.config.js
├── package.json
├── src
│ ├── images
│ ├── javascripts
│ │ └── application
│ │ ├── Hello.js
│ │ └── index.js
│ └── stylesheets
└── yarn.lock
6 directories, 5 files
ビルドした生成物は ./public/assets
以下に配置する。なお開発環境では webpack-dev-server
を使用するので具体的な生成物がないことに注意。
const path = require('path');
const ManifestPlugin = require('webpack-manifest-plugin');
const UglifyJSPlugin = require('uglify-js-plugin');
const isProduction = process.env.NODE_ENV === 'production';
const fileName = isProduction ? '[name]_[hash]' : '[name]';
const pathForAssets = path.resolve(__dirname, '../../public/assets');
const Manifest = new ManifestPlugin({ fileName: 'webpack-manifest.json' });
const UglifyJS = new UglifyJSPlugin({
parallel: 4,
sourceMap: !isProduction,
warnings: false,
});
const plugins = [Manifest];
const pluginsForProudction = plugins.concat(UglifyJS);
module.exports = {
entry: {
'frontend/application': ['./src/javascripts/application/index.js'],
},
output: {
filename: `${fileName}.js`,
path: pathForAssets,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules|bower_components/,
loader: 'babel-loader',
},
],
},
plugins: isProduction ? pluginsForProudction : plugins,
resolve: {
extensions: ['.js', '.jsx'],
},
devServer: isProduction ? {} : { contentBase: pathForAssets },
devtool: isProduction ? 'eval' : 'cheap-module-source-map',
};
ちなみに package.json の scripts
はこんな具合。
{
// ...
"scripts": {
"build": "webpack --config ./config/webpack.config.js",
"clean": "rimraf '../public/assets/frontend/**/*.{js,css}'",
"format": "prettier --write 'src/**/*.{js,css}'",
"lint": "eslint src",
"publish": "yarn clean && NODE_ENV=production yarn build",
"watch": "webpack-dev-server --config ./config/webpack.config.js --host 0.0.0.0 --port 4000 --colors --inline --progress"
}
}
webpack-manifest-plugin を導入することで、 webpack でビルドした生成物のマッピングが記された manifest ファイルを作成してくれる。
danethurber/webpack-manifest-plugin: webpack plugin for generating asset manifests
このファイルを用いて Rails に JavaScript を読み込ませる。やることは 2 つ。
config/initializers/assets.rb
なるファイルがデフォルトで存在しているので、こちらに追記する。
# config/initializers/assets.rb
manifest = Rails.root.join("public", "assets", "webpack-manifest.json")
Rails.application.config.assets_manifest = JSON.parse(File.read(manifest)) if File.exist?(manifest)
Rails.application.config.assets_manifest
を呼ぶことで manifest ファイルの情報を JSON 形式で読み込めるので、あとはいいかんじにヘルパーを書いてやればいい。
module ApplicationHelper
# ...
def frontend_asset_path(path)
return "http://0.0.0.0:4000/#{path}" if Rails.env.development?
routes = Rails.application.routes.url_helpers
host = Rails.application.config.action_controller.asset_host || routes.root_path
manifest = Rails.application.config.assets_manifest
return unless manifest.fetch(path, false)
Pathname.new(host).join("assets", manifest[path])
end
end
先述の通り開発環境では webpack-dev-server を使いたいので、ちょっとかっこ悪いけど分岐を入れている。
あとは View のファイル内で
<%= javascript_include_tag 'application', frontend_asset_path("frontend/application.js") %>
のように呼べば終了。rake assets:precompile
のない世界線へようこそ。
躓いたのが RSpec などのテスティングフレームワークによる E2E テストの実施。デフォルトでは RAILS_ENV=TEST
で実行されるため、 webpack でビルドした生成物が存在しない場合ファイル不在で落ちてしまう。いまはテスト実行前に手動で yarn build
してからテストを回すようにしているけれど、なかなかに面倒なので早くこの手間をなくしたい。
ここでやっとタイトルを回収するわけなんだけど、今回は Rails アプリを動かす app
コンテナから独立して webpack によるビルドを実行する node
コンテナを用意した。たしかに Procfile
を使えば 1 コンテナ内で複数のプロセスを管理できるみたいだけど、まずは「1 コンテナ 1 プロセス」という基本(出所不明)に沿ってやることにした。
$ tree docker
docker
├── node
│ └── Dockerfile.dev
└── rails
├── Dockerfile.dev
└── Dockerfile.test
Dockerfile はサービスごとにディレクトリを掘った。補足をしておくと Rails アプリの開発時は bundle install
するたびに docker build
したくないので、 .dev
ではボリュームをマウントするだけにして docker-compose exec app bundle install -j4
と叩んでコンテナにつど反映させている。
# docker-compose.yml
version: '3'
volumes:
app_data:
db_data:
node_modules:
services:
# ...
app: &app_base # ...
node:
container_name: node
build:
context: .
dockerfile: ./docker/node/Dockerfile.dev
command: yarn watch
ports:
- 4000:4000
volumes:
- ./frontend/:/app/frontend:cached
- ./frontend/node_modules:/app/frontend/node_modules:cached
- ./public/assets/:/app/public/assets:cached
# ...
Rails アプリが動く app コンテナの public/assets
にビルド結果の生成物を置きたいので ./public/assets/:/app/public/assets
のようにボリュームをマウントしてやる。
ここまでで JavaScript の管理は Webpack でできるようになったので、次は順当にスタイルシート(scss, css)でも webpack で管理できるようにしていきたい。まずはチュートリアル 5 章までで作成したスタイルシートを frontend/src/stylesheets
に移すところから。