propshaft 0.1.6 → 0.2.2

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: 5a482b12e3d939c6286f02d4e6ca66a141b9124527503f64ceb262277366fca6
4
- data.tar.gz: a0d5ad9626de08fbab61cf450d95abab5ea77a1e5b90a162cbdc79ee1e424e07
3
+ metadata.gz: 4b51bb39ebb02eaa2eb0491eb6af28b1d6034d8b6dbed1da92161cc9d6ece2a7
4
+ data.tar.gz: 80426c2f10fe259bb7aec42c86f15581686680a4bb017117c677df2cc95150e1
5
5
  SHA512:
6
- metadata.gz: 3e60fdc05df54c428a898d460f2df85bdaf1d65a39c4a1025e8e70b758b10e1f39453ee7665c052c6d4b090257cac0b34accb896a25f7362dade2e0fc06b5276
7
- data.tar.gz: d501ae6047c543b522f791d23d8cd6856a0d3c9101c5cbf3fdb3d632a942fa995dc0707c6bdc872040efb8c63d859ace32be66a93b51788f57aa452ce0930850
6
+ metadata.gz: 35ca25b6bc30528fa870343793320c59265819b1154ac14d0f2fefa4288a66c686e63b87ddc264d3ab3a467df1be0399b8bb37a0e3d157f5c5240eb6756db7d8
7
+ data.tar.gz: 9d3abd851135146a1bacf62c73c755679d4bc76f920b89f969a4f4b34700c09a3334025a15531f85196a420a63c2d5f77f7f7bacdff91ab4c8537117982c0948
data/README.md CHANGED
@@ -24,6 +24,11 @@ These assets can be referenced through their logical path using the normal helpe
24
24
  Additionally, Propshaft ships with a CSS function called `asset-path("image.svg")` that'll be compiled into `url("/assets/image-f2e1ec14d6856e1958083094170ca6119c529a73.svg")` when doing `assets:precompile`. This function is applied to all `.css` files.
25
25
 
26
26
 
27
+ ## Bypassing the digest step
28
+
29
+ If you need to put multiple files that refer to each other through Propshaft, like a JavaScript file and its source map, you have to digest these files in advance to retain stable file names. Propshaft looks for the specific pattern of `-[digest].digested.js` as the postfix to any asset file as an indication that the file has already been digested.
30
+
31
+
27
32
  ## Migrating from Sprockets
28
33
 
29
34
  Propshaft does a lot less than Sprockets, by design, so it might well be a fair bit of work to migrate if it's even desirable. This is particularly true if you rely on Sprockets to provide any form of transpiling, like CoffeeScript or Sass, or if you rely on any gems that do. You'll need to either stop transpiling or use a Node-based transpiler, like those in `jsbundling-rails` and `cssbundling-rails`.
@@ -14,11 +14,11 @@ class Propshaft::Assembly
14
14
  end
15
15
 
16
16
  def load_path
17
- Propshaft::LoadPath.new(config.paths)
17
+ @load_path ||= Propshaft::LoadPath.new(config.paths)
18
18
  end
19
19
 
20
20
  def resolver
21
- if manifest_path.exist?
21
+ @resolver ||= if manifest_path.exist?
22
22
  Propshaft::Resolver::Static.new manifest_path: manifest_path, prefix: config.prefix
23
23
  else
24
24
  Propshaft::Resolver::Dynamic.new load_path: load_path, prefix: config.prefix
@@ -43,6 +43,12 @@ class Propshaft::Assembly
43
43
  end
44
44
  end
45
45
 
46
+ def reveal
47
+ load_path.assets.each do |asset|
48
+ Propshaft.logger.info asset.logical_path
49
+ end
50
+ end
51
+
46
52
  private
47
53
  def manifest_path
48
54
  config.output_path.join(Propshaft::Processor::MANIFEST_FILENAME)
@@ -21,14 +21,27 @@ class Propshaft::Asset
21
21
  end
22
22
 
23
23
  def digest
24
- Digest::SHA1.hexdigest(content)
24
+ @digest ||= Digest::SHA1.hexdigest(content)
25
25
  end
26
26
 
27
27
  def digested_path
28
- logical_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
28
+ if already_digested?
29
+ logical_path
30
+ else
31
+ logical_path.sub(/\.(\w+)$/) { |ext| "-#{digest}#{ext}" }
32
+ end
33
+ end
34
+
35
+ def fresh?(digest)
36
+ self.digest == digest || already_digested?
29
37
  end
30
38
 
31
39
  def ==(other_asset)
32
40
  logical_path.hash == other_asset.logical_path.hash
33
41
  end
42
+
43
+ private
44
+ def already_digested?
45
+ logical_path.to_s =~ /-([0-9a-f]{7,128})\.digested/
46
+ end
34
47
  end
@@ -1,13 +1,35 @@
1
+ # frozen_string_literal: true
2
+ require "propshaft/errors"
3
+
1
4
  class Propshaft::Compilers::CssAssetUrls
2
5
  attr_reader :assembly
3
6
 
7
+ ASSET_URL_PATTERN = /url\(\s*["']?(?!(?:\#|data|http))([^"'\s)]+)\s*["']?\)/
8
+
4
9
  def initialize(assembly)
5
10
  @assembly = assembly
6
11
  end
7
12
 
8
- def compile(input)
9
- input.gsub(/asset-path\(["']([^"')]+)["']\)/) do |match|
10
- %[url("#{assembly.config.prefix}/#{assembly.load_path.find($1).digested_path}")]
11
- end
13
+ def compile(logical_path, input)
14
+ input.gsub(ASSET_URL_PATTERN) { asset_url resolve_path(logical_path.dirname, $1) }
12
15
  end
16
+
17
+ private
18
+ def resolve_path(directory, filename)
19
+ if filename.start_with?("../")
20
+ Pathname.new(directory + filename).relative_path_from("").to_s
21
+ elsif filename.start_with?("/")
22
+ filename.delete_prefix("/").to_s
23
+ else
24
+ (directory + filename.delete_prefix("./")).to_s
25
+ end
26
+ end
27
+
28
+ def asset_url(resolved_path)
29
+ if asset = assembly.load_path.find(resolved_path)
30
+ %[url("#{assembly.config.prefix}/#{asset.digested_path}")]
31
+ else
32
+ raise Propshaft::MissingAssetError.new(resolved_path)
33
+ end
34
+ end
13
35
  end
@@ -23,7 +23,7 @@ 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(input)
26
+ input.replace compiler.new(assembly).compile(asset.logical_path, input)
27
27
  end
28
28
  end
29
29
  else
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Propshaft
4
+ # Generic base class for all Propshaft exceptions.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when LoadPath cannot find the requested asset
8
+ class MissingAssetError < Error
9
+ def initialize(path)
10
+ super
11
+ @path = path
12
+ end
13
+
14
+ def message
15
+ "The asset '#{@path}' was not found in the load path."
16
+ end
17
+ end
18
+ end
@@ -27,9 +27,23 @@ class Propshaft::LoadPath
27
27
  end
28
28
  end
29
29
 
30
+ # Returns a ActiveSupport::FileUpdateChecker object configured to clear the cache of the load_path
31
+ # when the directories passed during its initialization have changes. This is used in development
32
+ # and test to ensure the map caches are reset when javascript files are changed.
33
+ def cache_sweeper
34
+ @cache_sweeper ||= begin
35
+ exts_to_watch = Mime::EXTENSION_LOOKUP.map(&:first)
36
+ files_to_watch = Array(paths).collect { |dir| [ dir.to_s, exts_to_watch ] }.to_h
37
+
38
+ Rails.application.config.file_watcher.new([], files_to_watch) do
39
+ clear_cache
40
+ end
41
+ end
42
+ end
43
+
30
44
  private
31
45
  def assets_by_path
32
- Hash.new.tap do |mapped|
46
+ @cached_assets_by_path ||= Hash.new.tap do |mapped|
33
47
  paths.each do |path|
34
48
  all_files_from_tree(path).each do |file|
35
49
  logical_path = file.relative_path_from(path)
@@ -42,4 +56,8 @@ class Propshaft::LoadPath
42
56
  def all_files_from_tree(path)
43
57
  path.children.flat_map { |child| child.directory? ? all_files_from_tree(child) : child }
44
58
  end
59
+
60
+ def clear_cache
61
+ @cached_assets_by_path = nil
62
+ end
45
63
  end
@@ -16,6 +16,10 @@ class Propshaft::Processor
16
16
  compress_assets
17
17
  end
18
18
 
19
+ def clean
20
+ FileUtils.rm_r(output_path) if File.exist?(output_path)
21
+ end
22
+
19
23
  private
20
24
  def ensure_output_path_exists
21
25
  FileUtils.mkdir_p output_path
@@ -32,6 +36,7 @@ class Propshaft::Processor
32
36
  def output_assets
33
37
  load_path.assets.each do |asset|
34
38
  unless output_path.join(asset.digested_path).exist?
39
+ Propshaft.logger.info "Writing #{asset.digested_path}"
35
40
  FileUtils.mkdir_p output_path.join(asset.digested_path.parent)
36
41
  output_asset(asset)
37
42
  end
@@ -14,9 +14,10 @@ end
14
14
  module Propshaft
15
15
  class Railtie < ::Rails::Railtie
16
16
  config.assets = ActiveSupport::OrderedOptions.new
17
- config.assets.paths = []
18
- config.assets.prefix = "/assets"
19
- config.assets.compilers = [ [ "text/css", Propshaft::Compilers::CssAssetUrls ] ]
17
+ config.assets.paths = []
18
+ config.assets.prefix = "/assets"
19
+ config.assets.compilers = [ [ "text/css", Propshaft::Compilers::CssAssetUrls ] ]
20
+ config.assets.sweep_cache = Rails.env.development?
20
21
 
21
22
  config.after_initialize do |app|
22
23
  config.assets.output_path ||=
@@ -31,6 +32,16 @@ module Propshaft
31
32
  ActiveSupport.on_load(:action_view) do
32
33
  include Propshaft::Helper
33
34
  end
35
+
36
+ if config.assets.sweep_cache
37
+ ActiveSupport.on_load(:action_controller_base) do
38
+ before_action { Rails.application.assets.load_path.cache_sweeper.execute_if_updated }
39
+ end
40
+ end
41
+ end
42
+
43
+ initializer "propshaft.logger" do
44
+ Propshaft.logger = config.assets.logger || Rails.logger
34
45
  end
35
46
 
36
47
  rake_tasks do |app|
@@ -39,6 +50,16 @@ module Propshaft
39
50
  task precompile: :environment do
40
51
  Rails.application.assets.processor.process
41
52
  end
53
+
54
+ desc "Remove config.assets.output_path"
55
+ task clean: :environment do
56
+ Rails.application.assets.processor.clean
57
+ end
58
+
59
+ desc "Print all the assets available in config.assets.paths"
60
+ task reveal: :environment do
61
+ Rails.application.assets.reveal
62
+ end
42
63
  end
43
64
  end
44
65
 
@@ -9,6 +9,8 @@ module Propshaft::Resolver
9
9
  def resolve(logical_path)
10
10
  if asset = load_path.find(logical_path)
11
11
  File.join prefix, asset.digested_path
12
+ else
13
+ raise Propshaft::MissingAssetError.new(logical_path)
12
14
  end
13
15
  end
14
16
  end
@@ -9,6 +9,8 @@ module Propshaft::Resolver
9
9
  def resolve(logical_path)
10
10
  if asset_path = parsed_manifest[logical_path]
11
11
  File.join prefix, asset_path
12
+ else
13
+ raise Propshaft::MissingAssetError.new(logical_path)
12
14
  end
13
15
  end
14
16
 
@@ -8,16 +8,17 @@ class Propshaft::Server
8
8
  def call(env)
9
9
  path, digest = extract_path_and_digest(env)
10
10
 
11
- if (asset = @assembly.load_path.find(path)) && asset.digest == digest
11
+ if (asset = @assembly.load_path.find(path)) && asset.fresh?(digest)
12
12
  compiled_content = @assembly.compilers.compile(asset)
13
13
 
14
14
  [
15
15
  200,
16
16
  {
17
- "Content-Length" => compiled_content.length.to_s,
18
- "Content-Type" => asset.content_type,
19
- "ETag" => asset.digest,
20
- "Cache-Control" => "public, max-age=31536000, immutable"
17
+ "Content-Length" => compiled_content.length.to_s,
18
+ "Content-Type" => asset.content_type.to_s,
19
+ "Accept-Encoding" => "Vary",
20
+ "ETag" => asset.digest,
21
+ "Cache-Control" => "public, max-age=31536000, immutable"
21
22
  },
22
23
  [ compiled_content ]
23
24
  ]
@@ -29,9 +30,9 @@ class Propshaft::Server
29
30
  private
30
31
  def extract_path_and_digest(env)
31
32
  full_path = Rack::Utils.unescape(env["PATH_INFO"].to_s.sub(/^\//, ""))
32
- digest = full_path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
33
+ digest = full_path[/-([0-9a-f]{7,128})\.(?!digested)[^.]+\z/, 1]
33
34
  path = digest ? full_path.sub("-#{digest}", "") : full_path
34
-
35
+
35
36
  [ path, digest ]
36
37
  end
37
38
  end
@@ -1,3 +1,3 @@
1
1
  module Propshaft
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.2"
3
3
  end
data/lib/propshaft.rb CHANGED
@@ -1,4 +1,9 @@
1
+ require "active_support"
2
+ require "active_support/core_ext/module/attribute_accessors"
3
+ require "logger"
4
+
1
5
  module Propshaft
6
+ mattr_accessor :logger, default: Logger.new(STDOUT)
2
7
  end
3
8
 
4
9
  require "propshaft/assembly"
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.1.6
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-30 00:00:00.000000000 Z
11
+ date: 2021-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,7 +24,7 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 7.0.0.alpha2
27
- description:
27
+ description:
28
28
  email: dhh@hey.com
29
29
  executables: []
30
30
  extensions: []
@@ -38,6 +38,7 @@ files:
38
38
  - lib/propshaft/asset.rb
39
39
  - lib/propshaft/compilers.rb
40
40
  - lib/propshaft/compilers/css_asset_urls.rb
41
+ - lib/propshaft/errors.rb
41
42
  - lib/propshaft/helper.rb
42
43
  - lib/propshaft/load_path.rb
43
44
  - lib/propshaft/processor.rb
@@ -50,7 +51,7 @@ homepage: https://github.com/rails/propshaft
50
51
  licenses:
51
52
  - MIT
52
53
  metadata: {}
53
- post_install_message:
54
+ post_install_message:
54
55
  rdoc_options: []
55
56
  require_paths:
56
57
  - lib
@@ -65,8 +66,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
66
  - !ruby/object:Gem::Version
66
67
  version: '0'
67
68
  requirements: []
68
- rubygems_version: 3.1.4
69
- signing_key:
69
+ rubygems_version: 3.2.22
70
+ signing_key:
70
71
  specification_version: 4
71
72
  summary: Deliver assets for Rails.
72
73
  test_files: []