sprockets 2.6.0 → 4.2.2

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 (100) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +118 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +541 -289
  5. data/bin/sprockets +20 -7
  6. data/lib/rake/sprocketstask.rb +34 -17
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +158 -210
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +8 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  12. data/lib/sprockets/autoload/eco.rb +8 -0
  13. data/lib/sprockets/autoload/ejs.rb +8 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +8 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +8 -0
  18. data/lib/sprockets/autoload/yui.rb +8 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +16 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +89 -378
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache/file_store.rb +190 -14
  26. data/lib/sprockets/cache/memory_store.rb +84 -0
  27. data/lib/sprockets/cache/null_store.rb +54 -0
  28. data/lib/sprockets/cache.rb +271 -0
  29. data/lib/sprockets/cached_environment.rb +64 -0
  30. data/lib/sprockets/closure_compressor.rb +48 -0
  31. data/lib/sprockets/coffee_script_processor.rb +39 -0
  32. data/lib/sprockets/compressing.rb +134 -0
  33. data/lib/sprockets/configuration.rb +79 -0
  34. data/lib/sprockets/context.rb +166 -150
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +197 -0
  37. data/lib/sprockets/directive_processor.rb +241 -215
  38. data/lib/sprockets/eco_processor.rb +33 -0
  39. data/lib/sprockets/ejs_processor.rb +32 -0
  40. data/lib/sprockets/encoding_utils.rb +261 -0
  41. data/lib/sprockets/environment.rb +23 -64
  42. data/lib/sprockets/erb_processor.rb +43 -0
  43. data/lib/sprockets/errors.rb +5 -13
  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 +16 -0
  50. data/lib/sprockets/http_utils.rb +135 -0
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +36 -19
  53. data/lib/sprockets/loader.rb +347 -0
  54. data/lib/sprockets/manifest.rb +228 -112
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +78 -31
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +77 -0
  59. data/lib/sprockets/path_digest_utils.rb +48 -0
  60. data/lib/sprockets/path_utils.rb +367 -0
  61. data/lib/sprockets/paths.rb +43 -19
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +146 -164
  64. data/lib/sprockets/processor_utils.rb +170 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +20 -15
  67. data/lib/sprockets/sass_compressor.rb +55 -10
  68. data/lib/sprockets/sass_functions.rb +3 -70
  69. data/lib/sprockets/sass_importer.rb +3 -29
  70. data/lib/sprockets/sass_processor.rb +313 -0
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +159 -91
  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 +173 -0
  77. data/lib/sprockets/uglifier_compressor.rb +66 -0
  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 +194 -0
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +193 -52
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. data/lib/sprockets.rb +217 -75
  86. metadata +272 -117
  87. data/lib/sprockets/asset_attributes.rb +0 -131
  88. data/lib/sprockets/bundled_asset.rb +0 -80
  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/sass_template.rb +0 -60
  99. data/lib/sprockets/scss_template.rb +0 -13
  100. data/lib/sprockets/static_asset.rb +0 -58
@@ -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,347 @@
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
+ extensions = config[:mime_types][type][:extensions]
169
+ extension = extensions.include?(extname) ? extname : extensions.first
170
+ logical_path += extension
171
+ end
172
+
173
+ if type != file_type && !config[:transformers][file_type][type]
174
+ raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
175
+ end
176
+
177
+ processors = processors_for(type, file_type, pipeline)
178
+
179
+ processors_dep_uri = build_processors_uri(type, file_type, pipeline)
180
+ dependencies = config[:dependencies] + [processors_dep_uri]
181
+
182
+ # Read into memory and process if theres a processor pipeline
183
+ if processors.any?
184
+ result = call_processors(processors, {
185
+ environment: self,
186
+ cache: self.cache,
187
+ uri: unloaded.uri,
188
+ filename: unloaded.filename,
189
+ load_path: load_path,
190
+ name: name,
191
+ content_type: type,
192
+ metadata: {
193
+ dependencies: dependencies
194
+ }
195
+ })
196
+ validate_processor_result!(result)
197
+ source = result.delete(:data)
198
+ metadata = result
199
+ metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset)
200
+ metadata[:digest] = digest(source)
201
+ metadata[:length] = source.bytesize
202
+ metadata[:environment_version] = version
203
+ else
204
+ dependencies << build_file_digest_uri(unloaded.filename)
205
+ metadata = {
206
+ digest: file_digest(unloaded.filename),
207
+ length: self.stat(unloaded.filename).size,
208
+ dependencies: dependencies,
209
+ environment_version: version,
210
+ }
211
+ end
212
+
213
+ asset = {
214
+ uri: unloaded.uri,
215
+ load_path: load_path,
216
+ filename: unloaded.filename,
217
+ name: name,
218
+ logical_path: logical_path,
219
+ content_type: type,
220
+ source: source,
221
+ metadata: metadata,
222
+ dependencies_digest: DigestUtils.digest(resolve_dependencies(metadata[:dependencies]))
223
+ }
224
+
225
+ asset[:id] = hexdigest(asset)
226
+ asset[:uri] = build_asset_uri(unloaded.filename, unloaded.params.merge(id: asset[:id]))
227
+
228
+ store_asset(asset, unloaded)
229
+ asset
230
+ end
231
+
232
+ # Internal: Save a given asset to the cache
233
+ #
234
+ # asset - A hash containing values of loaded asset
235
+ # unloaded - The UnloadedAsset used to lookup the `asset`
236
+ #
237
+ # This method converts all absolute paths to "compressed" paths
238
+ # which are relative if they're in the root.
239
+ def store_asset(asset, unloaded)
240
+ # Save the asset in the cache under the new URI
241
+ cached_asset = asset.dup
242
+ cached_asset[:uri] = compress_from_root(asset[:uri])
243
+ cached_asset[:filename] = compress_from_root(asset[:filename])
244
+ cached_asset[:load_path] = compress_from_root(asset[:load_path])
245
+
246
+ if cached_asset[:metadata]
247
+ # Deep dup to avoid modifying `asset`
248
+ cached_asset[:metadata] = cached_asset[:metadata].dup
249
+ compress_key_from_hash(cached_asset[:metadata], :included)
250
+ compress_key_from_hash(cached_asset[:metadata], :links)
251
+ compress_key_from_hash(cached_asset[:metadata], :stubbed)
252
+ compress_key_from_hash(cached_asset[:metadata], :required)
253
+ compress_key_from_hash(cached_asset[:metadata], :to_load)
254
+ compress_key_from_hash(cached_asset[:metadata], :to_link)
255
+ compress_key_from_hash(cached_asset[:metadata], :dependencies) { |uri| uri.start_with?("file-digest://") }
256
+
257
+ cached_asset[:metadata].each do |key, value|
258
+ next unless key.match?(/_dependencies\z/) # rubocop:disable Performance/EndWith
259
+ compress_key_from_hash(cached_asset[:metadata], key)
260
+ end
261
+ end
262
+
263
+ # Unloaded asset and stored_asset now have a different URI
264
+ stored_asset = UnloadedAsset.new(asset[:uri], self)
265
+ cache.set(stored_asset.asset_key, cached_asset, true)
266
+
267
+ # Save the new relative path for the digest key of the unloaded asset
268
+ cache.set(unloaded.digest_key(asset[:dependencies_digest]), stored_asset.compressed_path, true)
269
+ end
270
+
271
+
272
+ # Internal: Resolve set of dependency URIs.
273
+ #
274
+ # uris - An Array of "dependencies" for example:
275
+ # ["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
276
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
277
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
278
+ # "file-digest:///Full/path/app/assets/stylesheets"]
279
+ #
280
+ # Returns back array of things that the given uri depends on
281
+ # For example the environment version, if you're using a different version of sprockets
282
+ # then the dependencies should be different, this is used only for generating cache key
283
+ # for example the "environment-version" may be resolved to "environment-1.0-3.2.0" for
284
+ # version "3.2.0" of sprockets.
285
+ #
286
+ # Any paths that are returned are converted to relative paths
287
+ #
288
+ # Returns array of resolved dependencies
289
+ def resolve_dependencies(uris)
290
+ uris.map { |uri| resolve_dependency(uri) }
291
+ end
292
+
293
+ # Internal: Retrieves an asset based on its digest
294
+ #
295
+ # unloaded - An UnloadedAsset
296
+ # limit - An Integer which sets the maximum number of versions of "histories"
297
+ # stored in the cache
298
+ #
299
+ # This method attempts to retrieve the last `limit` number of histories of an asset
300
+ # from the cache a "history" which is an array of unresolved "dependencies" that the asset needs
301
+ # to compile. In this case a dependency can refer to either an asset e.g. index.js
302
+ # may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
303
+ # compilation, such as the VERSION of Sprockets (i.e. the environment) and what "processors"
304
+ # are used.
305
+ #
306
+ # For example a history array may look something like this
307
+ #
308
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
309
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
310
+ # "processors:type=text/css&file_digesttype=text/css&pipeline=self",
311
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
312
+ #
313
+ # Where the first entry is a Set of dependencies for last generated version of that asset.
314
+ # Multiple versions are stored since Sprockets keeps the last `limit` number of assets
315
+ # generated present in the system.
316
+ #
317
+ # If a "history" of dependencies is present in the cache, each version of "history" will be
318
+ # yielded to the passed block which is responsible for loading the asset. If found, the existing
319
+ # history will be saved with the dependency that found a valid asset moved to the front.
320
+ #
321
+ # If no history is present, or if none of the histories could be resolved to a valid asset then,
322
+ # the block is yielded to and expected to return a valid asset.
323
+ # When this happens the dependencies for the returned asset are added to the "history", and older
324
+ # entries are removed if the "history" is above `limit`.
325
+ def fetch_asset_from_dependency_cache(unloaded, limit = 3)
326
+ key = unloaded.dependency_history_key
327
+
328
+ history = cache.get(key) || []
329
+ history.each_with_index do |deps, index|
330
+ expanded_deps = deps.map do |path|
331
+ path.start_with?("file-digest://") ? expand_from_root(path) : path
332
+ end
333
+ if asset = yield(expanded_deps)
334
+ cache.set(key, history.rotate!(index)) if index > 0
335
+ return asset
336
+ end
337
+ end
338
+
339
+ asset = yield
340
+ deps = asset[:metadata][:dependencies].dup.map! do |uri|
341
+ uri.start_with?("file-digest://") ? compress_from_root(uri) : uri
342
+ end
343
+ cache.set(key, history.unshift(deps).take(limit))
344
+ asset
345
+ end
346
+ end
347
+ end