TL; DR(最初に結論)
2024年にrails newするなら、dartsass-railsを使おう。
そして、sass/scssファイルに画像のURLを指定する場合はimage-urlではなくurlを使おう。
.my-image { background-image: url('bg.png'); }
はじめに
Qiitaに書こうと思ったけど、記事としてきれいにまとまらないのでこっちに雑にまとめます。
たとえばRailsアプリで背景画像を出したいとき、かつSassを使っているとき、画像のURLはimage-urlで指定していました。
.my-image { background-image: image-url('bg.png'); }
ちなみにbg.pngはapp/assets/images/bg.pngに配置されている前提です。
上のscssファイルはプリコンパイルされるとダイジェスト付きのURLに変わります。(image-urlもurlに変わる)
.my-image { background-image: url(/assets/bg-31822...4a19b.png); }
が、image-urlはsass-railsやsassc-railsが提供している関数です。
そして、sass-railsやsassc-railsはすでに開発が止まっていて、現在開発が継続しているのはdartsass-railsです。
ただし、dartsass-railsではimage-urlは使えません。
冒頭のscssファイルをプリコンパイルするとurlの中身はダイジェスト付きのパスになるものの、image-urlがimage-urlのままになり、結果として無効なCSSになります。
.my-image { background-image: image-url(/assets/bg-31822...4a19b.png); }
じゃあどうしたらいいのかというと、scssにurlを指定します。
.my-image { background-image: url('bg.png'); }
こうすると正しいCSSが出力されます。
.my-image { background-image: url(/assets/bg-31822...4a19b.png); }
なお、この記事ではimage-urlを対象にしていますが、asset-url, font-url, video-url, audio-urlも考え方は同じです。
コラム:Railsで使えるSassのgemは3種類
歴史的経緯により、Railsで使えるSassのgemは、sass-rails、sassc-rails、dartsass-railsの3種類です。
さらにややこしいことに、sass-rails 6はsassc-railsのラッパーです。
This gem is now only just a wrapper around sassc-rails.
よって、これら3つのgemを古いものから順に並べると、以下のようになります。
- sass-rails 5以下(Rubyで実装されたオリジナルのSass。開発終了)
- sass-rails 6 = sassc-rails(Cで実装されたSass。開発終了)
- dartsass-rails(Dartで実装されたSass)
このほかにも、gemを使わずにnpmとして提供されているSassを使う、というアプローチもあります。
いろいろあってややこしいですね。
僕の疑問
上記のような挙動を知って、僕は以下のような疑問を持ちました。
image-urlってそもそもどこで定義されてるの??url(...)って標準のCSSなのに、なんでdartsass-railsだとプリコンパイルされたらダイジェスト付きのパスに変わるの??
それぞれ調査した結果を以下にまとめます。
セルフアンサー:image-urlはどこで定義されてるの??
image-urlはsprockets gemのimage_urlメソッドにマッピングされます。
def image_url(path) asset_url(path, type: :image) end
asset_urlメソッドはsassc-railsに定義されていて、これがさらにasset_pathメソッドを呼び出します。
こうした一連の流れにより、image-urlの引数として指定したパスはasset_pathメソッドでダイジェスト付きのパスに変換されます。
def asset_path(path, options = {}) path = path.value path, _, query, fragment = URI.split(path)[5..8] path = sprockets_context.asset_path(path, options) query = "?#{query}" if query fragment = "##{fragment}" if fragment ::SassC::Script::Value::String.new("#{path}#{query}#{fragment}", :string) end def asset_url(path, options = {}) ::SassC::Script::Value::String.new("url(#{asset_path(path, options).value})") end
しかし、dartsass-railsではasset_urlメソッドやasset_pathメソッドに相当する処理がありません。
セルフアンサー:なんでurlがダイジェスト付きのパスに変わるの??
これはsprockets-rails 3.3でurlに指定したパスをダイジェスト付きのパスに変換する機能が導入されたためです。
github.com
つまり、これはdartsass-railsではなく、sprockets-railsの機能です。
この機能は正規表現で実現されています。
module Sprockets module Rails class AssetUrlProcessor REGEX = /url\(\s*["']?(?!(?:\#|data|http))(?\.\/)?(? "'\s)]+)\s*["']?\)/ def self.call(input) context = input[:environment].context_class.new(input) data = input[:data].gsub(REGEX) do |_match| path = Regexp.last_match[:path] "url(#{context.asset_path(path)})" end context.metadata.merge(data: data) end end end end[^
実装上はurl(...)みたいな文字列を置換するだけなので、image-url(...)でもhoge-url(...)でも、正規表現にマッチした文字列なら何でも()内のパスがダイジェスト付きのパスに変換されます。
.my-image { background-image: hoge-url('bg.png'); }
.my-image { background-image: hoge-url(/assets/bg-31822...4a19b.png); }
また、sprockets-railsが提供している機能なので、SassではないプレーンなCSSでもアセットプリコンパイルの対象になっていればurlがダイジェスト付きのパスに変換されます。
.my-image { background-image: url(bg.png); }
.my-image { background-image: url(/assets/bg-31822...4a19b.png); }
ちなみにsprocets-rails 3.2以前ではurlはプリコンパイルしても何も変わらず、そのまま出力されます*1。
.my-image { background-image: url(bg.png); }
まとめ
わかったことをまとめるとこんな感じです。
image-urlはsass-railsやsassc-railsが提供している関数image-urlはdartsass-railsには存在しない- バージョン3.3以降のsprockets-railsは
urlをダイジェスト付きのパスに変換してくれる - dartsass-railsは
urlを使うしかない - sass-railsやsassc-railsはsprockets-rails 3.3以上がインストールされていれば、
image-urlもurlも両方使える
表にするとこんな感じでしょうか。
| 組み合わせ | |||
|---|---|---|---|
| sass-rails, sassc-rails | Y | Y | |
| dartsass-rails | Y | ||
| sprockets-rails 3.2以下 | Y | ||
| sprockets-rails 3.3以上 | Y | Y | |
| image-url | ✅ | ✅ | ❌ 2 |
| url | ❌ 1 | ✅ | ✅ |
- ❌ 1 =
()内のパスは変化しない(ダイジェスト付きにならない) - ❌ 2 = パスにダイジェストは付くものの、
image-urlがimage-urlのまま残るため、無効なCSSになる
2024年現在でrails newする場合、Sassを使いたいならdartsass-railsを使うことになるはずです(なぜならsass-railsやsassc-railsは開発が終了しているから)。
また、sprockets-rails 3.3がリリースされたのは2021年11月なので、3.2以前のsprockets-railsがインストールされることはまずないでしょう。
ということはSassで画像URLを指定する場合はimage-urlではなくurlを使う、というのが望ましい方法になりますね。
.my-image { background-image: url('bg.png'); }
参考文献
github.com
おまけ
Stack Overflowに同じような質問があり、まだ誰も回答していなかったので、僕が回答してみました(2013年の質問ですが・・・)。
stackoverflow.com
あわせて読みたい
アセットプリコンパイル周りは初心者泣かせのややこしい挙動が満載です。画像がうまく表示できないときはこちらの記事も参考にしてみてください。
qiita.com
Views: 2
