本ガイドでは、JavaScript機能をRailsアプリケーションに統合する方法について解説します。外部のJavaScriptパッケージを利用する場合に使えるオプションや、RailsでTurboを使う方法についても解説します。
このガイドの内容:
import mapsは、バージョン付けされたファイルに対応する論理名を用いてJavaScriptモジュールをブラウザで直接importできます。import mapsはRails 7からデフォルトになっており、トランスパイルやバンドルの必要なしにほとんどのnpmパッケージを用いて誰でもモダンなJavaScriptアプリケーションを構築できるようになります。
import mapsを利用するアプリケーションは、Node.jsやYarnなしで機能します。RailsのJavaScript依存関係をimportmap-rails
で管理する予定であれば、Node.jsやYarnをインストールする必要はありません。
import mapsを利用する場合、別途ビルドプロセスを実行する必要はなく、bin/rails server
コマンドでサーバーを起動するだけでOKです。
Importmap for Railsは、Rails 7以降の新規アプリケーションに自動的に含まれていますが、既存のアプリケーションに手動でインストールすることも可能です。
$ bin/bundle add importmap-rails
以下のインストールタスクを実行します。
$ bin/rails importmap:install
import mapを利用するアプリケーションに新しいパッケージを追加するには、ターミナルで以下のようにbin/importmap pin
コマンドを実行します。
$ bin/importmap pin react react-dom
続いて、従来と同様にapplication.js
ファイルでパッケージをimport
します。
import React from "react" import ReactDOM from "react-dom"
import mapsは新規Railsアプリケーションのデフォルトですが、従来のJavaScriptバンドラーを使いたい場合は、新規Railsアプリケーション作成時にいずれかを選択できます。
import mapsではなくJavaScriptバンドラーを新規Railsアプリケーションで利用するには、以下のようにrails new
コマンドに—-javascript
または-j
オプションを渡します。
$ rails new my_new_app --javascript=bun # または $ rails new my_new_app -j bun
どのバンドルオプションにも、シンプルな設定と、jsbundling-rails gemによるアセットパイプラインとの統合が用意されています。
バンドルオプションを利用する場合は、development環境でのRailsサーバー起動とJavaScriptのビルドにbin/dev
コマンドをお使いください。
Railsアプリケーションでesbuild、Rollup.js、webpackのいずれかをJavaScriptランタイムとして使う場合は、Node.jsとYarnをインストールしなければなりません。
Bunを使う場合は、BunをJavaScriptのランタイムとバンドラーの兼用としてインストールするだけで済みます。
Bunウェブサイトでインストール手順を見つけ、インストール後にプロジェクトのパスで以下のコマンドを実行して、正しいパスにインストールされているかを確認してください。
$ bun --version
インストールされているBunランタイムのバージョンが表示されるはずです。1.0.0
のようにバージョンが表示されれば、Bunは正しくインストールされています。
バージョンが表示されない場合は、Bunを現在のディレクトリで再インストールするか、ターミナルを再起動する必要があるかもしれません。
Railsアプリケーションでesbuild、rollup.js、webpackのいずれかを使う場合は、Node.jsとYarnをインストールしなければなりません。
Node.jsのインストール方法についてはNode.js Webサイトを参照してください。また、以下のコマンドで正しくインストールされたかどうかを確認してください。
$ node --version
Node.jsランタイムのバージョンが出力されるはずです。必ず8.16.0
より大きいバージョンをお使いください。
Yarnのインストール方法についてはYarn Webサイトの手順に沿ってください。インストール後、以下のコマンドを実行するとYarnのバージョンが出力されるはずです。
$ yarn --version
1.22.0
のように表示されれば、Yarnは正しくインストールされています。
Railsアプリケーションを新規作成する場合、import mapsとJavaScriptバンドラーのどちらかのソリューションを選ぶ必要があります。アプリケーションごとに要件は異なるので、JavaScriptのオプションを決める際には十分注意してください。特に大規模で複雑なアプリケーションほど、後から別のオプションに乗り換えようとすると時間がかかる可能性があります。
Railsチームは、import mapsが複雑さを削減して開発者のエクスペリエンスやパフォーマンスを向上させる能力を持っていると信じているので、import mapsがデフォルトのオプションとして選ばれています。
多くのアプリケーション、特にJavaScriptのニーズをHotwireスタックに依存しているアプリケーションにおいては、import mapが長期的に正しい選択肢となるでしょう。Rails 7でimport mapsがデフォルトのオプションになった背景についてはこちらの記事を参照してください。
それ以外のアプリケーションでは、引き続き従来のJavaScriptバンドラーが必要になることもあるでしょう。従来のJavaScriptバンドラーを選択すべきであることを示唆する要件は以下のとおりです。
rails new
で特に別のオプションを指定しなかった場合は、cssbundling-rails gemが自動的にesbuild
をインストールします(TailwindやSassを選んだ場合はインストールされません)。Turboは、import mapsを選ぶか従来のJavaScriptバンドラーを選ぶかどうかにかかわらず、Railsアプリケーションに同梱されます。Turboは、書かなければならないJavaScriptコード量を劇的に減らしつつ、アプリケーションを高速化します。
Turboは、Railsアプリケーションのサーバーサイドの役割をJSON API専用同然に縮小するさまざまなフロントエンドフレームワークとは異なる手法を用いるもので、サーバーから直接HTMLを配信できるようにします。
Turbo Driveは、ページ遷移リクエストのたびにページ全体を取り壊して再構築する動作を回避する形でページの読み込みを高速化します。
Turbo Framesは、ページの他の部分に影響を及ぼさずに、ページで事前定義された部分をリクエストに応じて更新できるようにします。
Turbo Framesを使うと、カスタムJavaScriptをまったく書かずにインプレース編集機能を構築したり、コンテンツを遅延読み込みしたり、サーバーレンダリングされたタブインターフェイスを作成したりする作業が手軽に行なえます。
Railsでは、turbo-rails gemを介してTurbo Framesを手軽に利用できるHTMLヘルパーを提供します。
このgemを使うと、アプリケーションで以下のようにturbo_frame_tag
ヘルパーを用いてTurbo Framesを追加できるようになります。
<%= turbo_frame_tag dom_id(post) do %> <div> <%= link_to post.title, post_path(post) %> </div> <% end %>
Turbo Streamsは、ページの変更を自己実行型の<turbo-stream>
要素でラップされたHTMLフラグメントとして配信します。Turbo Streamsを用いることで、他のユーザーによる変更内容をWebSocket上でブロードキャストしたり、フォーム送信後にページ全体を更新する必要なしにページの一部のみを更新したりできるようになります。
Railsでは、turbo-rails gemを介してTurbo Streamsを手軽に利用できるHTMLヘルパーを提供します。
このgemを使うと、以下のようにコントローラのアクションでTurbo Streamsをレンダリングできます。
def create @post = Post.new(post_params) respond_to do |format| if @post.save format.turbo_stream else format.html { render :new, status: :unprocessable_entity } end end end
Railsは自動的に.turbo_stream.erb
ビューファイルを探索し、見つかったらそのビューをレンダリングします。
Turbo Streamsのレスポンスも、以下のようにコントローラのアクションでインラインレンダリングできます。
def create @post = Post.new(post_params) respond_to do |format| if @post.save format.turbo_stream { render turbo_stream: turbo_stream.prepend("posts", partial: "post") } else format.html { render :new, status: :unprocessable_entity } end end end
最後に、Turbo Streamsは組み込みヘルパーを用いてモデルやバックグラウンドジョブから開始できます。これらのブロードキャストは、WebSocketコネクション経由で全ユーザーのコンテンツを更新するのにも利用可能で、ページの内容を常に最新に保って生き生きとしたアプリケーションにすることができます。
モデルでTurbo Streamsをブロードキャストするには、以下のようにモデルのコールバックと組み合わせます。
class Post < ApplicationRecord after_create_commit { broadcast_append_to("posts") } end
WebSocketによって、更新を受け取る以下のようなページとのコネクションが設定されます。
<%= turbo_stream_from "posts" %>
Rails 6にはUJS(Unobtrusive JavaScript)というツールが同梱されていました。UJSは、開発者が<a>
タグをオーバーライドすることでハイパーリンクのクリック後に非GETリクエストを実行し、アクション実行前に確認ダイアログを追加できるようにします。Rails 7より前はこの方法がデフォルトでしたが、現在はTurboの利用が推奨されています。
リンクをクリックすると、常にHTTP GETリクエストが発生します。RESTfulなアプリケーションでは、実際には一部のリンクがサーバーのデータを変更するアクションを起動しますが、これは非GETリクエストで実行されるべきです。data-turbo-method
属性を利用することで、そうしたリンクをPOSTやPUTやDELETEなどのHTTPメソッドで明示的にマークアップできるようになります。
Turboは、アプリケーション内の<a>
タグをスキャンしてturbo-method
データ属性があるかどうかを調べ、HTTPメソッドが指定されている場合はそのHTTPメソッドを使う形で、デフォルトのGETアクションをオーバーライドします。
例:
<%= link_to "投稿を削除", post_path(post), data: { turbo_method: "delete" } %>
上のERBは以下のHTMLを生成します。
<a data-turbo-method="delete" href="...">投稿を削除</a>
HTTPメソッドの変更は、data-turbo-method
属性をリンクに追加する方法の他に、Railsのbutton_to
ヘルパーでもできます。なお実際には、アクセシビリティの観点から、非GETアクションには(リンクではなく)ボタンとフォームを用いるのが望ましい方法です。
リンクやフォームにdata-turbo-confirm
属性を追加することで、ユーザーに確認ダイアログを表示して確認を求めることができます。リンクのクリックやフォームの送信では、JavaScriptのconfirm()
ダイアログに属性のテキストを含んだものが表示されます。ユーザーがキャンセルを選択するとアクションは行われません。
たとえばlink_to
ヘルパーを用いると、
<%= link_to "投稿を削除", post_path(post), data: { turbo_method: "delete", turbo_confirm: "削除してよろしいですか?" } %>
以下が生成されます。
<a href="..." data-turbo-confirm="削除してよろしいですか?" data-turbo-method="delete"> 投稿を削除 </a>
ユーザーがこの"投稿を削除"リンクをクリックすると、"削除してよろしいですか?"という確認ダイアログが表示されます。
この属性はbutton_to
ヘルパーでも利用できますが、button_to
ヘルパーが内部でレンダリングするフォームに属性を追加する必要があります。
<%= button_to "Delete post", post, method: :delete, form: { data: { turbo_confirm: "削除してよろしいですか?" } } %>
JavaScriptからGET以外のリクエストを行う場合、X-CSRF-Token
ヘッダーが必要です。このヘッダーがないと、Railsはリクエストを受け付けません。
このトークンは、クロスサイトリクエストフォージェリ(CSRF)攻撃を防ぐためにRailsで必須になっています。詳しくはセキュリティガイドをお読みください。
RailsリポジトリにあるRequest.JSは、Railsで必須のリクエストヘッダーを追加するロジックをカプセル化したものです。このパッケージからFetchRequest
クラスをインポートして、リクエストメソッド、URL、オプションを渡してインスタンス化し、await request.perform()
を呼び出せば、レスポンスに必要な処理を行えます。
以下の例を見てみましょう。
import { FetchRequest } from '@rails/request.js' .... async myMethod () { const request = new FetchRequest('post', 'localhost:3000/posts', { body: JSON.stringify({ name: 'Request.JS' }) }) const response = await request.perform() if (response.ok) { const body = await response.text } }
その他のライブラリを利用してAjax呼び出しを行う場合、自分でセキュリティトークンをデフォルトヘッダーとして追加する必要があります。このトークンを取得するには、csrf_meta_tags
から出力される<meta name='csrf-token' content='(トークン)'>
を参照します。トークンの取得は以下のような感じで行なえます。
document.head.querySelector("meta[name=csrf-token]")?.content
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。