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