diff --git a/benchmarks/css_asset_urls b/benchmarks/css_asset_urls index 72d6fee..063e326 100755 --- a/benchmarks/css_asset_urls +++ b/benchmarks/css_asset_urls @@ -9,7 +9,7 @@ require "open-uri" require_relative "./trackrod" require_relative "../lib/propshaft" require_relative "../lib/propshaft/compilers" -require_relative "../lib/propshaft/compilers/css_asset_urls" +require_relative "../lib/propshaft/compiler/css_asset_urls" trackrod = Trackrod.new(Dir.mktmpdir) trackrod.build @@ -17,11 +17,11 @@ trackrod.build assets = ActiveSupport::OrderedOptions.new assets.paths = [ trackrod.root ] assets.prefix = "/assets" -assets.compilers = [ [ "text/css", Propshaft::Compilers::CssAssetUrls ] ] +assets.compilers = [ [ "text/css", Propshaft::Compiler::CssAssetUrls ] ] assembly = Propshaft::Assembly.new(assets) asset = assembly.load_path.find(trackrod.assets.css) -compiler = Propshaft::Compilers::CssAssetUrls.new(assembly) +compiler = Propshaft::Compiler::CssAssetUrls.new(assembly) Benchmark.ips do |x| x.config(time: 5, warmup: 2) diff --git a/benchmarks/dynamic_resolver b/benchmarks/dynamic_resolver index d00b1bc..8d5c2cb 100755 --- a/benchmarks/dynamic_resolver +++ b/benchmarks/dynamic_resolver @@ -15,7 +15,7 @@ trackrod.build assets = ActiveSupport::OrderedOptions.new assets.paths = [ trackrod.root ] assets.prefix = "/assets" -assets.compilers = [ [ "text/css", Propshaft::Compilers::CssAssetUrls ] ] +assets.compilers = [ [ "text/css", Propshaft::Compiler::CssAssetUrls ] ] assets.output_path ||= Pathname.new(Dir.mktmpdir) assembly = Propshaft::Assembly.new(assets) diff --git a/benchmarks/static_resolver b/benchmarks/static_resolver index ef9ec4a..73e1635 100755 --- a/benchmarks/static_resolver +++ b/benchmarks/static_resolver @@ -15,7 +15,7 @@ trackrod.build assets = ActiveSupport::OrderedOptions.new assets.paths = [ trackrod.root ] assets.prefix = "/assets" -assets.compilers = [ [ "text/css", Propshaft::Compilers::CssAssetUrls ] ] +assets.compilers = [ [ "text/css", Propshaft::Compiler::CssAssetUrls ] ] assets.output_path ||= Pathname.new(Dir.mktmpdir) assembly = Propshaft::Assembly.new(assets) diff --git a/lib/propshaft/assembly.rb b/lib/propshaft/assembly.rb index 123fec0..3fbb11c 100644 --- a/lib/propshaft/assembly.rb +++ b/lib/propshaft/assembly.rb @@ -4,8 +4,8 @@ require "propshaft/server" require "propshaft/processor" require "propshaft/compilers" -require "propshaft/compilers/css_asset_urls" -require "propshaft/compilers/source_mapping_urls" +require "propshaft/compiler/css_asset_urls" +require "propshaft/compiler/source_mapping_urls" class Propshaft::Assembly attr_reader :config diff --git a/lib/propshaft/compiler.rb b/lib/propshaft/compiler.rb new file mode 100644 index 0000000..b60dd65 --- /dev/null +++ b/lib/propshaft/compiler.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Base compiler from which other compilers can inherit +class Propshaft::Compiler + attr_reader :assembly + + def initialize(assembly) + @assembly = assembly + end + + # Override this in a specific compiler + def compile(logical_path, input) + raise NotImplementedError + end + + private + def url_prefix + @url_prefix ||= File.join(assembly.config.relative_url_root.to_s, assembly.config.prefix.to_s).chomp("/") + end +end diff --git a/lib/propshaft/compilers/css_asset_urls.rb b/lib/propshaft/compiler/css_asset_urls.rb similarity index 81% rename from lib/propshaft/compilers/css_asset_urls.rb rename to lib/propshaft/compiler/css_asset_urls.rb index 1132b5a..b263b7d 100644 --- a/lib/propshaft/compilers/css_asset_urls.rb +++ b/lib/propshaft/compiler/css_asset_urls.rb @@ -1,14 +1,10 @@ # frozen_string_literal: true -class Propshaft::Compilers::CssAssetUrls - attr_reader :assembly +require "propshaft/compiler" +class Propshaft::Compiler::CssAssetUrls < Propshaft::Compiler ASSET_URL_PATTERN = /url\(\s*["']?(?!(?:\#|%23|data|http|\/\/))([^"'\s?#)]+)([#?][^"')]+)?\s*["']?\)/ - def initialize(assembly) - @assembly = assembly - end - def compile(logical_path, input) input.gsub(ASSET_URL_PATTERN) { asset_url resolve_path(logical_path.dirname, $1), logical_path, $2, $1 } end @@ -26,7 +22,7 @@ def resolve_path(directory, filename) def asset_url(resolved_path, logical_path, fingerprint, pattern) if asset = assembly.load_path.find(resolved_path) - %[url("#{assembly.config.prefix}/#{asset.digested_path}#{fingerprint}")] + %[url("#{url_prefix}/#{asset.digested_path}#{fingerprint}")] else Propshaft.logger.warn "Unable to resolve '#{pattern}' for missing asset '#{resolved_path}' in #{logical_path}" %[url("#{pattern}")] diff --git a/lib/propshaft/compilers/source_mapping_urls.rb b/lib/propshaft/compiler/source_mapping_urls.rb similarity index 77% rename from lib/propshaft/compilers/source_mapping_urls.rb rename to lib/propshaft/compiler/source_mapping_urls.rb index 43c8ed8..de867d8 100644 --- a/lib/propshaft/compilers/source_mapping_urls.rb +++ b/lib/propshaft/compiler/source_mapping_urls.rb @@ -1,14 +1,10 @@ # frozen_string_literal: true -class Propshaft::Compilers::SourceMappingUrls - attr_reader :assembly +require "propshaft/compiler" +class Propshaft::Compiler::SourceMappingUrls < Propshaft::Compiler SOURCE_MAPPING_PATTERN = %r{^(//|/\*)# sourceMappingURL=(.+\.map)} - def initialize(assembly) - @assembly = assembly - end - def compile(logical_path, input) input.gsub(SOURCE_MAPPING_PATTERN) { source_mapping_url(asset_path($2, logical_path), $1) } end @@ -24,7 +20,7 @@ def asset_path(source_mapping_url, logical_path) def source_mapping_url(resolved_path, comment) if asset = assembly.load_path.find(resolved_path) - "#{comment}# sourceMappingURL=#{assembly.config.prefix}/#{asset.digested_path}" + "#{comment}# sourceMappingURL=#{url_prefix}/#{asset.digested_path}" else Propshaft.logger.warn "Removed sourceMappingURL comment for missing asset '#{resolved_path}' from #{resolved_path}" comment diff --git a/lib/propshaft/railtie.rb b/lib/propshaft/railtie.rb index 4fe2954..ad8d769 100644 --- a/lib/propshaft/railtie.rb +++ b/lib/propshaft/railtie.rb @@ -11,12 +11,13 @@ class Railtie < ::Rails::Railtie config.assets.prefix = "/assets" config.assets.quiet = false config.assets.compilers = [ - [ "text/css", Propshaft::Compilers::CssAssetUrls ], - [ "text/css", Propshaft::Compilers::SourceMappingUrls ], - [ "text/javascript", Propshaft::Compilers::SourceMappingUrls ] + [ "text/css", Propshaft::Compiler::CssAssetUrls ], + [ "text/css", Propshaft::Compiler::SourceMappingUrls ], + [ "text/javascript", Propshaft::Compiler::SourceMappingUrls ] ] config.assets.sweep_cache = Rails.env.development? config.assets.server = Rails.env.development? || Rails.env.test? + config.assets.relative_url_root = nil # Register propshaft initializer to copy the assets path in all the Rails Engines. # This makes possible for us to keep all `assets` config in this Railtie, but still @@ -30,6 +31,7 @@ class Railtie < ::Rails::Railtie end config.after_initialize do |app| + config.assets.relative_url_root ||= app.config.relative_url_root config.assets.output_path ||= Pathname.new(File.join(app.config.paths["public"].first, app.config.assets.prefix)) diff --git a/test/propshaft/compilers/css_asset_urls_test.rb b/test/propshaft/compiler/css_asset_urls_test.rb similarity index 90% rename from test/propshaft/compilers/css_asset_urls_test.rb rename to test/propshaft/compiler/css_asset_urls_test.rb index 6904530..1047a2d 100644 --- a/test/propshaft/compilers/css_asset_urls_test.rb +++ b/test/propshaft/compiler/css_asset_urls_test.rb @@ -4,15 +4,13 @@ require "propshaft/assembly" require "propshaft/compilers" -class Propshaft::Compilers::CssAssetUrlsTest < ActiveSupport::TestCase +class Propshaft::Compiler::CssAssetUrlsTest < ActiveSupport::TestCase setup do - @assembly = Propshaft::Assembly.new(ActiveSupport::OrderedOptions.new.tap { |config| + @options = ActiveSupport::OrderedOptions.new.tap { |config| config.paths = [ Pathname.new("#{__dir__}/../../fixtures/assets/vendor") ] config.output_path = Pathname.new("#{__dir__}/../../fixtures/output") config.prefix = "/assets" - }) - - @assembly.compilers.register "text/css", Propshaft::Compilers::CssAssetUrls + } end test "basic" do @@ -120,6 +118,13 @@ class Propshaft::Compilers::CssAssetUrlsTest < ActiveSupport::TestCase assert_match(/{ background: url\("file-not-found.jpg"\); }/, compiled) end + test "relative url root" do + @options.relative_url_root = "/url-root" + + compiled = compile_asset_with_content(%({ background: url(file.jpg); })) + assert_match(/{ background: url\("\/url-root\/assets\/foobar\/source\/file-[a-z0-9]{40}.jpg"\); }/, compiled) + end + private def compile_asset_with_content(content) root_path = Pathname.new("#{__dir__}/../../fixtures/assets/vendor") @@ -127,7 +132,9 @@ def compile_asset_with_content(content) asset = Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path) asset.stub :content, content do - @assembly.compilers.compile(asset) + assembly = Propshaft::Assembly.new(@options) + assembly.compilers.register "text/css", Propshaft::Compiler::CssAssetUrls + assembly.compilers.compile(asset) end end end diff --git a/test/propshaft/compiler/source_mapping_urls_test.rb b/test/propshaft/compiler/source_mapping_urls_test.rb new file mode 100644 index 0000000..f3dc551 --- /dev/null +++ b/test/propshaft/compiler/source_mapping_urls_test.rb @@ -0,0 +1,66 @@ +require "test_helper" +require "minitest/mock" +require "propshaft/asset" +require "propshaft/assembly" +require "propshaft/compilers" + +class Propshaft::Compiler::SourceMappingUrlsTest < ActiveSupport::TestCase + setup do + @options = ActiveSupport::OrderedOptions.new.tap { |config| + config.paths = [ Pathname.new("#{__dir__}/../../fixtures/assets/mapped") ] + config.output_path = Pathname.new("#{__dir__}/../../fixtures/output") + config.prefix = "/assets" + } + end + + test "matching source map" do + assert_match %r{//# sourceMappingURL=/assets/source.js-[a-z0-9]{40}\.map}, + compile_asset(find_asset("source.js", fixture_path: "mapped")) + assert_match %r{/\*# sourceMappingURL=/assets/source.css-[a-z0-9]{40}\.map}, + compile_asset(find_asset("source.css", fixture_path: "mapped")) + end + + test "matching nested source map" do + assert_match %r{//# sourceMappingURL=/assets/nested/another-source.js-[a-z0-9]{40}\.map}, + compile_asset(find_asset("nested/another-source.js", fixture_path: "mapped")) + end + + test "missing source map" do + assert_no_match %r{sourceMappingURL}, + compile_asset(find_asset("sourceless.js", fixture_path: "mapped")) + assert_no_match %r{sourceMappingURL}, + compile_asset(find_asset("sourceless.css", fixture_path: "mapped")) + end + + test "sourceMappingURL removal due to missing map does not damage /* ... */ comments" do + assert_match %r{\A#{Regexp.escape ".failure { color: red; }\n/* */\n"}\Z}, + compile_asset(find_asset("sourceless.css", fixture_path: "mapped")) + end + + test "sourceMappingURL outside of a comment should be left alone" do + assert_match %r{sourceMappingURL=sourceMappingURL-outside-comment.css.map}, + compile_asset(find_asset("sourceMappingURL-outside-comment.css", fixture_path: "mapped")) + end + + test "sourceMappingURL not at the beginning of the line should be left alone" do + assert_match %r{sourceMappingURL=sourceMappingURL-not-at-start.css.map}, + compile_asset(find_asset("sourceMappingURL-not-at-start.css", fixture_path: "mapped")) + end + + test "relative url root" do + @options.relative_url_root = "/url-root" + + assert_match %r{//# sourceMappingURL=/url-root/assets/source.js-[a-z0-9]{40}\.map}, + compile_asset(find_asset("source.js", fixture_path: "mapped")) + end + + private + def compile_asset(asset) + + assembly = Propshaft::Assembly.new(@options) + assembly.compilers.register "text/javascript", Propshaft::Compiler::SourceMappingUrls + assembly.compilers.register "text/css", Propshaft::Compiler::SourceMappingUrls + + assembly.compilers.compile(asset) + end +end diff --git a/test/propshaft/compilers/source_mapping_urls_test.rb b/test/propshaft/compilers/source_mapping_urls_test.rb deleted file mode 100644 index bbf34d0..0000000 --- a/test/propshaft/compilers/source_mapping_urls_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require "test_helper" -require "minitest/mock" -require "propshaft/asset" -require "propshaft/assembly" -require "propshaft/compilers" - -class Propshaft::Compilers::SourceMappingUrlsTest < ActiveSupport::TestCase - setup do - @assembly = Propshaft::Assembly.new(ActiveSupport::OrderedOptions.new.tap { |config| - config.paths = [ Pathname.new("#{__dir__}/../../fixtures/assets/mapped") ] - config.output_path = Pathname.new("#{__dir__}/../../fixtures/output") - config.prefix = "/assets" - }) - - @assembly.compilers.register "text/javascript", Propshaft::Compilers::SourceMappingUrls - @assembly.compilers.register "text/css", Propshaft::Compilers::SourceMappingUrls - end - - test "matching source map" do - assert_match %r{//# sourceMappingURL=/assets/source.js-[a-z0-9]{40}\.map}, - @assembly.compilers.compile(find_asset("source.js", fixture_path: "mapped")) - assert_match %r{/\*# sourceMappingURL=/assets/source.css-[a-z0-9]{40}\.map}, - @assembly.compilers.compile(find_asset("source.css", fixture_path: "mapped")) - end - - test "matching nested source map" do - assert_match %r{//# sourceMappingURL=/assets/nested/another-source.js-[a-z0-9]{40}\.map}, - @assembly.compilers.compile(find_asset("nested/another-source.js", fixture_path: "mapped")) - end - - test "missing source map" do - assert_no_match %r{sourceMappingURL}, - @assembly.compilers.compile(find_asset("sourceless.js", fixture_path: "mapped")) - assert_no_match %r{sourceMappingURL}, - @assembly.compilers.compile(find_asset("sourceless.css", fixture_path: "mapped")) - end - - test "sourceMappingURL removal due to missing map does not damage /* ... */ comments" do - assert_match %r{\A#{Regexp.escape ".failure { color: red; }\n/* */\n"}\Z}, - @assembly.compilers.compile(find_asset("sourceless.css", fixture_path: "mapped")) - end - - test "sourceMappingURL outside of a comment should be left alone" do - assert_match %r{sourceMappingURL=sourceMappingURL-outside-comment.css.map}, - @assembly.compilers.compile(find_asset("sourceMappingURL-outside-comment.css", fixture_path: "mapped")) - end - - test "sourceMappingURL not at the beginning of the line should be left alone" do - assert_match %r{sourceMappingURL=sourceMappingURL-not-at-start.css.map}, - @assembly.compilers.compile(find_asset("sourceMappingURL-not-at-start.css", fixture_path: "mapped")) - end -end diff --git a/test/propshaft/compilers_test.rb b/test/propshaft/compilers_test.rb index 8c81c67..83a4fb2 100644 --- a/test/propshaft/compilers_test.rb +++ b/test/propshaft/compilers_test.rb @@ -13,7 +13,7 @@ class Propshaft::CompilersTest < ActiveSupport::TestCase end test "replace asset-path function in css with digested url" do - @assembly.compilers.register "text/css", Propshaft::Compilers::CssAssetUrls + @assembly.compilers.register "text/css", Propshaft::Compiler::CssAssetUrls assert_match(/"\/assets\/archive-[a-z0-9]{40}.svg/, @assembly.compilers.compile(find_asset("another.css"))) end diff --git a/test/propshaft/server_test.rb b/test/propshaft/server_test.rb index eb544c8..5c8d7bd 100644 --- a/test/propshaft/server_test.rb +++ b/test/propshaft/server_test.rb @@ -11,7 +11,7 @@ class Propshaft::ServerTest < ActiveSupport::TestCase config.output_path = Pathname.new("#{__dir__}../fixtures/output") }) - @assembly.compilers.register "text/css", Propshaft::Compilers::CssAssetUrls + @assembly.compilers.register "text/css", Propshaft::Compiler::CssAssetUrls @server = Propshaft::Server.new(@assembly) end