`bin/dev` でRailsサーバーとJSウォッチャーを一括起動
foreman と bin/dev スクリプトを導入することで、ローカル開発時に必要な複数プロセスを1コマンドで起動できるようになりました。これにより、従来は別々のターミナルで実行していた2つのプロセスを統一されたエントリポイントに集約しています。
背景
これまでのローカル開発では、RailsサーバーとJSウォッチャーを別々のコマンドで起動する必要がありました。具体的には、bin/rails server と yarn build -w をそれぞれ別のターミナルセッションで実行する必要があり、開発環境のセットアップ手順が複数のステップに分散していました。
さらに、Railsサーバーの起動時に前回のプロセスが残存していると、tmp/pids/server.pid が残ったまま起動に失敗するケースがありました。このような「stale PID」の問題も開発体験を損なう要因となっていました。
これらの課題を解消するため、プロセス管理ツールの foreman と統一エントリポイントとなる bin/dev が導入されました。
技術的な変更
今回の変更は、Procfile.dev・bin/dev・Gemfile の3つのファイルの追加・修正で構成されています。
Procfile.dev は、foremanが起動するプロセスを定義するファイルです。
web: bin/rails server
js: yarn watch
web プロセスでRailsサーバーを、js プロセスでJSウォッチャーをそれぞれ管理します。foremanはこれらのプロセスの標準出力をプレフィックス付きで多重化して表示し、どちらかが異常終了した場合はもう一方も停止します。
bin/dev は、stale PIDの処理とforemanの起動を担うBashスクリプトです。
#!/usr/bin/env bash
# Kill any previous Rails server
if [ -f tmp/pids/server.pid ]; then
pid=$(cat tmp/pids/server.pid)
if kill -0 "$pid" 2>/dev/null; then
echo "Killing previous Rails server (PID: $pid)..."
kill "$pid"
wait "$pid" 2>/dev/null
fi
rm -f tmp/pids/server.pid
fi
exec foreman start -f Procfile.dev -p 3000 "$@"
PIDファイルが存在する場合、kill -0 でプロセスの生存確認を行い、実際に動作中の場合のみ kill でプロセスを終了します。PIDファイルが残っているがプロセスが既に存在しない場合は、kill -0 が失敗するためスキップされ、PIDファイルのみ削除されます。最後の exec foreman start により、スクリプト自身のプロセスをforemanに置き換えることでプロセス管理を簡潔にしています。"$@" を渡すことで、bin/dev に渡した追加オプションをそのままforemanに転送できます。
Gemfile への foreman の追加は最小限の変更です。
gem "foreman"
開発専用の group :development ブロックではなく、トップレベルに追加されています。
設計判断
bin/dev によるstale PIDの事前クリーンアップを、foreman起動の前処理として組み込む構成が採用されました。
foremanはプロセスの起動と管理を担いますが、前回のRailsサーバープロセスが残留している場合の処理は担いません。bin/dev にこのクリーンアップロジックを持たせることで、開発者が毎回手動でPIDファイルを確認・削除する手間を排除しています。kill -0 によるプロセス生存確認を挟むことで、PIDファイルは残っているがプロセスは既に終了済みという状況でも安全に動作します。
また、exec によるプロセス置き換えを使っている点も注目です。bash bin/dev がforemanの親プロセスとして残るのではなく、foremanがシェルのプロセスを引き継ぐため、シグナルのハンドリングがシンプルになります。
まとめ
foreman と bin/dev の導入により、ローカル開発のエントリポイントが bin/dev の1コマンドに集約されました。stale PIDのクリーンアップとプロセスの多重起動を組み合わせることで、Railsアプリケーションでよく見られる開発環境の煩雑さを解消するシンプルかつ堅牢な設計となっています。