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

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

更新情報

  • 2021/12/28: 初版公開
  • 2023/04/18: 更新(Rails 7.0.2、Ruby 3.2.2)

参考: アセットパイプライン - Railsガイド

🔗 概要

参考: Rails 7.0でアセットパイプラインはどう変わるか | Wantedly Engineer Blog

上の記事にインスパイアされて、Rails 7 でrails newするときのフロントエンド関連オプションの組み合わせを確かめてみました。結果は今後変わる可能性がありますのでご了承ください。

rails newするときの環境は以下のテンプレートを元にしました。

hachi8833/rails7_ruby310p1 - GitHub

  • Rails: 7.0.0〜7.0.2
  • Ruby: 3.1.0preview1〜Ruby 3.2.2
    • YJITは有効

🔗 結果

長過ぎるので以下のgistに置きました。当初はRails 7 RC1で試し、Rails 7リリース後に再度試しました。

🔗 まとめ

以下は特記ない限りRails 7.0.0の時点のものです。まずは原則的なものから。

🔗 デフォルトではsprockets-railsがインストールされる

  • propshaftは、-a propshaftを指定しない限りインストールされない
  • propshaftとsprocketsはいずれか一方しかインストールできない
  • sprocketsが必要な他のオプションを指定すると、-a propshaftは指定しても無効になる

rails/propshaft - GitHub

🔗 importmap-railsは指定なしでも「基本的に」インストールされる(後述)

  • es-module-shmisもインストールされるので、FirefoxやSafariでもimportmap-railsの恩恵を受けられるはず

guybedford/es-module-shims - GitHub

🔗 turbo-railsとstimulus-railsは常にインストールされる

  • スキップするオプションは見当たらない

追記(2022/01/04)

--skip-hotwireでturbo-railsとstimulus-railsがスキップされることを確かめました。

🔗 オプションの組み合わせ

🔗 importmapは、完全にデフォルトというわけではない

  • -cオプションにtailwind以外のCSSフレームワークを指定すると、たとえ-j importmapを指定してもデフォルトでimportmapではなくesbuildがjsbundling-rails経由で使われる
  • つまり-cでCSSフレームワークを指定するときはesbuildを使うか、-jでrollupかwebpackのいずれかを指定することになる

🔗 tailwindは例外的にgemで提供される

  • DHHとしては、nodeを使わずにやれる見本としてtailwindcss-railsを考えているらしい(#43531のDHHコメント

🔗 それ以外のCSSフレームワークはcssbundling-rails経由でインストールされる

  • -c tailwindを指定する場合はimportmap-railsが使われる(jsbundling-railsもcssbundling-railsもインストールされない)
  • ただし-c tailwindに加えて、-jでesbuild/rollup/webpackを指定する場合は、tailwindはgemではなくcssbundling-rails経由でインストールされる

(追記2023/04/18)

dartsass-railsもtailwindcssのようにgemとして提供されていますが、今のところrails newのオプションにはありません。READMEにあるように、後から./bin/bundle add dartsass-rails./bin/rails dartsass:installを実行する形でインストールします。

  • rails new -c sassを指定すると、dartsassがjsbundling-railsとcssbundling-rails経由でインストールされます。つまりnode_modulesディレクトリができます。このとき-a propshaftを指定すると、propshaftはインストールされるものの、やはりdartsassがjsbundling-railsとcssbundling-rails経由でインストールされます。
    • なお、mainブランチでは--css sassでdartsass-railsをインストールできるようになりました(ウォッチ20230322)。おそらくRails 7.1から使えると思います。

importmapを使う場合は、node_modulesディレクトリは生成されません。それ以外のesbuild/rollup/webpackを使う場合はnode_modulesディレクトリが生成されます。

🔗 -a propshaft-c tailwindは両方指定可能

Rails 7 RC1の時点では、-a propshaft-c tailwindを両方指定するとインストール中にエラーになりましたが、Rails 7リリース後には正常になりました。

これはtailwindcss-rails側で認識されていたようです。rails/tailwindcss-railsの以前のREADME↓では、いずれtailwindcss-railsもpropshaftから使えるようにするとありましたが、その後正常に動くようになったためか、現在のREADMEではこの文が変更され、SprocketsとPropshaftの記述はなくなっています。

This gem gives access to the standard Tailwind CSS framework configured for dark mode, forms, aspect-ratio, typography, and the Inter font via the asset pipeline using Sprockets (and soon Propshaft).
同READMEより

自分がRails 7でアプリを作るときは、DHHが使って欲しそうな組み合わせでrails newしようと考え中です。

🔗 tailwindcss-railsではビューテンプレートにスタイルが付く

tailwindcss-railsにはジェネレータも含まれているので、以下のようにscaffoldで生成されるビューのテンプレートにtailwindのスタイルが追加されます。

  <div class="my-5">
    <%= form.label :comment %>
    <%= form.text_area :comment, rows: 4, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
  </div>

また、tailwindはimportmapかnode経由でもインストール可能です(この場合tailwindのscaffoldジェネレータは入らないと思いますが)。

(追記2023/04/18): dartsass-railsの場合はビューテンプレートにスタイルは付きませんでした。

🔗 rails-ujsは追加インストール可能

Rails 7ではrails-ujsが標準では入らなくなっていますが、npmパッケージやCDNにはあるので、./bin/importmap pin rails-ujsを実行すると以下のようにrails-ujsをインストールできることまでは確認しました。

# config/importmap.rb
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "rails-ujs", to: "https://ga.jspm.io/npm:rails-ujs@5.2.6/lib/assets/compiled/rails-ujs.js"

なお、npmでインストールする場合は以下のように@railsを追加する必要があるそうです(Rails 7アップグレードガイド

actioncable   → @rails/actioncable
activestorage → @rails/activestorage
rails-ujs     → @rails/ujs

🔗 Rails 7でActive Jobはスキップできないらしい

Turbo Streamは標準ではActive Jobに依存している部分があるようです。

--skip-active-jobでActive Jobをスキップすると、developmentモードだとエラーになりませんでしたがproductionでエラーになりました。

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
# require "active_job/railtie"
require "active_record/railtie"
# require "active_storage/engine"
require "action_controller/railtie"
# require "action_mailer/railtie"
# require "action_mailbox/engine"
# require "action_text/engine"
require "action_view/railtie"
# require "action_cable/engine"
require "rails/test_unit/railtie"
▶出力(クリックすると展開します)
RAILS_ENV=production dip rails s
Creating rails7_ruby310p1_rails_run ... done
=> Booting Puma
=> Rails 7.0.0 application starting in production
=> Run `bin/rails server --help` for more startup options
Exiting
/bundle/ruby/3.1.0/gems/turbo-rails-1.0.0/app/jobs/turbo/streams/broadcast_job.rb:3:in `<main>': uninitialized constant ActiveJob (NameError)

class Turbo::Streams::BroadcastJob < ActiveJob::Base
                                     ^^^^^^^^^
Did you mean?  ActiveModel
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:100:in `register'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/kernel.rb:27:in `require'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:95:in `const_get'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:95:in `cget'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:232:in `block (2 levels) in eager_load'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:26:in `block in ls'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:18:in `each_child'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:18:in `ls'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:227:in `block in eager_load'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:212:in `synchronize'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:212:in `eager_load'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:312:in `each'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:312:in `eager_load_all'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/application/finisher.rb:79:in `block in <module:Finisher>'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/initializable.rb:32:in `instance_exec'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/initializable.rb:32:in `run'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/initializable.rb:61:in `block in run_initializers'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:228:in `block in tsort_each'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:431:in `each_strongly_connected_component_from'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:349:in `block in each_strongly_connected_component'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:347:in `each'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:347:in `call'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:347:in `each_strongly_connected_component'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:226:in `tsort_each'
    from /usr/local/lib/ruby/3.1.0/tsort.rb:205:in `tsort_each'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/initializable.rb:60:in `run_initializers'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/application.rb:369:in `initialize!'
    from /app/config/environment.rb:5:in `<main>'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:100:in `register'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
    from /bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/kernel.rb:35:in `require'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `require_relative'
    from config.ru:3:in `block in <main>'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/builder.rb:116:in `eval'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/builder.rb:116:in `new_from_string'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/builder.rb:105:in `load_file'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/builder.rb:66:in `parse_file'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:349:in `build_app_and_options_from_config'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:249:in `app'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:422:in `wrapped_app'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:312:in `block in start'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:379:in `handle_profiling'
    from /bundle/ruby/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:311:in `start'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/commands/server/server_command.rb:38:in `start'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/commands/server/server_command.rb:143:in `block in perform'
    from <internal:kernel>:90:in `tap'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/commands/server/server_command.rb:134:in `perform'
    from /bundle/ruby/3.1.0/gems/thor-1.1.0/lib/thor/command.rb:27:in `run'
    from /bundle/ruby/3.1.0/gems/thor-1.1.0/lib/thor/invocation.rb:127:in `invoke_command'
    from /bundle/ruby/3.1.0/gems/thor-1.1.0/lib/thor.rb:392:in `dispatch'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/command/base.rb:87:in `perform'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/command.rb:48:in `invoke'
    from /bundle/ruby/3.1.0/gems/railties-7.0.0/lib/rails/commands.rb:18:in `<main>'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/loaded_features_index.rb:100:in `register'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
    from /bundle/ruby/3.1.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
    from bin/rails:4:in `<main>'
ERROR: 1

Active Jobを有効にするとproductionで正常に起動できました。Rails 7ではTurboが標準で入るので、Active Jobは省略できないようです(Turboをひっぺがせば可能なのかもしれませんが)。

関連記事

Propshaft gem README(翻訳)

tailwindcss-rails README(翻訳)

dartsass-rails README(翻訳)

jsbundling-rails README(翻訳)

Rails: Webpacker→jsbundling-rails+webpackアップグレード手順(翻訳)

Rails: Webpacker v5からShakapacker v6へのアップグレードガイド(翻訳)


CONTACT

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