sprockets 2.12.5 → 3.7.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +296 -0
  3. data/LICENSE +2 -2
  4. data/README.md +235 -262
  5. data/bin/sprockets +1 -0
  6. data/lib/rake/sprocketstask.rb +5 -4
  7. data/lib/sprockets/asset.rb +143 -212
  8. data/lib/sprockets/autoload/closure.rb +7 -0
  9. data/lib/sprockets/autoload/coffee_script.rb +7 -0
  10. data/lib/sprockets/autoload/eco.rb +7 -0
  11. data/lib/sprockets/autoload/ejs.rb +7 -0
  12. data/lib/sprockets/autoload/sass.rb +7 -0
  13. data/lib/sprockets/autoload/uglifier.rb +7 -0
  14. data/lib/sprockets/autoload/yui.rb +7 -0
  15. data/lib/sprockets/autoload.rb +11 -0
  16. data/lib/sprockets/base.rb +56 -393
  17. data/lib/sprockets/bower.rb +58 -0
  18. data/lib/sprockets/bundle.rb +69 -0
  19. data/lib/sprockets/cache/file_store.rb +168 -14
  20. data/lib/sprockets/cache/memory_store.rb +66 -0
  21. data/lib/sprockets/cache/null_store.rb +46 -0
  22. data/lib/sprockets/cache.rb +236 -0
  23. data/lib/sprockets/cached_environment.rb +69 -0
  24. data/lib/sprockets/closure_compressor.rb +35 -10
  25. data/lib/sprockets/coffee_script_processor.rb +25 -0
  26. data/lib/sprockets/coffee_script_template.rb +17 -0
  27. data/lib/sprockets/compressing.rb +44 -23
  28. data/lib/sprockets/configuration.rb +83 -0
  29. data/lib/sprockets/context.rb +86 -144
  30. data/lib/sprockets/dependencies.rb +73 -0
  31. data/lib/sprockets/deprecation.rb +90 -0
  32. data/lib/sprockets/digest_utils.rb +180 -0
  33. data/lib/sprockets/directive_processor.rb +207 -211
  34. data/lib/sprockets/eco_processor.rb +32 -0
  35. data/lib/sprockets/eco_template.rb +9 -30
  36. data/lib/sprockets/ejs_processor.rb +31 -0
  37. data/lib/sprockets/ejs_template.rb +9 -29
  38. data/lib/sprockets/encoding_utils.rb +261 -0
  39. data/lib/sprockets/engines.rb +53 -35
  40. data/lib/sprockets/environment.rb +17 -64
  41. data/lib/sprockets/erb_processor.rb +30 -0
  42. data/lib/sprockets/erb_template.rb +11 -0
  43. data/lib/sprockets/errors.rb +4 -13
  44. data/lib/sprockets/file_reader.rb +15 -0
  45. data/lib/sprockets/http_utils.rb +117 -0
  46. data/lib/sprockets/jst_processor.rb +35 -15
  47. data/lib/sprockets/legacy.rb +330 -0
  48. data/lib/sprockets/legacy_proc_processor.rb +35 -0
  49. data/lib/sprockets/legacy_tilt_processor.rb +29 -0
  50. data/lib/sprockets/loader.rb +325 -0
  51. data/lib/sprockets/manifest.rb +202 -127
  52. data/lib/sprockets/manifest_utils.rb +45 -0
  53. data/lib/sprockets/mime.rb +112 -31
  54. data/lib/sprockets/path_dependency_utils.rb +85 -0
  55. data/lib/sprockets/path_digest_utils.rb +47 -0
  56. data/lib/sprockets/path_utils.rb +287 -0
  57. data/lib/sprockets/paths.rb +42 -19
  58. data/lib/sprockets/processing.rb +178 -126
  59. data/lib/sprockets/processor_utils.rb +180 -0
  60. data/lib/sprockets/resolve.rb +211 -0
  61. data/lib/sprockets/sass_cache_store.rb +22 -17
  62. data/lib/sprockets/sass_compressor.rb +39 -15
  63. data/lib/sprockets/sass_functions.rb +2 -70
  64. data/lib/sprockets/sass_importer.rb +2 -30
  65. data/lib/sprockets/sass_processor.rb +292 -0
  66. data/lib/sprockets/sass_template.rb +12 -59
  67. data/lib/sprockets/server.rb +129 -84
  68. data/lib/sprockets/transformers.rb +145 -0
  69. data/lib/sprockets/uglifier_compressor.rb +39 -12
  70. data/lib/sprockets/unloaded_asset.rb +137 -0
  71. data/lib/sprockets/uri_tar.rb +98 -0
  72. data/lib/sprockets/uri_utils.rb +188 -0
  73. data/lib/sprockets/utils/gzip.rb +67 -0
  74. data/lib/sprockets/utils.rb +210 -44
  75. data/lib/sprockets/version.rb +1 -1
  76. data/lib/sprockets/yui_compressor.rb +39 -11
  77. data/lib/sprockets.rb +142 -81
  78. metadata +96 -90
  79. data/lib/sprockets/asset_attributes.rb +0 -137
  80. data/lib/sprockets/bundled_asset.rb +0 -78
  81. data/lib/sprockets/caching.rb +0 -96
  82. data/lib/sprockets/charset_normalizer.rb +0 -41
  83. data/lib/sprockets/index.rb +0 -100
  84. data/lib/sprockets/processed_asset.rb +0 -152
  85. data/lib/sprockets/processor.rb +0 -32
  86. data/lib/sprockets/safety_colons.rb +0 -28
  87. data/lib/sprockets/scss_template.rb +0 -13
  88. data/lib/sprockets/static_asset.rb +0 -60
@@ -0,0 +1,325 @@
1
+ require 'sprockets/asset'
2
+ require 'sprockets/digest_utils'
3
+ require 'sprockets/engines'
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 Engines, 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
+ #
31
+ # Returns Asset.
32
+ def load(uri)
33
+ unloaded = UnloadedAsset.new(uri, self)
34
+ if unloaded.params.key?(:id)
35
+ unless asset = asset_from_cache(unloaded.asset_key)
36
+ id = unloaded.params.delete(:id)
37
+ uri_without_id = build_asset_uri(unloaded.filename, unloaded.params)
38
+ asset = load_from_unloaded(UnloadedAsset.new(uri_without_id, self))
39
+ if asset[:id] != id
40
+ @logger.warn "Sprockets load error: Tried to find #{uri}, but latest was id #{asset[:id]}"
41
+ end
42
+ end
43
+ else
44
+ asset = fetch_asset_from_dependency_cache(unloaded) do |paths|
45
+ # When asset is previously generated, its "dependencies" are stored in the cache.
46
+ # The presence of `paths` indicates dependencies were stored.
47
+ # We can check to see if the dependencies have not changed by "resolving" them and
48
+ # generating a digest key from the resolved entries. If this digest key has not
49
+ # changed the asset will be pulled from cache.
50
+ #
51
+ # If this `paths` is present but the cache returns nothing then `fetch_asset_from_dependency_cache`
52
+ # will confusingly be called again with `paths` set to nil where the asset will be
53
+ # loaded from disk.
54
+ if paths
55
+ digest = DigestUtils.digest(resolve_dependencies(paths))
56
+ if uri_from_cache = cache.get(unloaded.digest_key(digest), true)
57
+ asset_from_cache(UnloadedAsset.new(uri_from_cache, self).asset_key)
58
+ end
59
+ else
60
+ load_from_unloaded(unloaded)
61
+ end
62
+ end
63
+ end
64
+ Asset.new(self, asset)
65
+ end
66
+
67
+ private
68
+
69
+ # Internal: Load asset hash from cache
70
+ #
71
+ # key - A String containing lookup information for an asset
72
+ #
73
+ # This method converts all "compressed" paths to absolute paths.
74
+ # Returns a hash of values representing an asset
75
+ def asset_from_cache(key)
76
+ asset = cache.get(key, true)
77
+ if asset
78
+ asset[:uri] = expand_from_root(asset[:uri])
79
+ asset[:load_path] = expand_from_root(asset[:load_path])
80
+ asset[:filename] = expand_from_root(asset[:filename])
81
+ asset[:metadata][:included].map! { |uri| expand_from_root(uri) } if asset[:metadata][:included]
82
+ asset[:metadata][:links].map! { |uri| expand_from_root(uri) } if asset[:metadata][:links]
83
+ asset[:metadata][:stubbed].map! { |uri| expand_from_root(uri) } if asset[:metadata][:stubbed]
84
+ asset[:metadata][:required].map! { |uri| expand_from_root(uri) } if asset[:metadata][:required]
85
+ asset[:metadata][:dependencies].map! { |uri| uri.start_with?("file-digest://") ? expand_from_root(uri) : uri } if asset[:metadata][:dependencies]
86
+
87
+ asset[:metadata].each_key do |k|
88
+ next unless k =~ /_dependencies\z/
89
+ asset[:metadata][k].map! { |uri| expand_from_root(uri) }
90
+ end
91
+ end
92
+ asset
93
+ end
94
+
95
+ # Internal: Loads an asset and saves it to cache
96
+ #
97
+ # unloaded - An UnloadedAsset
98
+ #
99
+ # This method is only called when the given unloaded asset could not be
100
+ # successfully pulled from cache.
101
+ def load_from_unloaded(unloaded)
102
+ unless file?(unloaded.filename)
103
+ raise FileNotFound, "could not find file: #{unloaded.filename}"
104
+ end
105
+
106
+ load_path, logical_path = paths_split(config[:paths], unloaded.filename)
107
+
108
+ unless load_path
109
+ raise FileOutsidePaths, "#{unloaded.filename} is no longer under a load path: #{self.paths.join(', ')}"
110
+ end
111
+
112
+ logical_path, file_type, engine_extnames, _ = parse_path_extnames(logical_path)
113
+ name = logical_path
114
+
115
+ if pipeline = unloaded.params[:pipeline]
116
+ logical_path += ".#{pipeline}"
117
+ end
118
+
119
+ if type = unloaded.params[:type]
120
+ logical_path += config[:mime_types][type][:extensions].first
121
+ end
122
+
123
+ if type != file_type && !config[:transformers][file_type][type]
124
+ raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
125
+ end
126
+
127
+ processors = processors_for(type, file_type, engine_extnames, pipeline)
128
+
129
+ processors_dep_uri = build_processors_uri(type, file_type, engine_extnames, pipeline)
130
+ dependencies = config[:dependencies] + [processors_dep_uri]
131
+
132
+ # Read into memory and process if theres a processor pipeline
133
+ if processors.any?
134
+ result = call_processors(processors, {
135
+ environment: self,
136
+ cache: self.cache,
137
+ uri: unloaded.uri,
138
+ filename: unloaded.filename,
139
+ load_path: load_path,
140
+ name: name,
141
+ content_type: type,
142
+ metadata: { dependencies: dependencies }
143
+ })
144
+ validate_processor_result!(result)
145
+ source = result.delete(:data)
146
+ metadata = result
147
+ metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset)
148
+ metadata[:digest] = digest(source)
149
+ metadata[:length] = source.bytesize
150
+ else
151
+ dependencies << build_file_digest_uri(unloaded.filename)
152
+ metadata = {
153
+ digest: file_digest(unloaded.filename),
154
+ length: self.stat(unloaded.filename).size,
155
+ dependencies: dependencies
156
+ }
157
+ end
158
+
159
+ asset = {
160
+ uri: unloaded.uri,
161
+ load_path: load_path,
162
+ filename: unloaded.filename,
163
+ name: name,
164
+ logical_path: logical_path,
165
+ content_type: type,
166
+ source: source,
167
+ metadata: metadata,
168
+ dependencies_digest: DigestUtils.digest(resolve_dependencies(metadata[:dependencies]))
169
+ }
170
+
171
+ asset[:id] = pack_hexdigest(digest(asset))
172
+ asset[:uri] = build_asset_uri(unloaded.filename, unloaded.params.merge(id: asset[:id]))
173
+
174
+ # Deprecated: Avoid tracking Asset mtime
175
+ asset[:mtime] = metadata[:dependencies].map { |u|
176
+ if u.start_with?("file-digest:")
177
+ s = self.stat(parse_file_digest_uri(u))
178
+ s ? s.mtime.to_i : nil
179
+ else
180
+ nil
181
+ end
182
+ }.compact.max
183
+ asset[:mtime] ||= self.stat(unloaded.filename).mtime.to_i
184
+
185
+ store_asset(asset, unloaded)
186
+ asset
187
+ end
188
+
189
+ # Internal: Save a given asset to the cache
190
+ #
191
+ # asset - A hash containing values of loaded asset
192
+ # unloaded - The UnloadedAsset used to lookup the `asset`
193
+ #
194
+ # This method converts all absolute paths to "compressed" paths
195
+ # which are relative if they're in the root.
196
+ def store_asset(asset, unloaded)
197
+ # Save the asset in the cache under the new URI
198
+ cached_asset = asset.dup
199
+ cached_asset[:uri] = compress_from_root(asset[:uri])
200
+ cached_asset[:filename] = compress_from_root(asset[:filename])
201
+ cached_asset[:load_path] = compress_from_root(asset[:load_path])
202
+
203
+ if cached_asset[:metadata]
204
+ # Deep dup to avoid modifying `asset`
205
+ cached_asset[:metadata] = cached_asset[:metadata].dup
206
+ if cached_asset[:metadata][:included] && !cached_asset[:metadata][:included].empty?
207
+ cached_asset[:metadata][:included] = cached_asset[:metadata][:included].dup
208
+ cached_asset[:metadata][:included].map! { |uri| compress_from_root(uri) }
209
+ end
210
+
211
+ if cached_asset[:metadata][:links] && !cached_asset[:metadata][:links].empty?
212
+ cached_asset[:metadata][:links] = cached_asset[:metadata][:links].dup
213
+ cached_asset[:metadata][:links].map! { |uri| compress_from_root(uri) }
214
+ end
215
+
216
+ if cached_asset[:metadata][:stubbed] && !cached_asset[:metadata][:stubbed].empty?
217
+ cached_asset[:metadata][:stubbed] = cached_asset[:metadata][:stubbed].dup
218
+ cached_asset[:metadata][:stubbed].map! { |uri| compress_from_root(uri) }
219
+ end
220
+
221
+ if cached_asset[:metadata][:required] && !cached_asset[:metadata][:required].empty?
222
+ cached_asset[:metadata][:required] = cached_asset[:metadata][:required].dup
223
+ cached_asset[:metadata][:required].map! { |uri| compress_from_root(uri) }
224
+ end
225
+
226
+ if cached_asset[:metadata][:dependencies] && !cached_asset[:metadata][:dependencies].empty?
227
+ cached_asset[:metadata][:dependencies] = cached_asset[:metadata][:dependencies].dup
228
+ cached_asset[:metadata][:dependencies].map! do |uri|
229
+ uri.start_with?("file-digest://".freeze) ? compress_from_root(uri) : uri
230
+ end
231
+ end
232
+
233
+ # compress all _dependencies in metadata like `sass_dependencies`
234
+ cached_asset[:metadata].each do |key, value|
235
+ next unless key =~ /_dependencies\z/
236
+ cached_asset[:metadata][key] = value.dup
237
+ cached_asset[:metadata][key].map! {|uri| compress_from_root(uri) }
238
+ end
239
+ end
240
+
241
+ # Unloaded asset and stored_asset now have a different URI
242
+ stored_asset = UnloadedAsset.new(asset[:uri], self)
243
+ cache.set(stored_asset.asset_key, cached_asset, true)
244
+
245
+ # Save the new relative path for the digest key of the unloaded asset
246
+ cache.set(unloaded.digest_key(asset[:dependencies_digest]), stored_asset.compressed_path, true)
247
+ end
248
+
249
+
250
+ # Internal: Resolve set of dependency URIs.
251
+ #
252
+ # uris - An Array of "dependencies" for example:
253
+ # ["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
254
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
255
+ # "processors:type=text/css&file_type=text/css&pipeline=self",
256
+ # "file-digest:///Full/path/app/assets/stylesheets"]
257
+ #
258
+ # Returns back array of things that the given uri dpends on
259
+ # For example the environment version, if you're using a different version of sprockets
260
+ # then the dependencies should be different, this is used only for generating cache key
261
+ # for example the "environment-version" may be resolved to "environment-1.0-3.2.0" for
262
+ # version "3.2.0" of sprockets.
263
+ #
264
+ # Any paths that are returned are converted to relative paths
265
+ #
266
+ # Returns array of resolved dependencies
267
+ def resolve_dependencies(uris)
268
+ uris.map { |uri| resolve_dependency(uri) }
269
+ end
270
+
271
+ # Internal: Retrieves an asset based on its digest
272
+ #
273
+ # unloaded - An UnloadedAsset
274
+ # limit - A Fixnum which sets the maximum number of versions of "histories"
275
+ # stored in the cache
276
+ #
277
+ # This method attempts to retrieve the last `limit` number of histories of an asset
278
+ # from the cache a "history" which is an array of unresolved "dependencies" that the asset needs
279
+ # to compile. In this case A dependency can refer to either an asset i.e. index.js
280
+ # may rely on jquery.js (so jquery.js is a depndency), or other factors that may affect
281
+ # compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors"
282
+ # are used.
283
+ #
284
+ # For example a history array may look something like this
285
+ #
286
+ # [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
287
+ # "file-digest:///Full/path/app/assets/stylesheets/application.css",
288
+ # "processors:type=text/css&file_digesttype=text/css&pipeline=self",
289
+ # "file-digest:///Full/path/app/assets/stylesheets"]]
290
+ #
291
+ # Where the first entry is a Set of dependencies for last generated version of that asset.
292
+ # Multiple versions are stored since sprockets keeps the last `limit` number of assets
293
+ # generated present in the system.
294
+ #
295
+ # If a "history" of dependencies is present in the cache, each version of "history" will be
296
+ # yielded to the passed block which is responsible for loading the asset. If found, the existing
297
+ # history will be saved with the dependency that found a valid asset moved to the front.
298
+ #
299
+ # If no history is present, or if none of the histories could be resolved to a valid asset then,
300
+ # the block is yielded to and expected to return a valid asset.
301
+ # When this happens the dependencies for the returned asset are added to the "history", and older
302
+ # entries are removed if the "history" is above `limit`.
303
+ def fetch_asset_from_dependency_cache(unloaded, limit = 3)
304
+ key = unloaded.dependency_history_key
305
+
306
+ history = cache.get(key) || []
307
+ history.each_with_index do |deps, index|
308
+ expanded_deps = deps.map do |path|
309
+ path.start_with?("file-digest://") ? expand_from_root(path) : path
310
+ end
311
+ if asset = yield(expanded_deps)
312
+ cache.set(key, history.rotate!(index)) if index > 0
313
+ return asset
314
+ end
315
+ end
316
+
317
+ asset = yield
318
+ deps = asset[:metadata][:dependencies].dup.map! do |uri|
319
+ uri.start_with?("file-digest://") ? compress_from_root(uri) : uri
320
+ end
321
+ cache.set(key, history.unshift(deps).take(limit))
322
+ asset
323
+ end
324
+ end
325
+ end