sprockets 3.0.0.beta.6 → 3.0.0.beta.7

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +171 -100
  3. data/lib/rake/sprocketstask.rb +2 -2
  4. data/lib/sprockets.rb +69 -63
  5. data/lib/sprockets/asset.rb +2 -61
  6. data/lib/sprockets/autoload_processor.rb +48 -0
  7. data/lib/sprockets/base.rb +4 -6
  8. data/lib/sprockets/bower.rb +8 -5
  9. data/lib/sprockets/bundle.rb +9 -13
  10. data/lib/sprockets/cache.rb +19 -14
  11. data/lib/sprockets/cache/file_store.rb +2 -1
  12. data/lib/sprockets/cached_environment.rb +15 -68
  13. data/lib/sprockets/closure_compressor.rb +17 -4
  14. data/lib/sprockets/coffee_script_processor.rb +26 -0
  15. data/lib/sprockets/coffee_script_template.rb +3 -20
  16. data/lib/sprockets/compressing.rb +10 -4
  17. data/lib/sprockets/configuration.rb +21 -37
  18. data/lib/sprockets/context.rb +37 -67
  19. data/lib/sprockets/dependencies.rb +73 -0
  20. data/lib/sprockets/digest_utils.rb +8 -2
  21. data/lib/sprockets/directive_processor.rb +122 -165
  22. data/lib/sprockets/eco_processor.rb +32 -0
  23. data/lib/sprockets/eco_template.rb +3 -26
  24. data/lib/sprockets/ejs_processor.rb +31 -0
  25. data/lib/sprockets/ejs_template.rb +3 -25
  26. data/lib/sprockets/encoding_utils.rb +9 -21
  27. data/lib/sprockets/engines.rb +25 -27
  28. data/lib/sprockets/environment.rb +9 -1
  29. data/lib/sprockets/erb_processor.rb +30 -0
  30. data/lib/sprockets/erb_template.rb +3 -20
  31. data/lib/sprockets/file_reader.rb +15 -0
  32. data/lib/sprockets/http_utils.rb +2 -0
  33. data/lib/sprockets/jst_processor.rb +9 -2
  34. data/lib/sprockets/legacy.rb +212 -3
  35. data/lib/sprockets/legacy_tilt_processor.rb +1 -1
  36. data/lib/sprockets/loader.rb +95 -89
  37. data/lib/sprockets/manifest.rb +23 -59
  38. data/lib/sprockets/mime.rb +28 -41
  39. data/lib/sprockets/path_dependency_utils.rb +76 -0
  40. data/lib/sprockets/path_utils.rb +21 -1
  41. data/lib/sprockets/paths.rb +23 -8
  42. data/lib/sprockets/processing.rb +102 -91
  43. data/lib/sprockets/processor_utils.rb +97 -0
  44. data/lib/sprockets/resolve.rb +110 -97
  45. data/lib/sprockets/sass_cache_store.rb +2 -2
  46. data/lib/sprockets/sass_compressor.rb +17 -4
  47. data/lib/sprockets/sass_functions.rb +2 -2
  48. data/lib/sprockets/sass_importer.rb +2 -2
  49. data/lib/sprockets/sass_processor.rb +305 -0
  50. data/lib/sprockets/sass_template.rb +4 -286
  51. data/lib/sprockets/server.rb +1 -13
  52. data/lib/sprockets/transformers.rb +62 -25
  53. data/lib/sprockets/uglifier_compressor.rb +17 -4
  54. data/lib/sprockets/uri_utils.rb +190 -0
  55. data/lib/sprockets/utils.rb +87 -6
  56. data/lib/sprockets/version.rb +1 -1
  57. data/lib/sprockets/yui_compressor.rb +17 -4
  58. metadata +14 -5
  59. data/lib/sprockets/asset_uri.rb +0 -80
  60. data/lib/sprockets/lazy_processor.rb +0 -15
@@ -1,7 +1,73 @@
1
+ require 'pathname'
2
+ require 'sprockets/asset'
3
+ require 'sprockets/base'
4
+ require 'sprockets/cached_environment'
5
+ require 'sprockets/context'
1
6
  require 'sprockets/manifest'
7
+ require 'sprockets/resolve'
2
8
 
3
9
  module Sprockets
4
- module Legacy
10
+ autoload :EcoTemplate, 'sprockets/eco_template'
11
+ autoload :EjsTemplate, 'sprockets/ejs_template'
12
+ autoload :ERBTemplate, 'sprockets/erb_template'
13
+ autoload :SassTemplate, 'sprockets/sass_template'
14
+ autoload :ScssTemplate, 'sprockets/sass_template'
15
+
16
+ # Deprecated
17
+ Index = CachedEnvironment
18
+
19
+ class Base
20
+ include Resolve
21
+
22
+ # Deprecated: Change default return type of resolve() to return 2.x
23
+ # compatible plain filename String. 4.x will always return an Asset URI
24
+ # and a set of file system dependencies that had to be read to compute the
25
+ # result.
26
+ #
27
+ # 2.x
28
+ #
29
+ # resolve("foo.js")
30
+ # # => "/path/to/app/javascripts/foo.js"
31
+ #
32
+ # 3.x
33
+ #
34
+ # resolve("foo.js")
35
+ # # => "/path/to/app/javascripts/foo.js"
36
+ #
37
+ # resolve("foo.js", compat: true)
38
+ # # => "/path/to/app/javascripts/foo.js"
39
+ #
40
+ # resolve("foo.js", compat: false)
41
+ # # => [
42
+ # # "file:///path/to/app/javascripts/foo.js?type=application/javascript"
43
+ # # #<Set: {"file-digest:/path/to/app/javascripts/foo.js"}>
44
+ # # ]
45
+ #
46
+ # 4.x
47
+ #
48
+ # resolve("foo.js")
49
+ # # => [
50
+ # # "file:///path/to/app/javascripts/foo.js?type=application/javascript"
51
+ # # #<Set: {"file-digest:/path/to/app/javascripts/foo.js"}>
52
+ # # ]
53
+ #
54
+ def resolve_with_compat(path, options = {})
55
+ options = options.dup
56
+ if options.delete(:compat) { true }
57
+ uri, _ = resolve_without_compat(path, options)
58
+ if uri
59
+ path, _ = parse_asset_uri(uri)
60
+ path
61
+ else
62
+ nil
63
+ end
64
+ else
65
+ resolve_without_compat(path, options)
66
+ end
67
+ end
68
+ alias_method :resolve_without_compat, :resolve
69
+ alias_method :resolve, :resolve_with_compat
70
+
5
71
  # Deprecated: Iterate over all logical paths with a matcher.
6
72
  #
7
73
  # Remove from 4.x.
@@ -54,11 +120,11 @@ module Sprockets
54
120
  end
55
121
 
56
122
  def cache_get(key)
57
- cache._get(key)
123
+ cache.get(key)
58
124
  end
59
125
 
60
126
  def cache_set(key, value)
61
- cache._set(key, value)
127
+ cache.set(key, value)
62
128
  end
63
129
 
64
130
  private
@@ -95,4 +161,147 @@ module Sprockets
95
161
  end
96
162
  end
97
163
  end
164
+
165
+ class Asset
166
+ # Deprecated: Use #filename instead.
167
+ #
168
+ # Returns Pathname.
169
+ def pathname
170
+ @pathname ||= Pathname.new(filename)
171
+ end
172
+
173
+ # Deprecated: Expand asset into an `Array` of parts.
174
+ #
175
+ # Appending all of an assets body parts together should give you
176
+ # the asset's contents as a whole.
177
+ #
178
+ # This allows you to link to individual files for debugging
179
+ # purposes.
180
+ #
181
+ # Use Asset#included instead. Keeping a full copy of the bundle's processed
182
+ # assets in memory (and in cache) is expensive and redundant. The common use
183
+ # case is to relink to the assets anyway.
184
+ #
185
+ # Returns Array of Assets.
186
+ def to_a
187
+ if metadata[:included]
188
+ metadata[:included].map { |uri| @environment.load(uri) }
189
+ else
190
+ [self]
191
+ end
192
+ end
193
+
194
+ # Deprecated: Get all required Assets.
195
+ #
196
+ # See Asset#to_a
197
+ #
198
+ # Returns Array of Assets.
199
+ def dependencies
200
+ to_a.reject { |a| a.filename.eql?(self.filename) }
201
+ end
202
+
203
+ # Deprecated: Returns Time of the last time the source was modified.
204
+ #
205
+ # Time resolution is normalized to the nearest second.
206
+ #
207
+ # Returns Time.
208
+ def mtime
209
+ Time.at(@mtime)
210
+ end
211
+ end
212
+
213
+ class Context
214
+ # Deprecated: Change default return type of resolve() to return 2.x
215
+ # compatible plain filename String. 4.x will always return an Asset URI.
216
+ #
217
+ # 2.x
218
+ #
219
+ # resolve("foo.js")
220
+ # # => "/path/to/app/javascripts/foo.js"
221
+ #
222
+ # 3.x
223
+ #
224
+ # resolve("foo.js")
225
+ # # => "/path/to/app/javascripts/foo.js"
226
+ #
227
+ # resolve("foo.js", compat: true)
228
+ # # => "/path/to/app/javascripts/foo.js"
229
+ #
230
+ # resolve("foo.js", compat: false)
231
+ # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
232
+ #
233
+ # 4.x
234
+ #
235
+ # resolve("foo.js")
236
+ # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
237
+ #
238
+ def resolve_with_compat(path, options = {})
239
+ options = options.dup
240
+
241
+ # Support old :content_type option, prefer :accept going forward
242
+ if type = options.delete(:content_type)
243
+ type = self.content_type if type == :self
244
+ options[:accept] ||= type
245
+ end
246
+
247
+ if options.delete(:compat) { true }
248
+ uri = resolve_without_compat(path, options)
249
+ path, _ = environment.parse_asset_uri(uri)
250
+ path
251
+ else
252
+ resolve_without_compat(path, options)
253
+ end
254
+ end
255
+ alias_method :resolve_without_compat, :resolve
256
+ alias_method :resolve, :resolve_with_compat
257
+ end
258
+
259
+ class Manifest
260
+ # Deprecated: Compile logical path matching filter into a proc that can be
261
+ # passed to logical_paths.select(&proc).
262
+ #
263
+ # compile_match_filter(proc { |logical_path|
264
+ # File.extname(logical_path) == '.js'
265
+ # })
266
+ #
267
+ # compile_match_filter(/application.js/)
268
+ #
269
+ # compile_match_filter("foo/*.js")
270
+ #
271
+ # Returns a Proc or raise a TypeError.
272
+ def self.compile_match_filter(filter)
273
+ # If the filter is already a proc, great nothing to do.
274
+ if filter.respond_to?(:call)
275
+ filter
276
+ # If the filter is a regexp, wrap it in a proc that tests it against the
277
+ # logical path.
278
+ elsif filter.is_a?(Regexp)
279
+ proc { |logical_path| filter.match(logical_path) }
280
+ elsif filter.is_a?(String)
281
+ # If its an absolute path, detect the matching full filename
282
+ if PathUtils.absolute_path?(filter)
283
+ proc { |logical_path, filename| filename == filter.to_s }
284
+ else
285
+ # Otherwise do an fnmatch against the logical path.
286
+ proc { |logical_path| File.fnmatch(filter.to_s, logical_path) }
287
+ end
288
+ else
289
+ raise TypeError, "unknown filter type: #{filter.inspect}"
290
+ end
291
+ end
292
+
293
+ # Deprecated: Filter logical paths in environment. Useful for selecting what
294
+ # files you want to compile.
295
+ #
296
+ # Returns an Enumerator.
297
+ def filter_logical_paths(*args)
298
+ filters = args.flatten.map { |arg| self.class.compile_match_filter(arg) }
299
+ environment.cached.logical_paths.select do |a, b|
300
+ filters.any? { |f| f.call(a, b) }
301
+ end
302
+ end
303
+
304
+ # Deprecated alias.
305
+ alias_method :find_logical_paths, :filter_logical_paths
306
+ end
98
307
  end
@@ -6,7 +6,7 @@ module Sprockets
6
6
  #
7
7
  # Will be removed in Sprockets 4.x.
8
8
  #
9
- # LegacyTiltProcessor.new(Tilt::CoffeeScriptTemplate)
9
+ # LegacyTiltProcessor.new(Tilt::CoffeeScriptProcessor)
10
10
  #
11
11
  class LegacyTiltProcessor < Delegator
12
12
  def initialize(klass)
@@ -1,19 +1,22 @@
1
- require 'sprockets/asset_uri'
2
1
  require 'sprockets/asset'
3
2
  require 'sprockets/digest_utils'
4
3
  require 'sprockets/engines'
5
4
  require 'sprockets/errors'
5
+ require 'sprockets/file_reader'
6
6
  require 'sprockets/mime'
7
7
  require 'sprockets/path_utils'
8
8
  require 'sprockets/processing'
9
+ require 'sprockets/processor_utils'
9
10
  require 'sprockets/resolve'
10
11
  require 'sprockets/transformers'
12
+ require 'sprockets/uri_utils'
11
13
 
12
14
  module Sprockets
13
15
  # The loader phase takes a asset URI location and returns a constructed Asset
14
16
  # object.
15
17
  module Loader
16
- include DigestUtils, Engines, Mime, PathUtils, Processing, Resolve, Transformers
18
+ include DigestUtils, PathUtils, ProcessorUtils, URIUtils
19
+ include Engines, Mime, Processing, Resolve, Transformers
17
20
 
18
21
  # Public: Load Asset by AssetURI.
19
22
  #
@@ -21,23 +24,35 @@ module Sprockets
21
24
  #
22
25
  # Returns Asset.
23
26
  def load(uri)
24
- _, params = AssetURI.parse(uri)
25
- asset = params.key?(:id) ?
26
- load_asset_by_id_uri(uri) :
27
- load_asset_by_uri(uri)
27
+ filename, params = parse_asset_uri(uri)
28
+ if params.key?(:id)
29
+ asset = cache.fetch(['asset-uri', uri]) do
30
+ load_asset_by_id_uri(uri, filename, params)
31
+ end
32
+ else
33
+ asset = fetch_asset_from_dependency_cache(uri, filename) do |paths|
34
+ if paths
35
+ digest = digest(resolve_dependencies(paths))
36
+ if id_uri = cache.get(['asset-uri-digest', VERSION, uri, digest], true)
37
+ cache.get(['asset-uri', VERSION, id_uri], true)
38
+ end
39
+ else
40
+ load_asset_by_uri(uri, filename, params)
41
+ end
42
+ end
43
+ end
28
44
  Asset.new(self, asset)
29
45
  end
30
46
 
31
47
  private
32
- def load_asset_by_id_uri(uri)
33
- path, params = AssetURI.parse(uri)
34
-
48
+ def load_asset_by_id_uri(uri, filename, params)
35
49
  # Internal assertion, should be routed through load_asset_by_uri
36
50
  unless id = params.delete(:id)
37
51
  raise ArgumentError, "expected uri to have an id: #{uri}"
38
52
  end
39
53
 
40
- asset = load_asset_by_uri(AssetURI.build(path, params))
54
+ uri = build_asset_uri(filename, params)
55
+ asset = load_asset_by_uri(uri, filename, params)
41
56
 
42
57
  if id && asset[:id] != id
43
58
  raise VersionNotFound, "could not find specified id: #{id}"
@@ -46,9 +61,7 @@ module Sprockets
46
61
  asset
47
62
  end
48
63
 
49
- def load_asset_by_uri(uri)
50
- filename, params = AssetURI.parse(uri)
51
-
64
+ def load_asset_by_uri(uri, filename, params)
52
65
  # Internal assertion, should be routed through load_asset_by_id_uri
53
66
  if params.key?(:id)
54
67
  raise ArgumentError, "expected uri to have no id: #{uri}"
@@ -58,8 +71,6 @@ module Sprockets
58
71
  raise FileNotFound, "could not find file: #{filename}"
59
72
  end
60
73
 
61
-
62
- type = params[:type]
63
74
  load_path, logical_path = paths_split(self.paths, filename)
64
75
 
65
76
  unless load_path
@@ -68,99 +79,94 @@ module Sprockets
68
79
 
69
80
  logical_path, file_type, engine_extnames = parse_path_extnames(logical_path)
70
81
  logical_path = normalize_logical_path(logical_path)
82
+ name = logical_path
71
83
 
72
- asset = {
73
- uri: uri,
74
- load_path: load_path,
75
- filename: filename,
76
- name: logical_path,
77
- logical_path: logical_path
78
- }
79
-
80
- if type
81
- asset[:content_type] = type
82
- asset[:logical_path] += mime_types[type][:extensions].first
84
+ if type = params[:type]
85
+ logical_path += mime_types[type][:extensions].first
83
86
  end
84
87
 
85
- if type != file_type
86
- transformers = unwrap_transformer(file_type, type)
87
- unless transformers.any?
88
- raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
89
- end
90
- else
91
- transformers = []
88
+ if type != file_type && !transformers[file_type][type]
89
+ raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
92
90
  end
93
91
 
94
- processed_processors = unwrap_preprocessors(file_type) +
95
- unwrap_engines(engine_extnames).reverse +
96
- transformers +
97
- unwrap_postprocessors(type)
98
-
99
- bundled_processors = params[:skip_bundle] ? [] : unwrap_bundle_processors(type)
92
+ skip_bundle = params[:skip_bundle]
93
+ processors = processors_for(type, file_type, engine_extnames, skip_bundle)
100
94
 
101
- processors = bundled_processors.any? ? bundled_processors : processed_processors
102
- processors += unwrap_encoding_processors(params[:encoding])
95
+ processors_dep_uri = build_processors_uri(type, file_type, engine_extnames, skip_bundle)
96
+ dependencies = self.dependencies + [processors_dep_uri]
103
97
 
104
- # Read into memory and process if theres a processor pipeline or the
105
- # content type is text.
106
- if processors.any? || mime_type_charset_detecter(type)
107
- data = read_file(asset[:filename], asset[:content_type])
108
- metadata = {}
109
-
110
- input = {
98
+ # Read into memory and process if theres a processor pipeline
99
+ if processors.any?
100
+ result = call_processors(processors, {
111
101
  environment: self,
112
102
  cache: self.cache,
113
- uri: asset[:uri],
114
- filename: asset[:filename],
115
- load_path: asset[:load_path],
116
- name: asset[:name],
117
- content_type: asset[:content_type],
118
- metadata: metadata
119
- }
120
-
121
- processors.each do |processor|
122
- begin
123
- result = processor.call(input.merge(data: data, metadata: metadata))
124
- case result
125
- when NilClass
126
- # noop
127
- when Hash
128
- data = result[:data] if result.key?(:data)
129
- metadata = metadata.merge(result)
130
- metadata.delete(:data)
131
- when String
132
- data = result
133
- else
134
- raise Error, "invalid processor return type: #{result.class}"
135
- end
136
- end
137
- end
138
-
139
- asset[:source] = data
140
- asset[:metadata] = metadata.merge(
141
- charset: data.encoding.name.downcase,
142
- digest: digest(data),
143
- length: data.bytesize
103
+ uri: uri,
104
+ filename: filename,
105
+ load_path: load_path,
106
+ name: name,
107
+ content_type: type,
108
+ metadata: { dependencies: dependencies }
109
+ })
110
+ source = result.delete(:data)
111
+ metadata = result.merge!(
112
+ charset: source.encoding.name.downcase,
113
+ digest: digest(source),
114
+ length: source.bytesize
144
115
  )
145
116
  else
146
- asset[:metadata] = {
147
- digest: file_digest(asset[:filename]),
148
- length: self.stat(asset[:filename]).size
117
+ metadata = {
118
+ digest: file_digest(filename),
119
+ length: self.stat(filename).size,
120
+ dependencies: dependencies
149
121
  }
150
122
  end
151
123
 
152
- metadata = asset[:metadata]
153
- metadata[:dependency_paths] = Set.new(metadata[:dependency_paths]).merge([asset[:filename]])
154
- metadata[:dependency_sources_digest] = files_digest(metadata[:dependency_paths])
155
-
156
- asset[:integrity] = integrity_uri(asset[:metadata][:digest], asset[:content_type])
124
+ asset = {
125
+ uri: uri,
126
+ load_path: load_path,
127
+ filename: filename,
128
+ name: name,
129
+ logical_path: logical_path,
130
+ content_type: type,
131
+ source: source,
132
+ metadata: metadata,
133
+ integrity: integrity_uri(metadata[:digest], type),
134
+ dependencies_digest: digest(resolve_dependencies(metadata[:dependencies]))
135
+ }
157
136
 
158
137
  asset[:id] = pack_hexdigest(digest(asset))
159
- asset[:uri] = AssetURI.build(filename, params.merge(id: asset[:id]))
138
+ asset[:uri] = build_asset_uri(filename, params.merge(id: asset[:id]))
160
139
 
161
140
  # Deprecated: Avoid tracking Asset mtime
162
- asset[:mtime] = metadata[:dependency_paths].map { |p| stat(p).mtime.to_i }.max
141
+ asset[:mtime] = metadata[:dependencies].map { |u|
142
+ if u.start_with?("file-digest:")
143
+ s = self.stat(parse_file_digest_uri(u))
144
+ s ? s.mtime.to_i : 0
145
+ else
146
+ 0
147
+ end
148
+ }.max
149
+
150
+ cache.set(['asset-uri', VERSION, asset[:uri]], asset, true)
151
+ cache.set(['asset-uri-digest', VERSION, uri, asset[:dependencies_digest]], asset[:uri], true)
152
+
153
+ asset
154
+ end
155
+
156
+ def fetch_asset_from_dependency_cache(uri, filename, limit = 3)
157
+ key = ['asset-uri-cache-dependencies', VERSION, uri, file_digest(filename)]
158
+ history = cache.get(key) || []
159
+
160
+ history.each_with_index do |deps, index|
161
+ if asset = yield(deps)
162
+ cache.set(key, history.rotate!(index)) if index > 0
163
+ return asset
164
+ end
165
+ end
163
166
 
167
+ asset = yield
168
+ deps = asset[:metadata][:dependencies]
169
+ cache.set(key, history.unshift(deps).take(limit))
164
170
  asset
165
171
  end
166
172
  end