sprockets 3.0.3 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +101 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +531 -276
  5. data/bin/sprockets +12 -7
  6. data/lib/rake/sprocketstask.rb +9 -4
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +41 -28
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +1 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  12. data/lib/sprockets/autoload/eco.rb +1 -0
  13. data/lib/sprockets/autoload/ejs.rb +1 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +1 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +1 -0
  18. data/lib/sprockets/autoload/yui.rb +1 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +5 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +61 -13
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +32 -7
  26. data/lib/sprockets/cache/memory_store.rb +28 -10
  27. data/lib/sprockets/cache/null_store.rb +8 -0
  28. data/lib/sprockets/cache.rb +43 -6
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +6 -11
  31. data/lib/sprockets/coffee_script_processor.rb +20 -6
  32. data/lib/sprockets/compressing.rb +62 -2
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +10 -9
  36. data/lib/sprockets/digest_utils.rb +103 -62
  37. data/lib/sprockets/directive_processor.rb +64 -36
  38. data/lib/sprockets/eco_processor.rb +4 -3
  39. data/lib/sprockets/ejs_processor.rb +4 -3
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +34 -21
  43. data/lib/sprockets/errors.rb +1 -0
  44. data/lib/sprockets/exporters/base.rb +71 -0
  45. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  46. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  47. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  48. data/lib/sprockets/exporting.rb +73 -0
  49. data/lib/sprockets/file_reader.rb +1 -0
  50. data/lib/sprockets/http_utils.rb +25 -7
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +11 -10
  53. data/lib/sprockets/loader.rb +244 -62
  54. data/lib/sprockets/manifest.rb +100 -46
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -42
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +3 -11
  59. data/lib/sprockets/path_digest_utils.rb +2 -1
  60. data/lib/sprockets/path_utils.rb +107 -22
  61. data/lib/sprockets/paths.rb +1 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +32 -52
  64. data/lib/sprockets/processor_utils.rb +38 -39
  65. data/lib/sprockets/resolve.rb +177 -97
  66. data/lib/sprockets/sass_cache_store.rb +1 -0
  67. data/lib/sprockets/sass_compressor.rb +21 -17
  68. data/lib/sprockets/sass_functions.rb +1 -0
  69. data/lib/sprockets/sass_importer.rb +1 -0
  70. data/lib/sprockets/sass_processor.rb +46 -18
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +77 -44
  74. data/lib/sprockets/source_map_processor.rb +66 -0
  75. data/lib/sprockets/source_map_utils.rb +483 -0
  76. data/lib/sprockets/transformers.rb +63 -35
  77. data/lib/sprockets/uglifier_compressor.rb +23 -20
  78. data/lib/sprockets/unloaded_asset.rb +139 -0
  79. data/lib/sprockets/uri_tar.rb +99 -0
  80. data/lib/sprockets/uri_utils.rb +14 -14
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +63 -71
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +5 -14
  85. data/lib/sprockets.rb +105 -33
  86. metadata +157 -27
  87. data/lib/sprockets/coffee_script_template.rb +0 -6
  88. data/lib/sprockets/eco_template.rb +0 -6
  89. data/lib/sprockets/ejs_template.rb +0 -6
  90. data/lib/sprockets/engines.rb +0 -81
  91. data/lib/sprockets/erb_template.rb +0 -6
  92. data/lib/sprockets/legacy.rb +0 -314
  93. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  94. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  95. data/lib/sprockets/sass_template.rb +0 -7
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Sprockets
2
3
  # Internal: HTTP URI utilities. Many adapted from Rack::Utils. Mixed into
3
4
  # Environment.
@@ -13,9 +14,9 @@ module Sprockets
13
14
  # Returns true if the given value is a mime match for the given mime match
14
15
  # specification, false otherwise.
15
16
  def match_mime_type?(value, matcher)
16
- v1, v2 = value.split('/', 2)
17
- m1, m2 = matcher.split('/', 2)
18
- (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
17
+ v1, v2 = value.split('/'.freeze, 2)
18
+ m1, m2 = matcher.split('/'.freeze, 2)
19
+ (m1 == '*'.freeze || v1 == m1) && (m2.nil? || m2 == '*'.freeze || m2 == v2)
19
20
  end
20
21
 
21
22
  # Public: Return values from Hash where the key matches the mime type.
@@ -36,7 +37,22 @@ module Sprockets
36
37
 
37
38
  # Internal: Parse Accept header quality values.
38
39
  #
39
- # Adapted from Rack::Utils#q_values.
40
+ # values - String e.g. "application/javascript"
41
+ #
42
+ # Adapted from Rack::Utils#q_values. Quality values are
43
+ # described in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
44
+ #
45
+ # parse_q_values("application/javascript")
46
+ # # => [["application/javascript", 1.0]]
47
+ #
48
+ # parse_q_values("*/*")
49
+ # # => [["*/*", 1.0]]
50
+ #
51
+ # parse_q_values("text/plain; q=0.5, image/*")
52
+ # # => [["text/plain", 0.5], ["image/*", 1.0]]
53
+ #
54
+ # parse_q_values("application/javascript, text/css")
55
+ # # => [["application/javascript", 1.0], ["text/css", 1.0]]
40
56
  #
41
57
  # Returns an Array of [String, Float].
42
58
  def parse_q_values(values)
@@ -70,14 +86,16 @@ module Sprockets
70
86
  raise TypeError, "unknown q_values type: #{q_values.class}"
71
87
  end
72
88
 
89
+ i = 0
73
90
  q_values.each do |accepted, quality|
74
91
  if match = available.find { |option| matcher.call(option, accepted) }
75
- matches << [match, quality]
92
+ i += 1
93
+ matches << [-quality, i, match]
76
94
  end
77
95
  end
78
96
 
79
- matches.sort_by! { |match, quality| -quality }
80
- matches.map! { |match, quality| match }
97
+ matches.sort!
98
+ matches.map! { |_, _, match| match }
81
99
  matches
82
100
  end
83
101
 
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/digest_utils'
4
+
5
+ module Sprockets
6
+ class JSMincCompressor
7
+ VERSION = '1'
8
+
9
+ def self.instance
10
+ @instance ||= new
11
+ end
12
+
13
+ def self.call(input)
14
+ instance.call(input)
15
+ end
16
+
17
+ def self.cache_key
18
+ instance.cache_key
19
+ end
20
+
21
+ attr_reader :cache_key
22
+
23
+ def initialize(options = {})
24
+ @compressor_class = Autoload::JSMinC
25
+ @cache_key = "#{self.class.name}:#{Autoload::JSMinC::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
26
+ end
27
+
28
+ def call(input)
29
+ @compressor_class.minify(input[:data])
30
+ end
31
+ end
32
+ end
@@ -1,21 +1,22 @@
1
+ # frozen_string_literal: true
1
2
  module Sprockets
2
- # Public: .jst engine.
3
+ # Public: JST transformer.
3
4
  #
4
5
  # Exports server side compiled templates to an object.
5
6
  #
6
- # Name your template "users/show.jst.ejs", "users/new.jst.eco", etc.
7
+ # Name your template "users/show.ejs", "users/new.eco", etc.
7
8
  #
8
9
  # To accept the default options
9
10
  #
10
- # environment.register_engine '.jst',
11
- # JstProcessor,
12
- # mime_type: 'application/javascript'
11
+ # environment.register_transformer
12
+ # 'application/javascript+function',
13
+ # 'application/javascript', JstProcessor
13
14
  #
14
15
  # Change the default namespace.
15
16
  #
16
- # environment.register_engine '.jst',
17
- # JstProcessor.new(namespace: 'App.templates'),
18
- # mime_type: 'application/javascript'
17
+ # environment.register_transformer
18
+ # 'application/javascript+function',
19
+ # 'application/javascript', JstProcessor.new(namespace: 'App.templates')
19
20
  #
20
21
  class JstProcessor
21
22
  def self.default_namespace
@@ -33,8 +34,8 @@ module Sprockets
33
34
  instance.call(input)
34
35
  end
35
36
 
36
- def initialize(options = {})
37
- @namespace = options[:namespace] || self.class.default_namespace
37
+ def initialize(namespace: self.class.default_namespace)
38
+ @namespace = namespace
38
39
  end
39
40
 
40
41
  def call(input)
@@ -1,6 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/asset'
2
3
  require 'sprockets/digest_utils'
3
- require 'sprockets/engines'
4
4
  require 'sprockets/errors'
5
5
  require 'sprockets/file_reader'
6
6
  require 'sprockets/mime'
@@ -10,66 +10,161 @@ require 'sprockets/processor_utils'
10
10
  require 'sprockets/resolve'
11
11
  require 'sprockets/transformers'
12
12
  require 'sprockets/uri_utils'
13
+ require 'sprockets/unloaded_asset'
13
14
 
14
15
  module Sprockets
16
+
15
17
  # The loader phase takes a asset URI location and returns a constructed Asset
16
18
  # object.
17
19
  module Loader
18
20
  include DigestUtils, PathUtils, ProcessorUtils, URIUtils
19
- include Engines, Mime, Processing, Resolve, Transformers
21
+ include Mime, Processing, Resolve, Transformers
22
+
20
23
 
21
- # Public: Load Asset by AssetURI.
24
+ # Public: Load Asset by Asset URI.
22
25
  #
23
- # uri - AssetURI
26
+ # uri - A String containing complete URI to a file including schema
27
+ # and full path such as:
28
+ # "file:///Path/app/assets/js/app.js?type=application/javascript"
24
29
  #
25
30
  # Returns Asset.
26
31
  def load(uri)
27
- filename, params = parse_asset_uri(uri)
28
- if params.key?(:id)
29
- unless asset = cache.get("asset-uri:#{VERSION}:#{uri}", true)
30
- id = params.delete(:id)
31
- uri_without_id = build_asset_uri(filename, params)
32
- asset = load_asset_by_uri(uri_without_id, filename, params)
32
+ unloaded = UnloadedAsset.new(uri, self)
33
+ if unloaded.params.key?(:id)
34
+ unless asset = asset_from_cache(unloaded.asset_key)
35
+ id = unloaded.params.delete(:id)
36
+ uri_without_id = build_asset_uri(unloaded.filename, unloaded.params)
37
+ asset = load_from_unloaded(UnloadedAsset.new(uri_without_id, self))
33
38
  if asset[:id] != id
34
39
  @logger.warn "Sprockets load error: Tried to find #{uri}, but latest was id #{asset[:id]}"
35
40
  end
36
41
  end
37
42
  else
38
- asset = fetch_asset_from_dependency_cache(uri, filename) do |paths|
43
+ asset = fetch_asset_from_dependency_cache(unloaded) do |paths|
44
+ # When asset is previously generated, its "dependencies" are stored in the cache.
45
+ # The presence of `paths` indicates dependencies were stored.
46
+ # We can check to see if the dependencies have not changed by "resolving" them and
47
+ # generating a digest key from the resolved entries. If this digest key has not
48
+ # changed, the asset will be pulled from cache.
49
+ #
50
+ # If this `paths` is present but the cache returns nothing then `fetch_asset_from_dependency_cache`
51
+ # will confusingly be called again with `paths` set to nil where the asset will be
52
+ # loaded from disk.
39
53
  if paths
40
- digest = digest(resolve_dependencies(paths))
41
- if id_uri = cache.get("asset-uri-digest:#{VERSION}:#{uri}:#{digest}", true)
42
- cache.get("asset-uri:#{VERSION}:#{id_uri}", true)
54
+ digest = DigestUtils.digest(resolve_dependencies(paths))
55
+ if uri_from_cache = cache.get(unloaded.digest_key(digest), true)
56
+ asset_from_cache(UnloadedAsset.new(uri_from_cache, self).asset_key)
43
57
  end
44
58
  else
45
- load_asset_by_uri(uri, filename, params)
59
+ load_from_unloaded(unloaded)
46
60
  end
47
61
  end
48
62
  end
49
- Asset.new(self, asset)
63
+ Asset.new(asset)
50
64
  end
51
65
 
52
66
  private
53
- def load_asset_by_uri(uri, filename, params)
54
- unless file?(filename)
55
- raise FileNotFound, "could not find file: #{filename}"
67
+ def compress_key_from_hash(hash, key)
68
+ return unless hash.key?(key)
69
+ value = hash[key].dup
70
+ return if !value
71
+
72
+ if block_given?
73
+ value.map! do |x|
74
+ if yield x
75
+ compress_from_root(x)
76
+ else
77
+ x
78
+ end
79
+ end
80
+ else
81
+ value.map! { |x| compress_from_root(x) }
82
+ end
83
+ hash[key] = value
84
+ end
85
+
86
+
87
+ def expand_key_from_hash(hash, key)
88
+ return unless hash.key?(key)
89
+ value = hash[key].dup
90
+ return if !value
91
+ if block_given?
92
+ value.map! do |x|
93
+ if yield x
94
+ expand_from_root(x)
95
+ else
96
+ x
97
+ end
98
+ end
99
+ else
100
+ value.map! { |x| expand_from_root(x) }
101
+ end
102
+ hash[key] = value
103
+ end
104
+
105
+ # Internal: Load asset hash from cache
106
+ #
107
+ # key - A String containing lookup information for an asset
108
+ #
109
+ # This method converts all "compressed" paths to absolute paths.
110
+ # Returns a hash of values representing an asset
111
+ def asset_from_cache(key)
112
+ asset = cache.get(key, true)
113
+ if asset
114
+ asset[:uri] = expand_from_root(asset[:uri])
115
+ asset[:load_path] = expand_from_root(asset[:load_path])
116
+ asset[:filename] = expand_from_root(asset[:filename])
117
+ expand_key_from_hash(asset[:metadata], :included)
118
+ expand_key_from_hash(asset[:metadata], :links)
119
+ expand_key_from_hash(asset[:metadata], :stubbed)
120
+ expand_key_from_hash(asset[:metadata], :required)
121
+ expand_key_from_hash(asset[:metadata], :to_load)
122
+ expand_key_from_hash(asset[:metadata], :to_link)
123
+ expand_key_from_hash(asset[:metadata], :dependencies) { |uri| uri.start_with?("file-digest://") }
124
+
125
+ asset[:metadata].each_key do |k|
126
+ next unless k.match?(/_dependencies\z/) # rubocop:disable Performance/EndWith
127
+ expand_key_from_hash(asset[:metadata], k)
128
+ end
129
+ end
130
+ asset
131
+ end
132
+
133
+ # Internal: Loads an asset and saves it to cache
134
+ #
135
+ # unloaded - An UnloadedAsset
136
+ #
137
+ # This method is only called when the given unloaded asset could not be
138
+ # successfully pulled from cache.
139
+ def load_from_unloaded(unloaded)
140
+ unless file?(unloaded.filename)
141
+ raise FileNotFound, "could not find file: #{unloaded.filename}"
56
142
  end
57
143
 
58
- load_path, logical_path = paths_split(config[:paths], filename)
144
+ path_to_split =
145
+ if index_alias = unloaded.params[:index_alias]
146
+ expand_from_root index_alias
147
+ else
148
+ unloaded.filename
149
+ end
150
+
151
+ load_path, logical_path = paths_split(config[:paths], path_to_split)
59
152
 
60
153
  unless load_path
61
- raise FileOutsidePaths, "#{filename} is no longer under a load path: #{self.paths.join(', ')}"
154
+ target = path_to_split
155
+ target += " (index alias of #{unloaded.filename})" if unloaded.params[:index_alias]
156
+ raise FileOutsidePaths, "#{target} is no longer under a load path: #{self.paths.join(', ')}"
62
157
  end
63
158
 
64
- logical_path, file_type, engine_extnames, _ = parse_path_extnames(logical_path)
65
- logical_path = normalize_logical_path(logical_path)
159
+ extname, file_type = match_path_extname(logical_path, mime_exts)
160
+ logical_path = logical_path.chomp(extname)
66
161
  name = logical_path
67
162
 
68
- if pipeline = params[:pipeline]
163
+ if pipeline = unloaded.params[:pipeline]
69
164
  logical_path += ".#{pipeline}"
70
165
  end
71
166
 
72
- if type = params[:type]
167
+ if type = unloaded.params[:type]
73
168
  logical_path += config[:mime_types][type][:extensions].first
74
169
  end
75
170
 
@@ -77,9 +172,9 @@ module Sprockets
77
172
  raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
78
173
  end
79
174
 
80
- processors = processors_for(type, file_type, engine_extnames, pipeline)
175
+ processors = processors_for(type, file_type, pipeline)
81
176
 
82
- processors_dep_uri = build_processors_uri(type, file_type, engine_extnames, pipeline)
177
+ processors_dep_uri = build_processors_uri(type, file_type, pipeline)
83
178
  dependencies = config[:dependencies] + [processors_dep_uri]
84
179
 
85
180
  # Read into memory and process if theres a processor pipeline
@@ -87,75 +182,162 @@ module Sprockets
87
182
  result = call_processors(processors, {
88
183
  environment: self,
89
184
  cache: self.cache,
90
- uri: uri,
91
- filename: filename,
185
+ uri: unloaded.uri,
186
+ filename: unloaded.filename,
92
187
  load_path: load_path,
93
188
  name: name,
94
189
  content_type: type,
95
- metadata: { dependencies: dependencies }
190
+ metadata: {
191
+ dependencies: dependencies
192
+ }
96
193
  })
97
194
  validate_processor_result!(result)
98
195
  source = result.delete(:data)
99
- metadata = result.merge!(
100
- charset: source.encoding.name.downcase,
101
- digest: digest(source),
102
- length: source.bytesize
103
- )
196
+ metadata = result
197
+ metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset)
198
+ metadata[:digest] = digest(source)
199
+ metadata[:length] = source.bytesize
200
+ metadata[:environment_version] = version
104
201
  else
105
- dependencies << build_file_digest_uri(filename)
202
+ dependencies << build_file_digest_uri(unloaded.filename)
106
203
  metadata = {
107
- digest: file_digest(filename),
108
- length: self.stat(filename).size,
109
- dependencies: dependencies
204
+ digest: file_digest(unloaded.filename),
205
+ length: self.stat(unloaded.filename).size,
206
+ dependencies: dependencies,
207
+ environment_version: version,
110
208
  }
111
209
  end
112
210
 
113
211
  asset = {
114
- uri: uri,
212
+ uri: unloaded.uri,
115
213
  load_path: load_path,
116
- filename: filename,
214
+ filename: unloaded.filename,
117
215
  name: name,
118
216
  logical_path: logical_path,
119
217
  content_type: type,
120
218
  source: source,
121
219
  metadata: metadata,
122
- integrity: integrity_uri(metadata[:digest], type),
123
- dependencies_digest: digest(resolve_dependencies(metadata[:dependencies]))
220
+ dependencies_digest: DigestUtils.digest(resolve_dependencies(metadata[:dependencies]))
124
221
  }
125
222
 
126
- asset[:id] = pack_hexdigest(digest(asset))
127
- asset[:uri] = build_asset_uri(filename, params.merge(id: asset[:id]))
223
+ asset[:id] = hexdigest(asset)
224
+ asset[:uri] = build_asset_uri(unloaded.filename, unloaded.params.merge(id: asset[:id]))
128
225
 
129
- # Deprecated: Avoid tracking Asset mtime
130
- asset[:mtime] = metadata[:dependencies].map { |u|
131
- if u.start_with?("file-digest:")
132
- s = self.stat(parse_file_digest_uri(u))
133
- s ? s.mtime.to_i : nil
134
- else
135
- nil
226
+ store_asset(asset, unloaded)
227
+ asset
228
+ end
229
+
230
+ # Internal: Save a given asset to the cache
231
+ #
232
+ # asset - A hash containing values of loaded asset
233
+ # unloaded - The UnloadedAsset used to lookup the `asset`
234
+ #
235
+ # This method converts all absolute paths to "compressed" paths
236
+ # which are relative if they're in the root.
237
+ def store_asset(asset, unloaded)
238
+ # Save the asset in the cache under the new URI
239
+ cached_asset = asset.dup
240
+ cached_asset[:uri] = compress_from_root(asset[:uri])
241
+ cached_asset[:filename] = compress_from_root(asset[:filename])
242
+ cached_asset[:load_path] = compress_from_root(asset[:load_path])
243
+
244
+ if cached_asset[:metadata]
245
+ # Deep dup to avoid modifying `asset`
246
+ cached_asset[:metadata] = cached_asset[:metadata].dup
247
+ compress_key_from_hash(cached_asset[:metadata], :included)
248
+ compress_key_from_hash(cached_asset[:metadata], :links)
249
+ compress_key_from_hash(cached_asset[:metadata], :stubbed)
250
+ compress_key_from_hash(cached_asset[:metadata], :required)
251
+ compress_key_from_hash(cached_asset[:metadata], :to_load)
252
+ compress_key_from_hash(cached_asset[:metadata], :to_link)
253
+ compress_key_from_hash(cached_asset[:metadata], :dependencies) { |uri| uri.start_with?("file-digest://") }
254
+
255
+ cached_asset[:metadata].each do |key, value|
256
+ next unless key.match?(/_dependencies\z/) # rubocop:disable Performance/EndWith
257
+ compress_key_from_hash(cached_asset[:metadata], key)
136
258
  end
137
- }.compact.max
138
- asset[:mtime] ||= self.stat(filename).mtime.to_i
259
+ end
139
260
 
140
- cache.set("asset-uri:#{VERSION}:#{asset[:uri]}", asset, true)
141
- cache.set("asset-uri-digest:#{VERSION}:#{uri}:#{asset[:dependencies_digest]}", asset[:uri], true)
261
+ # Unloaded asset and stored_asset now have a different URI
262
+ stored_asset = UnloadedAsset.new(asset[:uri], self)
263
+ cache.set(stored_asset.asset_key, cached_asset, true)
142
264
 
143
- asset
265
+ # Save the new relative path for the digest key of the unloaded asset
266
+ cache.set(unloaded.digest_key(asset[:dependencies_digest]), stored_asset.compressed_path, true)
144
267
  end
145
268
 
146
- def fetch_asset_from_dependency_cache(uri, filename, limit = 3)
147
- key = "asset-uri-cache-dependencies:#{VERSION}:#{uri}:#{file_digest(filename)}"
148
- history = cache.get(key) || []
149
269
 
270
+ # Internal: Resolve set of dependency URIs.
271
+ #
272
+ # uris - An Array of "dependencies" for example:
273
+ # ["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
274
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
275
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
276
+ # "file-digest:///Full/path/app/assets/stylesheets"]
277
+ #
278
+ # Returns back array of things that the given uri depends on
279
+ # For example the environment version, if you're using a different version of sprockets
280
+ # then the dependencies should be different, this is used only for generating cache key
281
+ # for example the "environment-version" may be resolved to "environment-1.0-3.2.0" for
282
+ # version "3.2.0" of sprockets.
283
+ #
284
+ # Any paths that are returned are converted to relative paths
285
+ #
286
+ # Returns array of resolved dependencies
287
+ def resolve_dependencies(uris)
288
+ uris.map { |uri| resolve_dependency(uri) }
289
+ end
290
+
291
+ # Internal: Retrieves an asset based on its digest
292
+ #
293
+ # unloaded - An UnloadedAsset
294
+ # limit - An Integer which sets the maximum number of versions of "histories"
295
+ # stored in the cache
296
+ #
297
+ # This method attempts to retrieve the last `limit` number of histories of an asset
298
+ # from the cache a "history" which is an array of unresolved "dependencies" that the asset needs
299
+ # to compile. In this case a dependency can refer to either an asset e.g. index.js
300
+ # may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
301
+ # compilation, such as the VERSION of Sprockets (i.e. the environment) and what "processors"
302
+ # are used.
303
+ #
304
+ # For example a history array may look something like this
305
+ #
306
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
307
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
308
+ # "processors:type=text/css&file_digesttype=text/css&pipeline=self",
309
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
310
+ #
311
+ # Where the first entry is a Set of dependencies for last generated version of that asset.
312
+ # Multiple versions are stored since Sprockets keeps the last `limit` number of assets
313
+ # generated present in the system.
314
+ #
315
+ # If a "history" of dependencies is present in the cache, each version of "history" will be
316
+ # yielded to the passed block which is responsible for loading the asset. If found, the existing
317
+ # history will be saved with the dependency that found a valid asset moved to the front.
318
+ #
319
+ # If no history is present, or if none of the histories could be resolved to a valid asset then,
320
+ # the block is yielded to and expected to return a valid asset.
321
+ # When this happens the dependencies for the returned asset are added to the "history", and older
322
+ # entries are removed if the "history" is above `limit`.
323
+ def fetch_asset_from_dependency_cache(unloaded, limit = 3)
324
+ key = unloaded.dependency_history_key
325
+
326
+ history = cache.get(key) || []
150
327
  history.each_with_index do |deps, index|
151
- if asset = yield(deps)
328
+ expanded_deps = deps.map do |path|
329
+ path.start_with?("file-digest://") ? expand_from_root(path) : path
330
+ end
331
+ if asset = yield(expanded_deps)
152
332
  cache.set(key, history.rotate!(index)) if index > 0
153
333
  return asset
154
334
  end
155
335
  end
156
336
 
157
337
  asset = yield
158
- deps = asset[:metadata][:dependencies]
338
+ deps = asset[:metadata][:dependencies].dup.map! do |uri|
339
+ uri.start_with?("file-digest://") ? compress_from_root(uri) : uri
340
+ end
159
341
  cache.set(key, history.unshift(deps).take(limit))
160
342
  asset
161
343
  end