Skip to content

Commit f840ca1

Browse files
committed
Add Propshaft::Compiler::JsAssetUrls
1 parent 48df362 commit f840ca1

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
require "propshaft/compiler"
4+
5+
class Propshaft::Compiler::JsAssetUrls < Propshaft::Compiler
6+
ASSET_URL_PATTERN = %r{rails_asset_url\(\s*["']?(?!(?:\#|%23|data|http|//))([^"'\s?#)]+)([#?][^"')]+)?\s*["']?\)}
7+
8+
def compile(asset, input)
9+
input.gsub(ASSET_URL_PATTERN) { asset_url(resolve_path(asset.logical_path.dirname, ::Regexp.last_match(1)), asset.logical_path, ::Regexp.last_match(2), ::Regexp.last_match(1)) }
10+
end
11+
12+
def referenced_by(asset, references: Set.new)
13+
asset.content.scan(ASSET_URL_PATTERN).each do |referenced_asset_url, _|
14+
referenced_asset = load_path.find(resolve_path(asset.logical_path.dirname, referenced_asset_url))
15+
16+
if referenced_asset && references.exclude?(referenced_asset)
17+
references << referenced_asset
18+
references.merge referenced_by(referenced_asset, references: references)
19+
end
20+
end
21+
22+
references
23+
end
24+
25+
private
26+
27+
def resolve_path(directory, filename)
28+
if filename.start_with?("../")
29+
Pathname.new(directory + filename).relative_path_from("").to_s
30+
elsif filename.start_with?("/")
31+
filename.delete_prefix("/").to_s
32+
else
33+
(directory + filename.delete_prefix("./")).to_s
34+
end
35+
end
36+
37+
def asset_url(resolved_path, logical_path, fingerprint, pattern)
38+
asset = load_path.find(resolved_path)
39+
if asset
40+
%["#{url_prefix}/#{asset.digested_path}#{fingerprint}"]
41+
else
42+
Propshaft.logger.warn("Unable to resolve '#{pattern}' for missing asset '#{resolved_path}' in #{logical_path}")
43+
%["#{pattern}"]
44+
end
45+
end
46+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
require "test_helper"
2+
require "minitest/mock"
3+
require "propshaft/asset"
4+
require "propshaft/assembly"
5+
require "propshaft/compilers"
6+
7+
require "propshaft/compiler/js_asset_urls"
8+
9+
module Propshaft
10+
class Compiler
11+
class JsAssetUrlsTest < ActiveSupport::TestCase
12+
setup do
13+
@options = ActiveSupport::OrderedOptions.new.tap do |config|
14+
config.paths = [Pathname.new("#{__dir__}/../../fixtures/assets/vendor")]
15+
config.output_path = Pathname.new("#{__dir__}/../../fixtures/output")
16+
config.prefix = "/assets"
17+
end
18+
end
19+
20+
test "the asset exists" do
21+
js_content = <<~JS
22+
export default class extends Controller {
23+
init() {
24+
this.img = rails_asset_url("/foobar/source/file.svg");
25+
}
26+
}
27+
JS
28+
29+
compiled = compile_asset_with_content(js_content)
30+
31+
assert_match(%r{this\.img = "/assets/foobar/source/file-[a-z0-9]{8}.svg"\;}, compiled)
32+
end
33+
34+
test "the asset does not exist" do
35+
js_content = <<~JS
36+
export default class extends Controller {
37+
init() {
38+
this.img = rails_asset_url("missing.svg");
39+
}
40+
}
41+
JS
42+
43+
compiled = compile_asset_with_content(js_content)
44+
45+
assert_match(/this\.img = "missing.svg"\;/, compiled)
46+
end
47+
48+
private
49+
50+
def compile_asset_with_content(content)
51+
# This has one more set of .. than it would in the propshaft repo
52+
root_path = Pathname.new("#{__dir__}/../../fixtures/assets/vendor")
53+
logical_path = "foobar/source/test.js"
54+
55+
assembly = Propshaft::Assembly.new(@options)
56+
assembly.compilers.register("text/javascript", Propshaft::Compiler::JsAssetUrls)
57+
58+
asset = Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path, load_path: assembly.load_path)
59+
asset.stub(:content, content) do
60+
assembly.compilers.compile(asset)
61+
end
62+
end
63+
end
64+
end
65+
end

0 commit comments

Comments
 (0)