皆さん、お久しぶりです。最近新しいプロジェクトで Rails を使っているのですが、そこで採用した Active Admin と、純粋な webpack の連携に少しコツが必要だったので今回はその紹介をします。
Active Admin
そのプロジェクトはまだローンチ前で開発が盛んに行われている事もあり、速やかに管理者サイトを立ち上げる事が求められました。簡単に管理者サイトを作る Gem は有名なものがいくつかありますが、今回は一番コミュニティが大きく、かつ何気に初めて使う Active Admin を採用しました。
webpack
Rails では Asset Pipeline や、 jsbuilding-rails (cssbuilding-rails) などのフロントエンド関連のライブラリがいくつか提供されていますが、今回はこれらは一切使わず純粋な webpack を使っています。これらを使う事で手っ取り早く比較的新しいフロントエンド界隈の技術を掻い摘む事ができますが、プロジェクトが成長するにつれ複雑度が増してくると、結局はフロントエンドのツールを純粋に使った方が楽なケースが多い為です。(経験則に基づく個人の感想です)
また将来的に違う技術 (Turbopack など) に切り替えたくなった時や、 Rails エコシステムの状況が変わった時に対応しやすい様に、フロントエンド周辺の技術と Rails のエコシステムを切り離しておく事自体にメリットがあります。
使っているバージョンまとめ
Rails 界隈のフロントエンド界隈との統合は比較的早く状況が変わりますので、もしかしたらこれを読んでいる時は状況が異なるかも知れません。同じ問題を抱えている人に向けて、検証に使った現時点でのバージョンを記載しておきます:
- Ruby 3.2
- Rails 7.0
- Active Admin 3.0
- webpack 5.x
- TypeScript 5.1
また今回は Asset Pipeline は使っていないのでプロジェクトからは削除しています。
サンプルについて
今回もいつもの様にプロジェクトを簡易的に再現したサンプルを用意していますので、ご興味がある方は是非ご覧ください:
https://github.com/issei-m/activeadmin_with_webpack
設定
さて、 Active Admin をインストールした後、 rails g active_admin:install
を実行 (この実行には Asset Pipeline が必要なのでこの時点ではまだ有効です) した直後はアセットを含む色々なファイルが作られますが、 Asset Pipeline が有効になっていないと、 Rails server や task を実行しようとした時に次の通りエラーになります:
NoMethodError: undefined method `assets' for #<Rails::Application::Configuration ...
Asset Pipeline で動く事が前提になっている為、Rails.Application.config.assets
が有効でないとエラーになってしまいます。これを回避する為、 config/initializers/active_admin.rb の以下の通り設定をします:
RailsAdmin.config do |config|
# ...
config.use_webpacker = true
# ...
end
実際には Webpacker は使わないのですが、こうする事で Asset Pipeline が使えない事によるエラーは回避できます。しかし、 Rails 自体は起動できますが、今度はビューで別のエラーが発生します:
Webpacker をインストールしていないので、これに含まれる helper メソッドが存在しないのでエラーになります。この問題に対処する為に、 ApplicationHelper に同名のメソッドを実装します:
module ApplicationHelper
def stylesheet_pack_tag(*sources)
stylesheet_link_tag(*sources.map { |s| s.is_a?(String) ? "#{config.asset_host}/#{s}" : s })
end
def javascript_pack_tag(*sources)
javascript_include_tag(*sources.map { |s| s.is_a?(String) ? "#{config.asset_host}/#{s}" : s })
end
end
この実装ではシンプルにリクエストされたファイル名に、 config.asset_host
を先頭に足しているだけです。config.asset_host
は Asset Pipeline の設定値ですが、今回 Asset Pipeline 有効にはしていないものの、 config/application.rb などで設定しておく事で参照する事ができます:
module RailsAdminTest
class Application < Rails::Application
# ...
config.asset_host = ENV['RAILS_ASSET_HOST']&.chomp('/')
end
end
この環境変数の設定値は development, test 環境では webpack DevServer の、production 環境ではバンドル済みアセットを配置した CDN (CloudFront など) の URL プレフィクスを想定しています。尚、ビルド時のアセットのファイル名にハッシュを付けたりする場合は manifest.json との連携が必要ですが、その部分については今回は触れません。 Webpacker が実装しているので詳しくはそちらをご覧下さい。
さて、ここまで設定して Rails はエラーが出なくなりますがまだ完了ではありません。Active Admin インストール時に作られる以下のアセットファイルに少し手を加える必要があります:
ファイル構成
- app/
- assets/
- javascripts/
- active_admin.js
- stylesheets/
- active_admin.scss
中身はそれぞれ次の様になっています:
// https://github.com/issei-m/activeadmin_with_webpack/blob/351f4089393b15e47b52f626f25bc3f8660f4f2c/app/assets/javascripts/active_admin.js
//= require active_admin/base
// https://github.com/issei-m/activeadmin_with_webpack/blob/351f4089393b15e47b52f626f25bc3f8660f4f2c/app/assets/stylesheets/active_admin.scss
@import "active_admin/mixins";
@import "active_admin/base";
いずれも Asset Pipeline により Active Admin の Gem 内のアセットが読み込まれる事を想定していますが、今回この手法は使えません。幸いな事に Active Admin はアセットを NPM でも配信しているので、これを使う事で簡単にバンドルができます。
npm i @activeadmin/activeadmin
先ほどのファイルをそれぞれ修正します。まずは active_admin.scss から:
// https://github.com/issei-m/activeadmin_with_webpack/blob/c7e09432bf02da35776269b5eebcea02c539f943/app/assets/stylesheets/active_admin.scss
@import "@activeadmin/activeadmin/src/scss/mixins";
@import "@activeadmin/activeadmin/src/scss/base";
先ほどインストールした NPM パッケージからロードする様パスを変更します。
次に JavaScript 側ですが、どうせならエントリポイントは TypeScript にしたいので、 active_admin.js は削除した上で、 active_admin.ts と active_admin_requirements.js に分けてそれぞれ以下の様にします:
// https://github.com/issei-m/activeadmin_with_webpack/blob/c7e09432bf02da35776269b5eebcea02c539f943/app/assets/javascripts/active_admin.ts
import './active_admin_requirements'
import '../stylesheets/active_admin.scss'
// https://github.com/issei-m/activeadmin_with_webpack/blob/c7e09432bf02da35776269b5eebcea02c539f943/app/assets/javascripts/active_admin_requirements.js
window.$ = window.jQuery = require('jquery')
require('jquery-ui/ui/widgets/datepicker')
require('jquery-ui/ui/widgets/dialog')
require('jquery-ui/ui/widgets/sortable')
require('jquery-ui/ui/widgets/tabs')
require('jquery-ui')
require('jquery-ujs')
require('@activeadmin/activeadmin')
Active Admin 側の jQuery を使ったコードは1枚の JS に閉じ込めて、メインのエントリポイントは TypeScript にします。jQuery 以外の要素をカスタマイズする場合は、TypeScript 側にコードを追加していく事を想定しています。またエントリポイントから scss も import しているので、 webpack の MiniCssExtractPlugin
により同名の *.css
ファイルがアセットとして出力されます。これにより本家の CSS アセットが stylesheet_pack_tag
により読み込める訳です。
※ webpack の設定詳細については https://github.com/issei-m/activeadmin_with_webpack/blob/main/webpack.config.js をご参照下さい。
ここまで設定が整ったら、再度 Rails server と webpack DevServer を再起動します。すると無事に画面が表示されます:
コンソールでエラーも出てないし、 jQuery UI の部分 (カレンダーなど) も無事に動作を確認できました。