Skip to content

Commit

Permalink
Digests of files that can have dependencies on other files in the loa…
Browse files Browse the repository at this point in the history
…d path need to reflect those dependencies
  • Loading branch information
dhh committed May 17, 2024
1 parent 6d6969d commit 0b02b0d
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 27 deletions.
2 changes: 1 addition & 1 deletion lib/propshaft/assembly.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize(config)
end

def load_path
@load_path ||= Propshaft::LoadPath.new(config.paths, version: config.version)
@load_path ||= Propshaft::LoadPath.new(config.paths, compilers: compilers, version: config.version)
end

def resolver
Expand Down
12 changes: 8 additions & 4 deletions lib/propshaft/asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
require "action_dispatch/http/mime_type"

class Propshaft::Asset
attr_reader :path, :logical_path, :version
attr_reader :path, :logical_path, :load_path

def initialize(path, logical_path:, version: nil)
@path, @logical_path, @version = path, Pathname.new(logical_path), version
def initialize(path, logical_path:, load_path:)
@path, @logical_path, @load_path = path, Pathname.new(logical_path), load_path
end

def content
Expand All @@ -21,7 +21,7 @@ def length
end

def digest
@digest ||= Digest::SHA1.hexdigest("#{content}#{version}").first(8)
@digest ||= Digest::SHA1.hexdigest("#{content_with_compile_dependencies}#{load_path.version}").first(8)
end

def digested_path
Expand All @@ -44,4 +44,8 @@ def ==(other_asset)
def already_digested?
logical_path.to_s =~ /-([0-9a-zA-Z_-]{7,128})\.digested/
end

def content_with_compile_dependencies
content + load_path.find_compiler_dependencies_to(self).collect(&:content).join
end
end
6 changes: 5 additions & 1 deletion lib/propshaft/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ def initialize(assembly)
end

# Override this in a specific compiler
def compile(logical_path, input)
def compile(asset)
raise NotImplementedError
end

def find_dependencies(asset)
Set.new
end

private
def url_prefix
@url_prefix ||= File.join(assembly.config.relative_url_root.to_s, assembly.config.prefix.to_s).chomp("/")
Expand Down
17 changes: 15 additions & 2 deletions lib/propshaft/compiler/css_asset_urls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@
class Propshaft::Compiler::CssAssetUrls < Propshaft::Compiler
ASSET_URL_PATTERN = /url\(\s*["']?(?!(?:\#|%23|data|http|\/\/))([^"'\s?#)]+)([#?][^"')]+)?\s*["']?\)/

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

def find_dependencies(asset)
Set.new.tap do |dependencies|
asset.content.scan(ASSET_URL_PATTERN).each do |dependent_asset_url, _|
dependent_asset = assembly.load_path.find(resolve_path(asset.logical_path.dirname, dependent_asset_url))

if dependencies.exclude?(dependent_asset)
dependencies << dependent_asset
dependencies.merge(find_dependencies(dependent_asset))
end
end
end
end

private
Expand Down
4 changes: 2 additions & 2 deletions lib/propshaft/compiler/source_mapping_urls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
class Propshaft::Compiler::SourceMappingUrls < Propshaft::Compiler
SOURCE_MAPPING_PATTERN = %r{(//|/\*)# sourceMappingURL=(.+\.map)(\s*?\*\/)?\s*?\Z}

def compile(logical_path, input)
input.gsub(SOURCE_MAPPING_PATTERN) { source_mapping_url(logical_path, asset_path($2, logical_path), $1, $3) }
def compile(asset)
asset.content.gsub(SOURCE_MAPPING_PATTERN) { source_mapping_url(asset.logical_path, asset_path($2, asset.logical_path), $1, $3) }
end

private
Expand Down
12 changes: 11 additions & 1 deletion lib/propshaft/compilers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,21 @@ def compile(asset)
if relevant_registrations = registrations[asset.content_type.to_s]
asset.content.dup.tap do |input|
relevant_registrations.each do |compiler|
input.replace compiler.new(assembly).compile(asset.logical_path, input)
input.replace compiler.new(assembly).compile(asset)
end
end
else
asset.content
end
end

def find_dependencies(asset)
Set.new.tap do |dependencies|
if relevant_registrations = registrations[asset.content_type.to_s]
relevant_registrations.each do |compiler|
dependencies.merge compiler.new(assembly).find_dependencies(asset)
end
end
end
end
end
13 changes: 8 additions & 5 deletions lib/propshaft/load_path.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
require "propshaft/asset"

class Propshaft::LoadPath
attr_reader :paths, :version
attr_reader :paths, :compilers, :version

def initialize(paths = [], version: nil)
@paths = dedup(paths)
@version = version
def initialize(paths = [], compilers: nil, version: nil)
@paths, @compilers, @version = dedup(paths), compilers, version
end

def find(asset_name)
assets_by_path[asset_name]
end

def find_compiler_dependencies_to(asset)
compilers&.find_dependencies(asset) || Set.new
end

def assets(content_types: nil)
if content_types
assets_by_path.values.select { |asset| asset.content_type.in?(content_types) }
Expand Down Expand Up @@ -48,7 +51,7 @@ def assets_by_path
paths.each do |path|
without_dotfiles(all_files_from_tree(path)).each do |file|
logical_path = file.relative_path_from(path)
mapped[logical_path.to_s] ||= Propshaft::Asset.new(file, logical_path: logical_path, version: version)
mapped[logical_path.to_s] ||= Propshaft::Asset.new(file, logical_path: logical_path, load_path: self)
end if path.exist?
end
end
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/assets/first_path/dependent/a.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import url('b.css')
1 change: 1 addition & 0 deletions test/fixtures/assets/first_path/dependent/b.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import url('c.css')
3 changes: 3 additions & 0 deletions test/fixtures/assets/first_path/dependent/c.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
p {
color: red;
}
43 changes: 42 additions & 1 deletion test/propshaft/asset_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,51 @@ class Propshaft::AssetTest < ActiveSupport::TestCase
assert_equal asset.digest.object_id, asset.digest.object_id
end

test "digest depends on first level of compiler dependency" do
open_asset_with_reset("dependent/b.css") do |asset_file|
digest_before_dependency_change = find_asset("dependent/a.css").digest

asset_file.write "changes!"
asset_file.flush

digest_after_dependency_change = find_asset("dependent/a.css").digest

assert_not_equal digest_before_dependency_change, digest_after_dependency_change
end
end

test "digest depends on second level of compiler dependency" do
open_asset_with_reset("dependent/c.css") do |asset_file|
digest_before_dependency_change = find_asset("dependent/a.css").digest

asset_file.write "changes!"
asset_file.flush

digest_after_dependency_change = find_asset("dependent/a.css").digest

assert_not_equal digest_before_dependency_change, digest_after_dependency_change
end
end

private
def find_asset(logical_path)
root_path = Pathname.new("#{__dir__}/../fixtures/assets/first_path")
path = root_path.join(logical_path)
Propshaft::Asset.new(path, logical_path: logical_path)

assembly = Propshaft::Assembly.new(ActiveSupport::OrderedOptions.new.tap { |config|
config.paths = [ root_path ]
config.compilers = [[ "text/css", Propshaft::Compiler::CssAssetUrls ]]
})

Propshaft::Asset.new(path, logical_path: logical_path, load_path: assembly.load_path)
end

def open_asset_with_reset(logical_path)
dependency_path = Pathname.new("#{__dir__}/../fixtures/assets/first_path/#{logical_path}")
existing_dependency_content = File.read(dependency_path)

File.open(dependency_path, "a") { |f| yield f }
ensure
File.write(dependency_path, existing_dependency_content)
end
end
7 changes: 4 additions & 3 deletions test/propshaft/compiler/css_asset_urls_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ def compile_asset_with_content(content)
root_path = Pathname.new("#{__dir__}/../../fixtures/assets/vendor")
logical_path = "foobar/source/test.css"

asset = Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path)
assembly = Propshaft::Assembly.new(@options)
assembly.compilers.register "text/css", Propshaft::Compiler::CssAssetUrls

asset = Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path, load_path: assembly.load_path)
asset.stub :content, content do
assembly = Propshaft::Assembly.new(@options)
assembly.compilers.register "text/css", Propshaft::Compiler::CssAssetUrls
assembly.compilers.compile(asset)
end
end
Expand Down
2 changes: 1 addition & 1 deletion test/propshaft/compilers_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ class Propshaft::CompilersTest < ActiveSupport::TestCase
private
def find_asset(logical_path)
root_path = Pathname.new("#{__dir__}/../fixtures/assets/first_path")
Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path)
Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path, load_path: Propshaft::LoadPath.new([ root_path ]))
end
end
6 changes: 2 additions & 4 deletions test/propshaft/load_path_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ class Propshaft::LoadPathTest < ActiveSupport::TestCase

private
def find_asset(logical_path)
Propshaft::Asset.new(
Pathname.new("#{__dir__}/../fixtures/assets/first_path/#{logical_path}"),
logical_path: Pathname.new(logical_path)
)
root_path = Pathname.new("#{__dir__}/../fixtures/assets/first_path")
Propshaft::Asset.new(root_path.join(logical_path), logical_path: logical_path, load_path: Propshaft::LoadPath.new([ root_path ]))
end
end
2 changes: 1 addition & 1 deletion test/propshaft/output_path_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Propshaft::OutputPathTest < ActiveSupport::TestCase

private
def output_asset(filename, content, created_at: Time.now)
asset = Propshaft::Asset.new(nil, logical_path: filename)
asset = Propshaft::Asset.new(nil, logical_path: filename, load_path: Propshaft::LoadPath.new)
asset.stub :content, content do
output_path = @output_path.path.join(asset.digested_path)
`touch -mt #{created_at.strftime('%y%m%d%H%M')} #{output_path}`
Expand Down
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ class ActiveSupport::TestCase
def find_asset(logical_path, fixture_path:)
root_path = Pathname.new("#{__dir__}/fixtures/assets/#{fixture_path}")
path = root_path.join(logical_path)
Propshaft::Asset.new(path, logical_path: logical_path)
Propshaft::Asset.new(path, logical_path: logical_path, load_path: Propshaft::LoadPath.new([ root_path ]))
end
end

0 comments on commit 0b02b0d

Please sign in to comment.