Sprockets 4: `url(relative.css)` issue

Sprockets 4: `url(relative.css)` issue

Disclaimer: sprockets in general is phased out in the Rails community, still some people need the fix ... I needed a fix today.

Issue: I have an old gem that reference an relative image in the CSS background-image: url("icon.png")) which once compile it become:

  • Sprockets 3: background-image: url("icon.png")) (it's a relative URL)

  • Sprockets 4: background-image: url("/icon.png")) (the / means root URL, eg http://example.org/icon.png)

  • With the fix below: background-image: url("./icon.png")) (./ is also a relative URL)

Notes about relative URL:

  • If the CSS file is http://example.com/application.css

    • then the icon.png is also at the root: http://example.com/icon.png
  • If the CSS file is in a gem (for me it was CKeditor), the CSS file is http://example.com/assets/ckeditor/skins/moono-lisa/editor.css

    • then the icon.png is resolved as http://example.com/assets/ckeditor/skins/moono-lisa/icon.png
# config/initializers/sprockets.rb

# Issue exists: https://github.com/rails/sprockets-rails/issues/507
# Fix from PR: https://github.com/rails/sprockets-rails/pull/511
# It will not get merge as Sprockets is phased out.
module Sprockets
  module Rails
    # Resolve assets referenced in CSS `url()` calls and replace them with the digested paths
    class AssetUrlProcessor
      REGEX = /url\(\s*["']?(?!(?:\#|data|http))(?<relativeToCurrentDir>\.\/)?(?<path>[^"'\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]
          # Prevent non-digest relative path from being converted to root absolute path
          orig_path = path
          path = context.asset_path(path)
          path = (path == "/#{orig_path}") ? ".#{path}" : path


        context.metadata.merge(data: data)

Caveat: the original file did not change therefore the digest did not either, until the browser invalid the the cache and download the new CSS, users will have the old CSS cached by the browser

Please share and comment on how I can write it better.