logo

アルパカログ

Rails 7をwebpackを使って構成する

Ruby on Rails 7 では webpacker が廃止されました。

この記事では JS のバンドラーとして webpack を使って Rails 7 を構成する方法を説明します。

前提条件
  • Ruby 3.0.3
  • Ruby on Rails 7.0.0
  • Docker Desktop

Rails プロジェクト作成

プロジェクトディレクトリを作成し bundler で rails をインストールします。

% mkdir rails7-webpack-sample && cd -
% echo "source 'https://rubygems.org'\n\ngem 'rails', '~> 7.0.0'" > Gemfile
% bundle install

rails new のオプションは次のようになっています。

% bundle exec rails new -h
Usage:
  rails new APP_PATH [options]

Options:
      [--skip-namespace], [--no-skip-namespace]              # Skip namespace (affects only isolated engines)
      [--skip-collision-check], [--no-skip-collision-check]  # Skip collision check
  -r, [--ruby=PATH]                                          # Path to the Ruby binary of your choice
                                                             # Default: /Users/hiroki.toyokawa/.asdf/installs/ruby/2.7.2/bin/ruby
  -m, [--template=TEMPLATE]                                  # Path to some application template (can be a filesystem path or URL)
  -d, [--database=DATABASE]                                  # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
                                                             # Default: sqlite3
  -G, [--skip-git], [--no-skip-git]                          # Skip .gitignore file
      [--skip-keeps], [--no-skip-keeps]                      # Skip source control .keep files
  -M, [--skip-action-mailer], [--no-skip-action-mailer]      # Skip Action Mailer files
      [--skip-action-mailbox], [--no-skip-action-mailbox]    # Skip Action Mailbox gem
      [--skip-action-text], [--no-skip-action-text]          # Skip Action Text gem
  -O, [--skip-active-record], [--no-skip-active-record]      # Skip Active Record files
      [--skip-active-job], [--no-skip-active-job]            # Skip Active Job
      [--skip-active-storage], [--no-skip-active-storage]    # Skip Active Storage files
  -C, [--skip-action-cable], [--no-skip-action-cable]        # Skip Action Cable files
  -A, [--skip-asset-pipeline], [--no-skip-asset-pipeline]    # Indicates when to generate skip asset pipeline
  -a, [--asset-pipeline=ASSET_PIPELINE]                      # Choose your asset pipeline [options: sprockets (default), propshaft]
                                                             # Default: sprockets
  -J, [--skip-javascript], [--no-skip-javascript]            # Skip JavaScript files
      [--skip-hotwire], [--no-skip-hotwire]                  # Skip Hotwire integration
      [--skip-jbuilder], [--no-skip-jbuilder]                # Skip jbuilder gem
  -T, [--skip-test], [--no-skip-test]                        # Skip test files
      [--skip-system-test], [--no-skip-system-test]          # Skip system test files
      [--skip-bootsnap], [--no-skip-bootsnap]                # Skip bootsnap gem
      [--dev], [--no-dev]                                    # Set up the application with Gemfile pointing to your Rails checkout
      [--edge], [--no-edge]                                  # Set up the application with Gemfile pointing to Rails repository
  --master, [--main], [--no-main]                            # Set up the application with Gemfile pointing to Rails repository main branch
      [--rc=RC]                                              # Path to file containing extra configuration options for rails command
      [--no-rc], [--no-no-rc]                                # Skip loading of extra configuration options from .railsrc file
      [--api], [--no-api]                                    # Preconfigure smaller stack for API only apps
      [--minimal], [--no-minimal]                            # Preconfigure a minimal rails app
  -j, [--javascript=JAVASCRIPT]                              # Choose JavaScript approach [options: importmap (default), webpack, esbuild, rollup]
                                                             # Default: importmap
  -c, [--css=CSS]                                            # Choose CSS processor [options: tailwind, bootstrap, bulma, postcss, sass... check https://github.com/rails/cssbundling-rails]
  -B, [--skip-bundle], [--no-skip-bundle]                    # Don't run bundle install

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

Rails options:
  -h, [--help], [--no-help]        # Show this help message and quit
  -v, [--version], [--no-version]  # Show Rails version number and quit

Description:
    The 'rails new' command creates a new Rails application with a default
    directory structure and configuration at the path you specify.

    You can specify extra command-line arguments to be used every time
    'rails new' runs in the .railsrc configuration file in your home directory,
    or in $XDG_CONFIG_HOME/rails/railsrc if XDG_CONFIG_HOME is set.

    Note that the arguments specified in the .railsrc file don't affect the
    defaults values shown above in this help message.

Example:
    rails new ~/Code/Ruby/weblog

    This generates a skeletal Rails installation in ~/Code/Ruby/weblog.

必要なものだけに絞って rails new . します(お好みで変更してください)。

既に作成した Gemfile を上書きするので --force オプションを指定します。

下記の例ではデータベースとして MySQL を指定しています。

% bundle exec rails new . \
  --force \
  --database=mysql \
  --skip-action-mailer \
  --skip-action-mailbox \
  --skip-action-text \
  --skip-active-job \
  --skip-active-storage \
  --skip-action-cable \
  --skip-hotwire \
  --skip-jbuilder \
  --javascript=webpack

開発環境の構築

開発環境は Docker Compose を使って構築します。

下記の内容で Dockerfile を作成します。

FROM node:17.3-slim AS node
FROM node:17.3-slim AS build-node-modules

WORKDIR /app

COPY package.json /app/
COPY yarn.lock /app/

RUN yarn install


FROM ruby:3.0.3-slim AS ruby-base

WORKDIR /app

ENV BUNDLE_APP_CONFIG .bundle
ENV BUNDLE_PATH vendor/bundle

# For nokogiri
# https://nokogiri.org/tutorials/installing_nokogiri.html#installing-using-standard-system-libraries
# For mysql
# https://github.com/brianmario/mysql2#linux-and-other-unixes
RUN apt-get update -y && apt-get install -y \
    build-essential \
    pkg-config libxml2-dev libxslt-dev \
    libmariadb-dev default-mysql-client


FROM ruby-base AS build-ruby-gems

COPY Gemfile* /app/

RUN bundle config build.nokogiri --use-system-libraries \
 && bundle install -j4


FROM ruby-base

WORKDIR /app

ARG RAILS_ENV
ARG RAILS_MASTER_KEY
ARG DB_USER
ARG DB_PASSWORD
ARG DB_HOST
ARG DB_PORT

RUN apt-get install -y \
    curl \
    gnupg2 \
 && echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list \
 && curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg  add - \
 && apt-get update -y && apt-get install -y \
    tzdata \
    google-cloud-sdk \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /opt/yarn* /opt/yarn/
RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
 && ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

COPY --from=build-node-modules /app/node_modules node_modules/
COPY --from=build-ruby-gems /app/.bundle .bundle/
COPY --from=build-ruby-gems /app/vendor vendor/

VOLUME /app/node_modules /app/.bundle /app/vendor

COPY . /app

CMD bin/rails server -b 0.0.0.0 -p ${PORT}

下記の内容で docker-compose.yml を作成します。

version: '3'

services:
  mysql:
    image: mariadb:10.7
    volumes:
      - ./docker/mysql/volumes/data:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      MYSQL_ROOT_PASSWORD: ''
    command:
      - mysqld
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_bin

  app:
    build:
      context: .
    ports:
      - 58000:8000
    volumes:
      - ./:/app:cached
    depends_on:
      - mysql
    command: ["./scripts/wait-for-db.sh", "mysql", "bin/rails", "server", "-b", "0.0.0.0", "-p", "8000"]

scripts/ ディレクトリを作成し下記の内容で scripts/wait-for-db.sh を作成します。

#!/bin/sh
# wait-for-db.sh

set -e

host="$1"
shift

until mysql -h "$host" -u root; do
  >&2 echo "MySQL is unavailable - sleeping"
  sleep 1
done

>&2 echo "MySQL is up - executing command"
exec "$@"

scripts/wait-for-db.sh にパーミッションを付与しておきます。

% chmod a+x scripts/wait-for-db.sh

config/database.yml を編集します。

 default: &default
   adapter: mysql2
   encoding: utf8mb4
   pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
-  username: root
-  password:
+  username: <%= ENV.fetch("DB_USER") { "root" } %>
+  password: <%= ENV.fetch("DB_PASSWORD") { "" } %>
+  host: <%= ENV.fetch("DB_HOST") { "mysql" } %>
+  port: <%= ENV.fetch("DB_PORT") { "3306" } %>

 production:
   <<: *default
-  database: rails7_webpack_sample_production
-  username: rails7_webpack_sample
-  password: <%= ENV["RAILS7_WEBPACK_SAMPLE_DATABASE_PASSWORD"] %>
+  database: rails7_webpack_sample

Docker イメージをビルドします。

% docker compose build

データベース作成タスクを実行します。

% docker compose run --rm app bundle exec rails db:create

これで開発環境は完成です。開発サーバーを起動して動作確認してみましょう。

% docker compose up

下記のログが表示されたらブラウザで http://localhost:58000 にアクセスします。

app_1    | * Listening on http://0.0.0.0:8000

Rails のロゴが表示されたら成功です。

Webpack を使って JS をバンドルする

次に Webpack を使って JS をバンドルしてみましょう。

最初に確認のためのルーティングを追加します。config/routes.rb を編集します。

Rails.application.routes.draw do
   # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

   # Defines the root path route ("/")
-  # root "articles#index"
+  root "articles#index"
 end

下記の内容で app/controllers/articles_controller.rb を作成します。

class ArticlesController < ApplicationController
  layout "application"

  def index
  end
end

下記の内容で app/views/articles/index.html.erb を作成します。

<h2>Articles</h2>

Procfile.dev を編集します。

-web: bin/rails server -p 3000
+web: rm -f ./tmp/pids/server.pid && bin/rails server -b 0.0.0.0 -p 8000
 js: yarn build --watch

docker-compose.yml を編集します。

     depends_on:
       - mysql
-    command: ["./scripts/wait-for-db.sh", "mysql", "bin/rails", "server", "-b", "0.0.0.0", "-p", "8000"]
+    command: ["./scripts/wait-for-db.sh", "mysql", "bin/dev"]

package.json にビルドコマンドを追加します。

 "dependencies": {
     "webpack": "^5.65.0",
     "webpack-cli": "^4.9.1"
+  },
+  "scripts": {
+    "build": "webpack"
   }
 }

カスタム JavaScript ファイルとしてapp/javascript/hello-world.js を作成します。

console.log("Hello World!")

エントリーポイントとなる app/javascript/application.jshello-world.js をインポートします。

// Entry point for the build script in your package.json
+import "./hello-world"

Docker イメージをビルドし直して再度開発サーバーを起動します。

% docker compose down
% docker compose build
% docker compose up

ブラウザで http://localhost:58000 にアクセスします。

開発者ツールから JavaScript コンソールを開き Hello World! が表示されていれば成功です。

今回使用したサンプルコードは下記のリポジトリにあります。

以上です。

この記事では JS のバンドラーとして webpack を使って Rails 7 を構成する方法を説明しました。