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

Sign up to get free protection for your applications and to get access to all the features.
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