Rails 7でStimulusを書き始めています。Rails 7でimportmap-railsを使うかjsbundling-railsを使うかでStimulusのセットアップが少し違うことに今頃気づいたので、小ネタですがメモします。
ローカルのStimulusコントローラファイル
ここでは、ローカルのapp/javascripts/controllers/以下のStimulusコントローラファイルの扱いについて書きます。
1. importmap-railsの場合
importmap-railsを使う場合は、app/javascripts/controllers/index.jsが以下のようにeagerLoadControllersFrom()
を呼び出しています。
- importmap利用時のapp/javascripts/controllers/index.js
// Import and register all your controllers from the importmap under controllers/*
import { application } from "controllers/application"
// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)
// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)
そのおかげで、app/javascripts/controllers/以下に置かれたなんちゃら_controller.js
はそのコントローラ名で自動登録されます。
2. jsbundling-railsの場合
jsbundling-railsの場合は、app/javascripts/controllers/以下のコントローラファイルを自動登録しません(後述)。そのため、何らかの形でコントローラファイルを登録する必要があります。以下のHelloController
のような要領で登録します。
- jsbundling-rails(つまりnode)利用時のapp/javascripts/controllers/index.js
// This file is auto-generated by ./bin/rails stimulus:manifest:update
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName
import { application } from "./application"
import HelloController from "./hello_controller"
application.register("hello", HelloController)
コントローラを登録する手間を軽減するため、Stimulusコントローラのジェネレータが使えます。
index.jsのコメントにもあるように、jsbundling-railsの場合は原則として以下を実行してコントローラファイルを生成し、それを使うことになります。
./bin/rails generate stimulus コントローラ名
このジェネレータでは./bin/rails stimulus:manifest:update
も実行されてindex.js内のマニフェストも自動更新されます。./bin/rails stimulus:manifest:update
は単独でも実行できます。
$ ./bin/rails g stimulus mycon
create app/javascript/controllers/mycon_controller.js
rails stimulus:manifest:update
以下は実行後のindex.jsです。
// This file is auto-generated by ./bin/rails stimulus:manifest:update
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName
import { application } from "./application"
import HelloController from "./hello_controller.js"
application.register("hello", HelloController)
// 以下が自動で追加される
import MyconController from "./mycon_controller.js"
application.register("mycon", MyconController)
ジェネレータを使わずに、app/javascripts/controllers/以下に自分でコントローラファイルを作成したときは、自分でindex.jsにマニフェストを追加する必要があります。
外部のStimulusコンポーネントを利用する場合
importmap-railsの場合は./bin/importmap pin npmパッケージ名
で追加します。importmap-railsは、このStimulusコンポーネントをCDNから取り入れて利用します。
# 例
./bin/importmap pin tailwindcss-stimulus-components stimulus-textarea-autogrow stimulus-scroll-to
jsbundling-railsの場合はyarn add npmパッケージ名
で追加します。こちらは普通にビルドされます。
# 例
yarn add tailwindcss-stimulus-components stimulus-textarea-autogrow stimulus-scroll-to
しかし外部のStimulusコンポーネントを利用する場合は、どちらの場合もindex.jsでマニフェストを手動で追加する必要があります。
// index.js
// This file is auto-generated by ./bin/rails stimulus:manifest:update
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName
import { application } from "./application"
// たとえば以下のような感じで手動で追加する
import { Alert, Autosave, Dropdown, Modal, Tabs, Popover, Toggle, Slideover } from "tailwindcss-stimulus-components"
application.register('alert', Alert)
application.register('autosave', Autosave)
application.register('dropdown', Dropdown)
application.register('modal', Modal)
application.register('tabs', Tabs)
application.register('popover', Popover)
application.register('toggle', Toggle)
application.register('slideover', Slideover)
import TextareaAutogrow from "stimulus-textarea-autogrow"
application.register("textarea-autogrow", TextareaAutogrow)
import ScrollTo from "stimulus-scroll-to"
application.register("scroll-to", ScrollTo)
最後に
importmap-railsとjsbundling-railsの違いがわかると、ローカルでStimulusコントローラを書いたときにindex.jsのマニフェストを更新しなくてよいimportmap-railsがいいなという気持ちに傾いてきました。
StimulusコンポーネントであればローカルでもCDN上の外部コンポーネントでも問題なくimportmap-railsで扱えるので、自分はimportmap-railsを選び、JavaScriptもすべてStimulusで書くことにしました。
ただし、Stimulusコンポーネント以外の一般的な外部npmパッケージで、変数の依存関係などでビルドが必須になる複雑なものだと、ビルドのないimportmap-railsでは扱えません。
以下の記事にも書いたように、bootstrap.jsとpopper.jsをimportmap-railsでpin
して動かせましたが、たとえばBootstrap RFSはSass変数が絡んでくるのでimportmap-railsではインストールできませんでした。
ある程度以上複雑な外部JSライブラリを使うことが事前にわかっている場合は、最初から素直にjsbundling-railsを使うのがよさそうです。
おまけ: jsbundling-rails + webpackならコントローラをオートロードできる
ここまで書いてから、Stimulus公式ハンドブックにひととおり書いてあることに気づきました。
webpackを使っているのであれば、Railsであるかどうかにかかわらず、@hotwired/stimulus-webpack-helpers
パッケージを追加したうえで、以下をindex.jsまたはメインのjsファイルに書けばローカルのStimulusコントローラをオートロードできるそうです(esbuildやrollupではオートロードはサポートされないそうです)。
import { Application } from "@hotwired/stimulus"
import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers"
window.Stimulus = Application.start()
const context = require.context("./controllers", true, /\.js$/)
Stimulus.load(definitionsFromContext(context))