Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 7: importmap-railsとjsbundling-railsでのStimulusの扱いの違い

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ではインストールできませんでした。

Rails 7: dartsass-rails gemはNode.jsなしで使える

ある程度以上複雑な外部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))

関連記事

Rails 7: importmap-rails gem README(翻訳)

Rails 7 : rails newのフロントエンド関連オプションの組み合わせを調べてみた


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。