Skip to content

Commit e28a762

Browse files
authored
Add Propshaft::Compiler::JsAssetUrls (#207)
* Add Propshaft::Compiler::JsAssetUrls * Register JsAssetUrls compiler in railtie
1 parent 00b43ed commit e28a762

File tree

4 files changed

+113
-1
lines changed

4 files changed

+113
-1
lines changed

lib/propshaft/assembly.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require "propshaft/processor"
66
require "propshaft/compilers"
77
require "propshaft/compiler/css_asset_urls"
8+
require "propshaft/compiler/js_asset_urls"
89
require "propshaft/compiler/source_mapping_urls"
910

1011
class Propshaft::Assembly
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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, $1), asset.logical_path, $2, $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+
def resolve_path(directory, filename)
27+
if filename.start_with?("../")
28+
Pathname.new(directory + filename).relative_path_from("").to_s
29+
elsif filename.start_with?("/")
30+
filename.delete_prefix("/").to_s
31+
else
32+
(directory + filename.delete_prefix("./")).to_s
33+
end
34+
end
35+
36+
def asset_url(resolved_path, logical_path, fingerprint, pattern)
37+
asset = load_path.find(resolved_path)
38+
if asset
39+
%["#{url_prefix}/#{asset.digested_path}#{fingerprint}"]
40+
else
41+
Propshaft.logger.warn("Unable to resolve '#{pattern}' for missing asset '#{resolved_path}' in #{logical_path}")
42+
%["#{pattern}"]
43+
end
44+
end
45+
end

lib/propshaft/railtie.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class Railtie < ::Rails::Railtie
1313
config.assets.compilers = [
1414
[ "text/css", Propshaft::Compiler::CssAssetUrls ],
1515
[ "text/css", Propshaft::Compiler::SourceMappingUrls ],
16-
[ "text/javascript", Propshaft::Compiler::SourceMappingUrls ]
16+
[ "text/javascript", Propshaft::Compiler::JsAssetUrls ],
17+
[ "text/javascript", Propshaft::Compiler::SourceMappingUrls ],
1718
]
1819
config.assets.sweep_cache = Rails.env.development?
1920
config.assets.server = Rails.env.development? || Rails.env.test?
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)