Propshaft で node_modules 内のファイルをアセットとして使う
結論
これから作るものについては、 node_modules を assets パスに含めようとするのはやめよう
動機
importmap-rails で node_modules
以下のファイルを指定したい。
config/importmap.rb
の to:
は asset_path
で解決できるものを指定できるので、アセットパスに追加すれば使える。
経過
paths に node_modules を追加すれば使えはするが
次のように node_modules
を追加すれば参照できるが、Propshaftは paths 以下にあるすべてのファイルをプリコンパイルの際に public/assets
にコピーするため、不要なものを含む大量のファイルをコピーしてしまい、よくない。
Rails.configuration.assets.paths << Rails.root.join("node_modules")
package.json の dependencies 記載のもののみに…も問題あり
必要なものは dependencies
に追加することとして、そのモジュールのみ追加すれば、いくらかましになる。
node_modules = Rails.root.join("node_modules")
Rails.application.config.assets.paths << node_modules
package_json = Rails.root.join("package.json")
Rails.configuration.watchable_files << package_json
Rails.configuration.to_prepare do
node_config = JSON.parse(package_json.read)
Rails.configuration.assets.excluded_paths.reject! { |p| p.to_s.start_with?(node_modules.to_s) }
node_config["dependencies"].keys.each do |node_module|
Rails.application.config.assets.paths << node_modules.join(node_module)
end
end
が、各モジュールの下に同じディレクトリが存在する場合、最初にマッチしたものが優先されるので、うまくいかない。
pin "jquery", to: "dist/jquery.min.js"
pin "jquery-ui", to: "dist/jquery-ui.min.js" # Importmap skipped missing path: dist/jquery-ui.min.js
excluded_paths は使えないか?使えない
excluded_paths で追加したディレクトリは除外できます。とあるので、これをつかって依存関係を明記していないモジュールのディレクトリを除外設定すればどうか?
例えばこうだ。
node_modules = Rails.root.join("node_modules")
Rails.application.config.assets.paths << node_modules
package_json = Rails.root.join("package.json")
Rails.configuration.watchable_files << package_json
Rails.configuration.to_prepare do
node_config = JSON.parse(package_json.read)
# 依存関係にないパッケージは除外できる?
Rails.configuration.assets.excluded_paths.reject! { |p| p.to_s.start_with?(node_modules.to_s) }
(Dir.children(node_modules) - node_config["dependencies"].keys).each do |package|
# ディレクトリを指定するならこう?
Rails.configuration.assets.excluded_paths << node_modules.join(package)
# フルパスで指定するならこう?
Dir.glob(node_modules.join(package, "**", "*")).each do |exclude_path|
Rails.configuration.assets.excluded_paths << exclude_path
end
end
end
効果なし。プリコンパイルですべてのファイルがコピーされてしまった。
コードを確認すると、Engineでパスが追加された際に、追加されたパスの中から除外するものであり、パスを参照するときのフィルタではないことがわかった。なるほどね…先人の知恵…もやはりうまくいかない
package.json
の main
で指定されているもののパスを追加するので、フォルダ名被り問題は発生しない。
が、 main
が必ずしも参照したいファイルのディレクトリとは限らず、うまくいかない。
main": "dist/jquery.js",
"main": "ui/widget.js",
pin "jquery", to: "jquery.min.js" # node_modules/jquery/dist が paths に含まれているのでok
pin "jquery-ui-dist", to: "jquery-ui.min.js" # node_modules/jquery-ui-dist/ui が paths に含まれているので jquery-ui.min.js は見つからなくてng
まとめ
Propshaft はそういうのやってない。考え方を変えよう。
必要なファイルがあるときはCDNで参照するか、 vendor/javascript
にダウンロードするのが正しい方法だと思う。
調べごとの過程で、 Propshaft や importmap-rails のコードを読むことになり、勉強になったので良かった。
Discussion