Rails 7の裏側でのImport Maps

Rails 7では、Import Mapsという新機能により、Node.jsやWebpackといったツールとさよならを言うことができます。もうバンドルする必要はありません。この新しい仕組みを使うと、特定のバージョンのJavaScriptライブラリを管理し続けることができますが、一つの大きなファイルではなく、多くの小さなJavaScriptファイルがアプリケーションによって提供されます。

Import Mapsの動作について理解することは、Railsの新しいバージョンから恩恵を受けるために必須です(でも心配しないでください、Webpackのようなツールを代わりに使い続けることもできます)。ですが、何が裏側で起こっているのかを発見することも価値があります。それによって、プロジェクトにJavaScriptライブラリをインストールしてから、その内容をユーザーに提供するまでのプロセスをより理解できます。

この記事では、Import Mapsを使ってJavaScriptライブラリをインストールし、提供し、アンインストールする方法と、それぞれのフェースで裏側で何が起きているのかを見せます。

Import Mapsのコア

特徴自体は複雑ではありません。でも、ライブラリ内部で起こることを見せる前に、JSPMツールの紹介をしたいと思います。JSPMとはJavaScript Package Managementの略です。このツールのおかげで、追加のツールなしにブラウザ内で任意のNPMパッケージをロードでき、完全に最適化されます。

RailsはJSPMを使用してアプリケーションでJavaScriptライブラリを提供します。ソースファイルをvendorディレクトリにダウンロードするか、URLから直接コードを提供するかができます。

例えば、jQueryライブラリにアクセスするには、ブラウザでhttps://api.jspm.io/generate?install=jqueryのURLを呼び出し、JSONレスポンスで縮小されたソースコードへのURLが受け取れます。もちろん、リクエストに対してもっと色々なオプションが提供されていますが、RailsがImport MapsライブラリでJSPMとどのように協力しているかを理解するには、この知識で十分です。

RailsプロジェクトでImport Mapsをインストールする

Import Mapsの機能はRubyのgemとしてRailsで利用可能です。Rails 7で新しいプロジェクトを生成すると、デフォルトでGemfileに含まれています。既存のプロジェクトに追加するには、以下のコマンドを実行します:

bundle add importmap-rails

gemをインストールしたら、以下のコマンドを使って必要なファイルを生成します:

./bin/rails importmap:install

このコマンドは具体的には何をするのでしょうか?まず、importmap名前空間に含まれるインストールrakeタスクを呼び出します。このgemにはrakeタスクが付属しています。その中で標準のrails rakeタスクが実行されます:

system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/install.rb",  __dir__)}"

install.rbファイルは以下の順番でいくつかの事を行います:

  1. レイアウトのheadセクションにヘルパーを追加する - もしapplication.html.erbレイアウトがプロジェクトに存在する場合、閉じる</head>タグの前にjavascript_importmap_tags行を追加します。このヘルパーはimport mapsでピン留めされたJavaScriptライブラリを含んでいます。もし、標準のレイアウトファイルがプロジェクトに含まれていない場合には、ヘルパーについての情報をレンダリングするため、自分で追加することができます。
  2. app/javascript/application.jsファイルを作成する - 依存関係を定義するために新しい場所があることを知らせるコメントに関してimport mapsが追加されます。
  3. vendor/javascriptディレクトリを作成する - これはJavaScriptライブラリがURL経由で直接提供されていない場合に保存される場所です。
  4. それが存在する場合、sprocketsのマニフェストを更新する - sprocketsはvendor/javascriptディレクトリ内に配置されたJavaScriptファイルについて知る必要があります。
  5. config/importmap.rbファイルを作成する - これはRailsが使用するライブラリのリスト(package.jsonファイルのようなもの)を含んでいます。
  6. bin/importmapファイルをコピーし、実行するための正しい許可を設定する - このファイルは特定のライブラリをピン留めしたり外したりするのに使用されます。

このプロセスが終了すると、./bin/importmapファイルを使用してJavaScriptライブラリをインストールする準備ができ、Railsはこれらのファイルを訪問者に提供する準備ができます。

Railsプロジェクトにライブラリを追加する

Import Mapsでライブラリをインストールする際には、CDN URLから直接コードを使用するか、ファイルをダウンロードしてサーバーから直接提供するかの2つの選択肢があります。

まずCDNオプションから見ていきましょう。

JavaScript CDN経由でNPMパッケージを使用する

全てはライブラリ名とpinコマンドから始まります:

./bin/importmap pin jquery

./bin/importmap内のプログラムに2つの引数を渡します。importmapはプロジェクトからconfig/application.rbファイルとimport mapsのコマンドファイルをロードする実行可能ファイルです。

Import mapsはThor gemを使ってコマンドラインプログラムを繊細に取り扱います。最初の引数、pin、はコマンド名です。それを呼び出すと以下のことが裏側で起きます:

  • JSPM APIへのリクエストが実行される

前述したように、gemはアプリケーションにパッケージの内容を取得するためにJSPMを使用しています。従って、最初のステップはリクエストURLの形成です。pinコマンドに追加のパラメータを何も渡さない場合、リクエストURLは https://api.jspm.io/generate で以下のパラメータを含んでいます:

  • install - ["jquery"] - パラメータは配列です、なぜなら複数のパッケージを同時にインストールすることができるからです。
  • flattenScope - true - このパラメータがtrueに設定されていると、可能であればよりシンプルなスコープなしのimport mapフォーマットが戻されます。
  • env - ["browser", "module", "production"] - これは環境条件文字列のリストです。
  • provider - "jspm" - JSPMの他に、Skypack、JSdelivr、Unpkgのプロバイダーが利用可能です。

ブラウザでhttps://api.jspm.io/generate?install=jquery&flattenScope=trueにアクセスしてみると、いくつかのシンプルな属性を含むシンプルなJSONレスポンスを得ることができます。

  • JSPMからのレスポンスが解析される

解析はとてもストレートフォワードなフェーズです。見たとおり、レスポンスはシンプルで、必要なのはライブラリ名とCDN URLだけです。Packagerクラスはimport mapライブラリ内でレスポンスを解析する責任を持っています。属性を戻すかわりに、configに直接使える完全な行を戻します:

%(pin "#{package}", to: "#{url}")

'%()'機能にまだ慣れていない場合、心配しないでください、それはほぼ次のように動作します:

"pin \"#{package}\", to: \"#{url}\""

唯一の違いは、'%()'表記法は自動的に二重引用符をエスケープすることです。生成されたconfig行は、configファイルの更新プロセスを処理する別の機能に渡されます。

Import Mapのconfigが更新される

最後のステップは、pin定義でconfigファイルを更新することです。以前のpin定義が存在する可能性があるので、gemは最初にconfigファイルを確認し、既存のライブラリ定義を検索します。regexを使うと簡単です:

/^pin "#{package}".*$/

指定されたパッケージのpin定義が存在する場合、gemは行を新しい定義と置き換えます。以前の定義がない場合、gemはconfig/importmap.rbファイルの最終部に新しい行を追加します。

: この方法を使用する際には、このオプションが無料であるが、JSPMサーバーはコミュニティによって支援されている一人の人物が管理していることを念頭に置くことが重要です。この状況は将来変わる可能性があるので、プロダクションレディなアプリケーションでImport Mapsを使用するつもりなら、ライブラリをローカルに保存することをお勧めします。

NPMパッケージのダウンロード

CDN URLからソースコードを使用したくない場合、アプリケーションのvendorディレクトリにライブラリをダウンロードできます。この場合、CDN URLのケースと非常に似ています。異なるのは、ファイルにconfig行が戻される前に、コードが.jsファイルにダウンロードされることです。

ダウンロードプロセス

ダウンロードプロセスはいくつかの小さなステップで構成されます:

  • gemはFileUtils.mkdir_pを呼び出し、まだ存在していない場合はvendorディレクトリを作成します。
  • gemはFileUtils.rm_rfを呼び出し、以前のパッケージファイルが存在する場合はそれを削除します。
  • gemはJSPMが提供するURLの下に位置するJSコードを.jsファイルに保存し、vendorディレクトリに配置します。

もしgemがパッケージの内容をダウンロードすることができない場合、適切なエラーがコマンドラインに表示されます。

Config行の生成

もしパッケージ名がパッケージソースを含むファイルと同じである場合、config行は次のとおり簡単です:

%(pin "#{package}" # #{version})

それ以外の場合、import mapはパッケージ名をファイルに直接マッピングする必要があります:

%(pin "#{package}", to: "#{filename}" # #{version})

config行がフォーマットされ戻されたとき、configはパッケージ定義で更新されます(CDN URLバージョンで説明されているように)。

Railsアプリでピン留めしたパッケージを使用する

gemはRailsエンジンメカニズムを広く使用して機能を提供します。Importmap::Engineは設定を実行するために初期化プログラムの束を定義します。

config/importmap.rbからピン留めしたパッケージをインポートする

config/importmap.rbファイル内の設定行はアプリケーション内の参照に置き換えられます。まず、gemはStructオブジェクトで表される各定義を含むハッシュに全ての定義を収集します。これにより、特定の属性に簡単にアクセスすることができます。

Rails.application.importmap.packagesを呼び出すことで、アプリケーション内の全ての定義を含むハッシュにアクセスできます。

ビュー内でパッケージへの参照を含める

ピン留めされたパッケージがインポートされると、コードを提供するためにビュー内でそれらを含めることができます。gemはjavascript_importmap_tagsヘルパーを提供し、単純にレイアウト内でレンダーすることができます。これはRails.application.importmapを使用して全てのピン留めされたライブラリに対してJSON出力を生成し、Railsのasset_helperを使用してライブラリへの正しいパスを提供します。

レイアウトのheadセクションに以下の行を追加した後:

<%= javascript_importmap_tags %>

次の出力を得ることになります(jQueryのみを使用していると仮定して):

<script type="importmap" data-turbo-track="reload">
  {
    "imports": {
      "application": "/assets/application-37f365cbecf1fa2810a8303f4b6571676fa1f9c56c248528bc14ddb857531b95.js",
      "jquery": "https://ga.jspm.io/npm:jquery@3.6.0/dist/jquery.js"
    }
  }
</script>

ブラウザによる

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/appsignal/import-maps-under-the-hood-in-rails-7-4752