Skip to content

Commit 59406ab

Browse files
authored
Digests of files that can have dependencies on other files in the load path need to reflect those dependencies (#188)
* Digests of files that can have dependencies on other files in the load path need to reflect those dependencies
1 parent ca1dfbd commit 59406ab

File tree

18 files changed

+124
-36
lines changed

18 files changed

+124
-36
lines changed

benchmarks/css_asset_urls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ compiler = Propshaft::Compiler::CssAssetUrls.new(assembly)
2525

2626
Benchmark.ips do |x|
2727
x.config(time: 5, warmup: 2)
28-
x.report("compile") { compiler.compile(asset.logical_path, asset.content) }
28+
x.report("compile") { compiler.compile(asset, asset.content) }
2929
end

lib/propshaft/assembly.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def initialize(config)
1515
end
1616

1717
def load_path
18-
@load_path ||= Propshaft::LoadPath.new(config.paths, version: config.version)
18+
@load_path ||= Propshaft::LoadPath.new(config.paths, compilers: compilers, version: config.version)
1919
end
2020

2121
def resolver

lib/propshaft/asset.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
require "action_dispatch/http/mime_type"
33

44
class Propshaft::Asset
5-
attr_reader :path, :logical_path, :version
5+
attr_reader :path, :logical_path, :load_path
66

7-
def initialize(path, logical_path:, version: nil)
8-
@path, @logical_path, @version = path, Pathname.new(logical_path), version
7+
def initialize(path, logical_path:, load_path:)
8+
@path, @logical_path, @load_path = path, Pathname.new(logical_path), load_path
99
end
1010

1111
def content
@@ -21,7 +21,7 @@ def length
2121
end
2222

2323
def digest
24-
@digest ||= Digest::SHA1.hexdigest("#{content}#{version}").first(8)
24+
@digest ||= Digest::SHA1.hexdigest("#{content_with_compile_references}#{load_path.version}").first(8)
2525
end
2626

2727
def digested_path
@@ -41,6 +41,10 @@ def ==(other_asset)
4141
end
4242

4343
private
44+
def content_with_compile_references
45+
content + load_path.find_referenced_by(self).collect(&:content).join
46+
end
47+
4448
def already_digested?
4549
logical_path.to_s =~ /-([0-9a-zA-Z_-]{7,128})\.digested/
4650
end

lib/propshaft/compiler.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,23 @@
33
# Base compiler from which other compilers can inherit
44
class Propshaft::Compiler
55
attr_reader :assembly
6+
delegate :config, :load_path, to: :assembly
67

78
def initialize(assembly)
89
@assembly = assembly
910
end
1011

1112
# Override this in a specific compiler
12-
def compile(logical_path, input)
13+
def compile(asset, input)
1314
raise NotImplementedError
1415
end
1516

17+
def referenced_by(asset)
18+
Set.new
19+
end
20+
1621
private
1722
def url_prefix
18-
@url_prefix ||= File.join(assembly.config.relative_url_root.to_s, assembly.config.prefix.to_s).chomp("/")
23+
@url_prefix ||= File.join(config.relative_url_root.to_s, config.prefix.to_s).chomp("/")
1924
end
2025
end

lib/propshaft/compiler/css_asset_urls.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,21 @@
55
class Propshaft::Compiler::CssAssetUrls < Propshaft::Compiler
66
ASSET_URL_PATTERN = /url\(\s*["']?(?!(?:\#|%23|data|http|\/\/))([^"'\s?#)]+)([#?][^"')]+)?\s*["']?\)/
77

8-
def compile(logical_path, input)
9-
input.gsub(ASSET_URL_PATTERN) { asset_url resolve_path(logical_path.dirname, $1), logical_path, $2, $1 }
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
1023
end
1124

1225
private
@@ -21,7 +34,7 @@ def resolve_path(directory, filename)
2134
end
2235

2336
def asset_url(resolved_path, logical_path, fingerprint, pattern)
24-
if asset = assembly.load_path.find(resolved_path)
37+
if asset = load_path.find(resolved_path)
2538
%[url("#{url_prefix}/#{asset.digested_path}#{fingerprint}")]
2639
else
2740
Propshaft.logger.warn "Unable to resolve '#{pattern}' for missing asset '#{resolved_path}' in #{logical_path}"

lib/propshaft/compiler/source_mapping_urls.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
class Propshaft::Compiler::SourceMappingUrls < Propshaft::Compiler
66
SOURCE_MAPPING_PATTERN = %r{(//|/\*)# sourceMappingURL=(.+\.map)(\s*?\*\/)?\s*?\Z}
77

8-
def compile(logical_path, input)
9-
input.gsub(SOURCE_MAPPING_PATTERN) { source_mapping_url(logical_path, asset_path($2, logical_path), $1, $3) }
8+
def compile(asset, input)
9+
input.gsub(SOURCE_MAPPING_PATTERN) { source_mapping_url(asset.logical_path, asset_path($2, asset.logical_path), $1, $3) }
1010
end
1111

1212
private
@@ -21,7 +21,7 @@ def asset_path(source_mapping_url, logical_path)
2121
end
2222

2323
def source_mapping_url(logical_path, resolved_path, comment_start, comment_end)
24-
if asset = assembly.load_path.find(resolved_path)
24+
if asset = load_path.find(resolved_path)
2525
"#{comment_start}# sourceMappingURL=#{url_prefix}/#{asset.digested_path}#{comment_end}"
2626
else
2727
Propshaft.logger.warn "Removed sourceMappingURL comment for missing asset '#{resolved_path}' from #{logical_path}"

lib/propshaft/compilers.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,21 @@ def compile(asset)
2323
if relevant_registrations = registrations[asset.content_type.to_s]
2424
asset.content.dup.tap do |input|
2525
relevant_registrations.each do |compiler|
26-
input.replace compiler.new(assembly).compile(asset.logical_path, input)
26+
input.replace compiler.new(assembly).compile(asset, input)
2727
end
2828
end
2929
else
3030
asset.content
3131
end
3232
end
33+
34+
def referenced_by(asset)
35+
Set.new.tap do |references|
36+
if relevant_registrations = registrations[asset.content_type.to_s]
37+
relevant_registrations.each do |compiler|
38+
references.merge compiler.new(assembly).referenced_by(asset)
39+
end
40+
end
41+
end
42+
end
3343
end

lib/propshaft/load_path.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
require "propshaft/asset"
22

33
class Propshaft::LoadPath
4-
attr_reader :paths, :version
4+
attr_reader :paths, :compilers, :version
55

6-
def initialize(paths = [], version: nil)
7-
@paths = dedup(paths)
8-
@version = version
6+
def initialize(paths = [], compilers:, version: nil)
7+
@paths, @compilers, @version = dedup(paths), compilers, version
98
end
109

1110
def find(asset_name)
1211
assets_by_path[asset_name]
1312
end
1413

14+
def find_referenced_by(asset)
15+
compilers.referenced_by(asset).delete(self)
16+
end
17+
1518
def assets(content_types: nil)
1619
if content_types
1720
assets_by_path.values.select { |asset| asset.content_type.in?(content_types) }
@@ -48,7 +51,7 @@ def assets_by_path
4851
paths.each do |path|
4952
without_dotfiles(all_files_from_tree(path)).each do |file|
5053
logical_path = file.relative_path_from(path)
51-
mapped[logical_path.to_s] ||= Propshaft::Asset.new(file, logical_path: logical_path, version: version)
54+
mapped[logical_path.to_s] ||= Propshaft::Asset.new(file, logical_path: logical_path, load_path: self)
5255
end if path.exist?
5356
end
5457
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import url('b.css')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@import url('c.css')
2+
@import url('missing.css')

0 commit comments

Comments
 (0)