propshaft 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56736cd8d13ffa9511e5cd47d39e3d3849f66484f0ba489686b75aa5a5c57936
4
- data.tar.gz: 7adfc7103ca6694027ad577b7cd14bf6972227f02bdf0bd0659d508371afdfad
3
+ metadata.gz: 0cec588585c21b06e765dbf70ac6f0d3a4b9ad2826c8a11120553a8db0931559
4
+ data.tar.gz: dde9f2d2a7095731ea93585e237421b73adc4acc71218ef574e566468796d1eb
5
5
  SHA512:
6
- metadata.gz: 67c4c686072bc75784519f8043225b9bb462a19a74a2617ec1628a25cb701bad1aabe05a873126038718b6bd18ba9d51f47cdd0daa89418186996832a0f5707c
7
- data.tar.gz: f476c9aff5b7975df5815095bdf1917eeb748ee1c9ebab0f058aa670863597a6d4794c77b3bd379eafb355c83b9fa8008b366796ad52188d7804410f93c8ec33
6
+ metadata.gz: c372a207074f656261b8be407d224780678fd68e6caf645323c898ca06cb04f4cc0e1b2ab2ac2d59179162bb3a6c1e137297635aef8c49d1a0e4b3d279e609c6
7
+ data.tar.gz: 80c3963e4963ae52b19793b8de066a173b1227a5b6ea1d1ff4420f88b1939a09f2a476fd55e52a4a18fe688c53bc0862756700723557a108d052441ade893259
data/README.md CHANGED
@@ -49,7 +49,7 @@ But for greenfield apps using the default import-map approach, Propshaft can als
49
49
 
50
50
  ## Will Propshaft replace Sprockets as the Rails default?
51
51
 
52
- Most likely, but Sprockets need to be supported as well for a long time to come. Plenty of apps and gems were built on Sprocket features, and they won't be migrating soon. Still working out the compatibility story. This is very much beta software at the moment.
52
+ Most likely, but Sprockets needs to be supported as well for a long time to come. Plenty of apps and gems were built on Sprocket features, and they won't be migrating soon. Still working out the compatibility story. This is very much beta software at the moment.
53
53
 
54
54
 
55
55
  ## License
@@ -15,7 +15,7 @@ class Propshaft::Assembly
15
15
  end
16
16
 
17
17
  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)
19
19
  end
20
20
 
21
21
  def resolver
@@ -2,10 +2,10 @@ require "digest/sha1"
2
2
  require "action_dispatch/http/mime_type"
3
3
 
4
4
  class Propshaft::Asset
5
- attr_reader :path, :logical_path, :version
5
+ attr_reader :path, :logical_path, :load_path
6
6
 
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
9
9
  end
10
10
 
11
11
  def content
@@ -21,14 +21,14 @@ class Propshaft::Asset
21
21
  end
22
22
 
23
23
  def digest
24
- @digest ||= Digest::SHA1.hexdigest("#{content}#{version}")
24
+ @digest ||= Digest::SHA1.hexdigest("#{content_with_compile_references}#{load_path.version}").first(8)
25
25
  end
26
26
 
27
27
  def digested_path
28
28
  if already_digested?
29
29
  logical_path
30
30
  else
31
- logical_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
31
+ logical_path.sub(/\.(\w+(\.map)?)$/) { |ext| "-#{digest}#{ext}" }
32
32
  end
33
33
  end
34
34
 
@@ -41,7 +41,11 @@ class Propshaft::Asset
41
41
  end
42
42
 
43
43
  private
44
+ def content_with_compile_references
45
+ content + load_path.find_referenced_by(self).collect(&:content).join
46
+ end
47
+
44
48
  def already_digested?
45
- logical_path.to_s =~ /-([0-9a-zA-Z]{7,128})\.digested/
49
+ logical_path.to_s =~ /-([0-9a-zA-Z_-]{7,128})\.digested/
46
50
  end
47
51
  end
@@ -5,8 +5,21 @@ require "propshaft/compiler"
5
5
  class Propshaft::Compiler::CssAssetUrls < Propshaft::Compiler
6
6
  ASSET_URL_PATTERN = /url\(\s*["']?(?!(?:\#|%23|data|http|\/\/))([^"'\s?#)]+)([#?][^"')]+)?\s*["']?\)/
7
7
 
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
10
23
  end
11
24
 
12
25
  private
@@ -21,7 +34,7 @@ class Propshaft::Compiler::CssAssetUrls < Propshaft::Compiler
21
34
  end
22
35
 
23
36
  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)
25
38
  %[url("#{url_prefix}/#{asset.digested_path}#{fingerprint}")]
26
39
  else
27
40
  Propshaft.logger.warn "Unable to resolve '#{pattern}' for missing asset '#{resolved_path}' in #{logical_path}"
@@ -5,12 +5,14 @@ require "propshaft/compiler"
5
5
  class Propshaft::Compiler::SourceMappingUrls < Propshaft::Compiler
6
6
  SOURCE_MAPPING_PATTERN = %r{(//|/\*)# sourceMappingURL=(.+\.map)(\s*?\*\/)?\s*?\Z}
7
7
 
8
- def compile(logical_path, input)
9
- input.gsub(SOURCE_MAPPING_PATTERN) { source_mapping_url(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) }
10
10
  end
11
11
 
12
12
  private
13
13
  def asset_path(source_mapping_url, logical_path)
14
+ source_mapping_url.gsub!(/^(.+\/)?#{url_prefix}\//, "")
15
+
14
16
  if logical_path.dirname.to_s == "."
15
17
  source_mapping_url
16
18
  else
@@ -18,11 +20,11 @@ class Propshaft::Compiler::SourceMappingUrls < Propshaft::Compiler
18
20
  end
19
21
  end
20
22
 
21
- def source_mapping_url(resolved_path, comment_start, comment_end)
22
- if asset = assembly.load_path.find(resolved_path)
23
+ def source_mapping_url(logical_path, resolved_path, comment_start, comment_end)
24
+ if asset = load_path.find(resolved_path)
23
25
  "#{comment_start}# sourceMappingURL=#{url_prefix}/#{asset.digested_path}#{comment_end}"
24
26
  else
25
- Propshaft.logger.warn "Removed sourceMappingURL comment for missing asset '#{resolved_path}' from #{resolved_path}"
27
+ Propshaft.logger.warn "Removed sourceMappingURL comment for missing asset '#{resolved_path}' from #{logical_path}"
26
28
  "#{comment_start}#{comment_end}"
27
29
  end
28
30
  end
@@ -3,18 +3,23 @@
3
3
  # Base compiler from which other compilers can inherit
4
4
  class Propshaft::Compiler
5
5
  attr_reader :assembly
6
+ delegate :config, :load_path, to: :assembly
6
7
 
7
8
  def initialize(assembly)
8
9
  @assembly = assembly
9
10
  end
10
11
 
11
12
  # Override this in a specific compiler
12
- def compile(logical_path, input)
13
+ def compile(asset, input)
13
14
  raise NotImplementedError
14
15
  end
15
16
 
17
+ def referenced_by(asset)
18
+ Set.new
19
+ end
20
+
16
21
  private
17
22
  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("/")
19
24
  end
20
25
  end
@@ -23,11 +23,21 @@ class Propshaft::Compilers
23
23
  if relevant_registrations = registrations[asset.content_type.to_s]
24
24
  asset.content.dup.tap do |input|
25
25
  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)
27
27
  end
28
28
  end
29
29
  else
30
30
  asset.content
31
31
  end
32
32
  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
33
43
  end
@@ -4,10 +4,14 @@ module Propshaft
4
4
  Rails.application.assets.resolver.resolve(path) || raise(MissingAssetError.new(path))
5
5
  end
6
6
 
7
- # Add an option to call `stylesheet_link_tag` with `:all` to include every css file found on the load path.
8
- def stylesheet_link_tag(*sources)
9
- if sources.first == :all
10
- super *all_stylesheets_paths
7
+ # Add an option to call `stylesheet_link_tag` with `:all` to include every css file found on the load path
8
+ # or `:app` to include css files found in `Rails.root("app/assets/**/*.css")`, which will exclude lib/ and plugins.
9
+ def stylesheet_link_tag(*sources, **options)
10
+ case sources.first
11
+ when :all
12
+ super(*all_stylesheets_paths , **options)
13
+ when :app
14
+ super(*app_stylesheets_paths , **options)
11
15
  else
12
16
  super
13
17
  end
@@ -15,11 +19,12 @@ module Propshaft
15
19
 
16
20
  # Returns a sorted and unique array of logical paths for all stylesheets in the load path.
17
21
  def all_stylesheets_paths
18
- Rails.application.assets.load_path
19
- .assets(content_types: [ Mime::EXTENSION_LOOKUP["css"] ])
20
- .collect { |css| css.logical_path.to_s }
21
- .sort
22
- .uniq
22
+ Rails.application.assets.load_path.asset_paths_by_type("css")
23
+ end
24
+
25
+ # Returns a sorted and unique array of logical paths for all stylesheets in app/assets/**/*.css.
26
+ def app_stylesheets_paths
27
+ Rails.application.assets.load_path.asset_paths_by_glob("#{Rails.root.join("app/assets")}/**/*.css")
23
28
  end
24
29
  end
25
30
  end
@@ -1,23 +1,32 @@
1
1
  require "propshaft/asset"
2
2
 
3
3
  class Propshaft::LoadPath
4
- attr_reader :paths, :version
4
+ attr_reader :paths, :compilers, :version
5
5
 
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
9
8
  end
10
9
 
11
10
  def find(asset_name)
12
11
  assets_by_path[asset_name]
13
12
  end
14
13
 
15
- def assets(content_types: nil)
16
- if content_types
17
- assets_by_path.values.select { |asset| asset.content_type.in?(content_types) }
18
- else
19
- assets_by_path.values
20
- end
14
+ def find_referenced_by(asset)
15
+ compilers.referenced_by(asset).delete(self)
16
+ end
17
+
18
+ def assets
19
+ assets_by_path.values
20
+ end
21
+
22
+ def asset_paths_by_type(content_type)
23
+ (@cached_asset_paths_by_type ||= Hash.new)[content_type] ||=
24
+ extract_logical_paths_from(assets.select { |a| a.content_type == Mime::EXTENSION_LOOKUP[content_type] })
25
+ end
26
+
27
+ def asset_paths_by_glob(glob)
28
+ (@cached_asset_paths_by_glob ||= Hash.new)[glob] ||=
29
+ extract_logical_paths_from(assets.select { |a| a.path.fnmatch?(glob) })
21
30
  end
22
31
 
23
32
  def manifest
@@ -35,9 +44,13 @@ class Propshaft::LoadPath
35
44
  @cache_sweeper ||= begin
36
45
  exts_to_watch = Mime::EXTENSION_LOOKUP.map(&:first)
37
46
  files_to_watch = Array(paths).collect { |dir| [ dir.to_s, exts_to_watch ] }.to_h
47
+ mutex = Mutex.new
38
48
 
39
49
  Rails.application.config.file_watcher.new([], files_to_watch) do
40
- clear_cache
50
+ mutex.synchronize do
51
+ clear_cache
52
+ seed_cache
53
+ end
41
54
  end
42
55
  end
43
56
  end
@@ -48,7 +61,7 @@ class Propshaft::LoadPath
48
61
  paths.each do |path|
49
62
  without_dotfiles(all_files_from_tree(path)).each do |file|
50
63
  logical_path = file.relative_path_from(path)
51
- mapped[logical_path.to_s] ||= Propshaft::Asset.new(file, logical_path: logical_path, version: version)
64
+ mapped[logical_path.to_s] ||= Propshaft::Asset.new(file, logical_path: logical_path, load_path: self)
52
65
  end if path.exist?
53
66
  end
54
67
  end
@@ -58,12 +71,22 @@ class Propshaft::LoadPath
58
71
  path.children.flat_map { |child| child.directory? ? all_files_from_tree(child) : child }
59
72
  end
60
73
 
74
+ def extract_logical_paths_from(assets)
75
+ assets.collect { |asset| asset.logical_path.to_s }.sort
76
+ end
77
+
61
78
  def without_dotfiles(files)
62
79
  files.reject { |file| file.basename.to_s.starts_with?(".") }
63
80
  end
64
81
 
65
82
  def clear_cache
66
83
  @cached_assets_by_path = nil
84
+ @cached_asset_paths_by_type = nil
85
+ @cached_asset_paths_by_glob = nil
86
+ end
87
+
88
+ def seed_cache
89
+ assets_by_path
67
90
  end
68
91
 
69
92
  def dedup(paths)
@@ -20,8 +20,8 @@ class Propshaft::Processor
20
20
  FileUtils.rm_r(output_path) if File.exist?(output_path)
21
21
  end
22
22
 
23
- def clean
24
- Propshaft::OutputPath.new(output_path, load_path.manifest).clean(2, 1.hour)
23
+ def clean(count)
24
+ Propshaft::OutputPath.new(output_path, load_path.manifest).clean(count, 1.hour)
25
25
  end
26
26
 
27
27
  private
@@ -14,8 +14,9 @@ namespace :assets do
14
14
  end
15
15
 
16
16
  desc "Removes old files in config.assets.output_path"
17
- task clean: :environment do
18
- Rails.application.assets.processor.clean
17
+ task :clean, [:count] => [:environment] do |_, args|
18
+ count = args.fetch(:count, 2)
19
+ Rails.application.assets.processor.clean(count.to_i)
19
20
  end
20
21
 
21
22
  desc "Print all the assets available in config.assets.paths"
@@ -1,4 +1,5 @@
1
1
  require "rack/utils"
2
+ require "rack/version"
2
3
 
3
4
  class Propshaft::Server
4
5
  def initialize(assembly)
@@ -1,3 +1,3 @@
1
1
  module Propshaft
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: propshaft
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-25 00:00:00.000000000 Z
11
+ date: 2024-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -114,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  - !ruby/object:Gem::Version
115
115
  version: '0'
116
116
  requirements: []
117
- rubygems_version: 3.4.20
117
+ rubygems_version: 3.5.10
118
118
  signing_key:
119
119
  specification_version: 4
120
120
  summary: Deliver assets for Rails.