Skip to content

Commit

Permalink
Add Propshaft::Compiler::JsAssetUrls (#207)
Browse files Browse the repository at this point in the history
* Add Propshaft::Compiler::JsAssetUrls

* Register JsAssetUrls compiler in railtie
  • Loading branch information
keiththomps authored Sep 30, 2024
1 parent 00b43ed commit e28a762
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/propshaft/assembly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require "propshaft/processor"
require "propshaft/compilers"
require "propshaft/compiler/css_asset_urls"
require "propshaft/compiler/js_asset_urls"
require "propshaft/compiler/source_mapping_urls"

class Propshaft::Assembly
Expand Down
45 changes: 45 additions & 0 deletions lib/propshaft/compiler/js_asset_urls.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require "propshaft/compiler"

class Propshaft::Compiler::JsAssetUrls < Propshaft::Compiler
ASSET_URL_PATTERN = %r{RAILS_ASSET_URL\(\s*["']?(?!(?:\#|%23|data|http|//))([^"'\s?#)]+)([#?][^"')]+)?\s*["']?\)}

def compile(asset, input)
input.gsub(ASSET_URL_PATTERN) { asset_url(resolve_path(asset.logical_path.dirname, $1), asset.logical_path, $2, $1) }
end

def referenced_by(asset, references: Set.new)
asset.content.scan(ASSET_URL_PATTERN).each do |referenced_asset_url, _|
referenced_asset = load_path.find(resolve_path(asset.logical_path.dirname, referenced_asset_url))

if referenced_asset && references.exclude?(referenced_asset)
references << referenced_asset
references.merge referenced_by(referenced_asset, references: references)
end
end

references
end

private
def resolve_path(directory, filename)
if filename.start_with?("../")
Pathname.new(directory + filename).relative_path_from("").to_s
elsif filename.start_with?("/")
filename.delete_prefix("/").to_s
else
(directory + filename.delete_prefix("./")).to_s
end
end

def asset_url(resolved_path, logical_path, fingerprint, pattern)
asset = load_path.find(resolved_path)
if asset
%["#{url_prefix}/#{asset.digested_path}#{fingerprint}"]
else
Propshaft.logger.warn("Unable to resolve '#{pattern}' for missing asset '#{resolved_path}' in #{logical_path}")
%["#{pattern}"]
end
end
end
3 changes: 2 additions & 1 deletion lib/propshaft/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ class Railtie < ::Rails::Railtie
config.assets.compilers = [
[ "text/css", Propshaft::Compiler::CssAssetUrls ],
[ "text/css", Propshaft::Compiler::SourceMappingUrls ],
[ "text/javascript", Propshaft::Compiler::SourceMappingUrls ]
[ "text/javascript", Propshaft::Compiler::JsAssetUrls ],
[ "text/javascript", Propshaft::Compiler::SourceMappingUrls ],
]
config.assets.sweep_cache = Rails.env.development?
config.assets.server = Rails.env.development? || Rails.env.test?
Expand Down
65 changes: 65 additions & 0 deletions test/propshaft/compiler/js_asset_urls_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require "test_helper"
require "minitest/mock"
require "propshaft/asset"
require "propshaft/assembly"
require "propshaft/compilers"

require "propshaft/compiler/js_asset_urls"

module Propshaft
class Compiler
class JsAssetUrlsTest < ActiveSupport::TestCase
setup do
@options = ActiveSupport::OrderedOptions.new.tap do |config|
config.paths = [Pathname.new("#{__dir__}/../../fixtures/assets/vendor")]
config.output_path = Pathname.new("#{__dir__}/../../fixtures/output")
config.prefix = "/assets"
end
end

test "the asset exists" do
js_content = <<~JS
export default class extends Controller {
init() {
this.img = RAILS_ASSET_URL("/foobar/source/file.svg");
}
}
JS

compiled = compile_asset_with_content(js_content)

assert_match(%r{this\.img = "/assets/foobar/source/file-[a-z0-9]{8}.svg"\;}, compiled)
end

test "the asset does not exist" do
js_content = <<~JS
export default class extends Controller {
init() {
this.img = RAILS_ASSET_URL("missing.svg");
}
}
JS

compiled = compile_asset_with_content(js_content)

assert_match(/this\.img = "missing.svg"\;/, compiled)
end

private

def compile_asset_with_content(content)
# This has one more set of .. than it would in the propshaft repo
root_path = Pathname.new("#{__dir__}/../../fixtures/assets/vendor")
logical_path = "foobar/source/test.js"

assembly = Propshaft::Assembly.new(@options)
assembly.compilers.register("text/javascript", Propshaft::Compiler::JsAssetUrls)

asset = Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path, load_path: assembly.load_path)
asset.stub(:content, content) do
assembly.compilers.compile(asset)
end
end
end
end
end

0 comments on commit e28a762

Please sign in to comment.