こんにちは、akitoshigaです。
先日、アセットファイルをapp/assets/**
以外の場所に配置した上でmanifest.js
を利用して複数の場所からアセットパイプラインを走らせる必要が生じたのですが、類似の事例が見つからず自身が実践した方法を共有します。
1. 前提:アセットパイプラインとは
アセットパイプラインとは、アセットファイル(画像・CSS・JavaScriptなど)を効率的に配信するためのフレームワークでありRuby on Railsに組み込まれています。
複数のCSSファイルを圧縮して一つにまとめたり、フィンガープリントを利用したキャッシングの管理をしてくれます。
アセットパイプラインは内部でsprockets
やimportmap-rails
というgemを利用しています。
詳しい説明はRailsガイドをご覧ください。
2. やりたいこと
下記のように、Ruby on Rails指定のアセットファイルの置き場であるapp/assets/
配下に加えてpacks/new_module/app/assets/
配下にアセットファイルを配置します。
その上でpacks/new_module/app/assets/config/manifest.js
を読み込んでビューのレイアウトからアセットファイルの読み込みを行おうとしていました。
※ 以降一部のディレクトリ名やファイル名は実際のものから変更しています。
project_root
└── app Rails指定
│ └── assets
│ ├── config
│ │ └── manifest.js
│ ├── images
│ ├── javascripts
│ └── stylesheets
└── packs ⭐追加
└── new_module
└── app
└──assets
├── config
│ └── manifest.js
├── images
├── javascripts
└── stylesheets
3. 何故やりたいのか
モジュラーモノリスなRailsアプリケーションを構築するために、packs-rails
を用いてビューファイルやそこで使うアセットファイルを一部別のディレクトリに切り出したかったためです。
packs-rails
を簡単に説明すると、ドキュメントルートのapp/
と同じ階層にpacks/
というディレクトリを作り、その配下で複数のパッケージ構成を可能にするgemです。
packs-rails
指定のディレクトリ構成に従う代わりに面倒なオートロードの設定を省略してくれます。
しかし、利用する過程でassets
は対象外だったことが発覚したため今回自力での対応に至りました。
4. 最初に行ったこと
4.1. アセットファイルの追加
- 上記のディレクトリにアセットファイルを追加します。
- 追加したアセットファイルを読み込むための
manifest.js
をpacks/new_module/assets/config/
配下に追加します。
//= link_tree ../images
//= link_tree ../stylesheets .css
//= link_tree ../javascripts .js
4.2. 追加したファイルをアセットパイプラインに追加
追加したファイルをアセットファイルパイプラインの対象とするためにassets.rb
に定義を追加します。
今回のケースではpacks-rails
を利用しているためpacks/new_module/config/initializers/assets.rb
を追加してそこにnew_module
で使用するアセットファイルの定義を追加しました。
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','config')
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','stylesheets')
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','javascripts')
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','images')
5. しかし、反映されない
Railsを再起動して、アセットファイルを使用しているビューにアクセスしてみるとSprockets::Rails::Helper::AssetNotPrecompiledError
というエラーが発生します。
新しく配置したアセットファイルがプリコンパイルされず読み込むことができないためにエラーが送出されているようです。
6. 検証してみる
6.1 パスの確認
assets.rb
で指定したパスが正しく読み込まれているか確認します。
Railsコンソールを開いて以下の値を確認します。
Rails.application.config.assets.paths
そうすると以下のような形で読み込み対象のパスの一覧が出力されるようになります。
=> ["/path/to/document/app/assets/config",
"/path/to/document/app/assets/images",
"/path/to/document/app/assets/javascripts",
"/path/to/document/app/assets/others",
"/path/to/document/app/assets/stylesheets",
"/path/to/document/vendor/assets/javascripts",
"/path/to/document/vendor/assets/stylesheets",
#<Pathname:/path/to/project_root/packs/new_module/app/assets/config>,
#<Pathname:/path/to/project_root/packs/new_module/app/assets/stylesheets>,
#<Pathname:/path/to/project_root/packs/new_module/app/assets/javascripts>,
#<Pathname:/path/to/project_root/packs/new_module/app/assets/images>]
出力されたパスを環境のls
コマンドの引数にわたすと意図したファイルが出力されるのでパスはあっていそうです。
6.2 プリコンパイルしてみる
rakeタスクassets:precompile
を実行してアセットファイルを出力してみます。
$ bundle exec rails assets:precompile
このタスクを実行すると開発環境でもpublic/assets/
にアセットファイルがプリコンパイルされてひとまとめに出力されます。
その中を確認するとやはり新しく配置した静的アセットファイルが入っていませんでした。
上記のことからパスは合っている、呼び出し先が間違っているのではなくプリコンパイルがされていないことが確認できました。
7. 解決方法
試行錯誤の結果、以下の解決方法にたどりつきました。
7.1. manifest.jsのファイル名を変更
新しく追加した方のmanifest.js
のファイル名を変更します。
自分の場合は下記のようにモジュール名をプレフィックスにしました。
new_module_manifest.js
7.2 プリコンパイルの対象に追加する
new_module_manifest.js
がプリコンパイルの対象となるように新しい方のassets.rb
に定義を追加します。
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','config')
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','stylesheets')
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','javascripts')
Rails.application.config.assets.paths << Rails.root.join('packs','new_module','app','assets','images')
Rails.application.config.assets.precompile += %w[new_module_manifest.js] # ⭐追加
これらを実施することで無事アセットファイルを読み込めるようになりました!
sproketsはapp/assets/config/
配下のmanifest.js
以外で、トップレベルターゲットを定義するファイルを用意する場合は、都度対象となるファイルを定義しなければいけないようです。(ドキュメント等確認しましたが明確に記載されておらず、、、どなたか情報提供お待ちしています。)
8. まとめ
おわって見れば単純な問題でしたが、解決に結構かかりました。
現在Ruby on Railsアプリケーションのモジュラーモノリス化は注目されていると思うので、同じような試みをされている方の一助になれば幸いです🙏