From a536fadd85eb1acb726569094eee8ea2cee334b8 Mon Sep 17 00:00:00 2001 From: Alex Mekhovov Date: Sun, 20 Feb 2022 17:40:21 +0100 Subject: [PATCH] Process css files so that they get digested paths for asset files. Add assets.resolve_assets_in_css_urls configuration option to allow disabling AssetUrlProcessor --- README.md | 3 + lib/sprockets/rails/asset_url_processor.rb | 22 +++++++ lib/sprockets/railtie.rb | 8 +++ test/test_asset_url_processor.rb | 70 ++++++++++++++++++++++ test/test_railtie.rb | 17 ++++++ 5 files changed, 120 insertions(+) create mode 100644 lib/sprockets/rails/asset_url_processor.rb create mode 100644 test/test_asset_url_processor.rb diff --git a/README.md b/README.md index 702277c0..371961f5 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,9 @@ config.assets.configure do |env| end ``` +**`config.assets.resolve_assets_in_css_urls`** + + When this option is enabled, sprockets-rails will register a CSS postprocessor to resolve assets referenced in [`url()`](https://developer.mozilla.org/en-US/docs/Web/CSS/url()) function calls and replace them with the digested paths. Defaults to `true`. ## Complementary plugins diff --git a/lib/sprockets/rails/asset_url_processor.rb b/lib/sprockets/rails/asset_url_processor.rb new file mode 100644 index 00000000..72323849 --- /dev/null +++ b/lib/sprockets/rails/asset_url_processor.rb @@ -0,0 +1,22 @@ +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))(?\.\/)?(?[^"'\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] + begin + "url(#{context.asset_path(path)})" + rescue => e + puts "AssetUrlProcessor: Error processing asset |#{path}|: #{e.class.name}: #{e.message}" + "url(#{path})" + end + end + + context.metadata.merge(data: data) + end + end + end +end diff --git a/lib/sprockets/railtie.rb b/lib/sprockets/railtie.rb index 9446745d..fbf875c8 100644 --- a/lib/sprockets/railtie.rb +++ b/lib/sprockets/railtie.rb @@ -5,6 +5,7 @@ require 'sprockets' require 'sprockets/rails/helper' require 'sprockets/rails/version' +require 'sprockets/rails/asset_url_processor' module Rails class Application @@ -77,6 +78,13 @@ def configure(&block) config.assets.debug = false config.assets.compile = true config.assets.digest = false + config.assets.resolve_assets_in_css_urls = true + + initializer :asset_url_processor do |app| + if app.config.assets.resolve_assets_in_css_urls + Sprockets.register_postprocessor "text/css", ::Sprockets::Rails::AssetUrlProcessor + end + end rake_tasks do |app| require 'sprockets/rails/task' diff --git a/test/test_asset_url_processor.rb b/test/test_asset_url_processor.rb new file mode 100644 index 00000000..7b1bdac2 --- /dev/null +++ b/test/test_asset_url_processor.rb @@ -0,0 +1,70 @@ +require 'minitest/autorun' +require 'sprockets/railtie' + + +Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) +class TestAssetUrlProcessor < Minitest::Test + FIXTURES_PATH = File.expand_path("../fixtures", __FILE__) + + def setup + @env = Sprockets::Environment.new + @env.append_path FIXTURES_PATH + @env.context_class.class_eval do + include ::Sprockets::Rails::Context + end + @env.context_class.digest_assets = true + + @logo_digest = @env["logo.png"].etag + @logo_uri = @env["logo.png"].uri + end + + def test_basic + input = { environment: @env, data: 'background: url(logo.png);', filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + assert_equal("background: url(/logo-#{@logo_digest}.png);", output[:data]) + end + + def test_spaces + input = { environment: @env, data: 'background: url( logo.png );', filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + assert_equal("background: url(/logo-#{@logo_digest}.png);", output[:data]) + end + + def test_single_quote + input = { environment: @env, data: "background: url('logo.png');", filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + assert_equal("background: url(/logo-#{@logo_digest}.png);", output[:data]) + end + + def test_double_quote + input = { environment: @env, data: 'background: url("logo.png");', filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + assert_equal("background: url(/logo-#{@logo_digest}.png);", output[:data]) + end + + def test_dependencies_are_tracked + input = { environment: @env, data: 'background: url(logo.png);', filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + assert_equal(1, output[:links].size) + assert_equal(@logo_uri, output[:links].first) + end + + def test_relative + input = { environment: @env, data: 'background: url(./logo.png);', filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + assert_equal("background: url(/logo-#{@logo_digest}.png);", output[:data]) + end + + def test_subdirectory + input = { environment: @env, data: "background: url('jquery/jquery.js');", filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + jquery_digest = 'c6910e1db4a5ed4905be728ab786471e81565f4a9d544734b199f3790de9f9a3' + assert_equal("background: url(/jquery/jquery-#{jquery_digest}.js);", output[:data]) + end + + def test_protocol_relative_paths + input = { environment: @env, data: "background: url(//assets.example.com/assets/fontawesome-webfont-82ff0fe46a6f60e0ab3c4a9891a0ae0a1f7b7e84c625f55358379177a2dcb202.eot);", filename: 'url2.css', metadata: {} } + output = Sprockets::Rails::AssetUrlProcessor.call(input) + assert_equal("background: url(//assets.example.com/assets/fontawesome-webfont-82ff0fe46a6f60e0ab3c4a9891a0ae0a1f7b7e84c625f55358379177a2dcb202.eot);", output[:data]) + end +end diff --git a/test/test_railtie.rb b/test/test_railtie.rb index 1be43334..46929fa3 100644 --- a/test/test_railtie.rb +++ b/test/test_railtie.rb @@ -256,4 +256,21 @@ def test_manifest_path_respects_rails_public_path assert_match %r{test_public/assets$}, manifest.dir end end + + def test_resolve_assets_in_css_urls_defaults_to_true + app.initialize! + + assert_equal true, app.config.assets.resolve_assets_in_css_urls + assert_includes Sprockets.postprocessors['text/css'], Sprockets::Rails::AssetUrlProcessor + end + + def test_resolve_assets_in_css_urls_when_false_avoids_registering_postprocessor + app.configure do + config.assets.resolve_assets_in_css_urls = false + end + app.initialize! + + assert_equal false, app.config.assets.resolve_assets_in_css_urls + refute_includes Sprockets.postprocessors['text/css'], Sprockets::Rails::AssetUrlProcessor + end end