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
@@ -0,0 +1,73 @@
1
+ require 'sprockets/digest_utils'
2
+ require 'sprockets/path_digest_utils'
3
+ require 'sprockets/uri_utils'
4
+
5
+ module Sprockets
6
+ # `Dependencies` is an internal mixin whose public methods are exposed on the
7
+ # `Environment` and `CachedEnvironment` classes.
8
+ module Dependencies
9
+ include DigestUtils, PathDigestUtils, URIUtils
10
+
11
+ # Public: Mapping dependency schemes to resolver functions.
12
+ #
13
+ # key - String scheme
14
+ # value - Proc.call(Environment, String)
15
+ #
16
+ # Returns Hash.
17
+ def dependency_resolvers
18
+ config[:dependency_resolvers]
19
+ end
20
+
21
+ # Public: Default set of dependency URIs for assets.
22
+ #
23
+ # Returns Set of String URIs.
24
+ def dependencies
25
+ config[:dependencies]
26
+ end
27
+
28
+ # Public: Register new dependency URI resolver.
29
+ #
30
+ # scheme - String scheme
31
+ # block -
32
+ # environment - Environment
33
+ # uri - String dependency URI
34
+ #
35
+ # Returns nothing.
36
+ def register_dependency_resolver(scheme, &block)
37
+ self.config = hash_reassoc(config, :dependency_resolvers) do |hash|
38
+ hash.merge(scheme => block)
39
+ end
40
+ end
41
+
42
+ # Public: Add environmental dependency inheirted by all assets.
43
+ #
44
+ # uri - String dependency URI
45
+ #
46
+ # Returns nothing.
47
+ def add_dependency(uri)
48
+ self.config = hash_reassoc(config, :dependencies) do |set|
49
+ set + Set.new([uri])
50
+ end
51
+ end
52
+ alias_method :depend_on, :add_dependency
53
+
54
+ # Internal: Resolve set of dependency URIs.
55
+ #
56
+ # Returns Array of resolved Objects.
57
+ def resolve_dependencies(uris)
58
+ uris.map { |uri| resolve_dependency(uri) }
59
+ end
60
+
61
+ # Internal: Resolve dependency URIs.
62
+ #
63
+ # Returns resolved Object.
64
+ def resolve_dependency(str)
65
+ scheme = str[/([^:]+)/, 1]
66
+ if resolver = dependency_resolvers[scheme]
67
+ resolver.call(self, str)
68
+ else
69
+ raise TypeError, "unknown cache scheme: #{scheme}"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,8 +1,11 @@
1
1
  require 'digest/md5'
2
2
  require 'digest/sha1'
3
3
  require 'digest/sha2'
4
+ require 'set'
4
5
 
5
6
  module Sprockets
7
+ # Internal: Hash functions and digest related utilities. Mixed into
8
+ # Environment.
6
9
  module DigestUtils
7
10
  extend self
8
11
 
@@ -33,6 +36,9 @@ module Sprockets
33
36
 
34
37
  # Internal: Generate a hexdigest for a nested JSON serializable object.
35
38
  #
39
+ # This is used for generating cache keys, so its pretty important its
40
+ # wicked fast. Microbenchmarks away!
41
+ #
36
42
  # obj - A JSON serializable object.
37
43
  #
38
44
  # Returns a String digest of the object.
@@ -111,7 +117,7 @@ module Sprockets
111
117
  # Internal: Maps digest class to the named information hash algorithm name.
112
118
  #
113
119
  # http://www.iana.org/assignments/named-information/named-information.xhtml
114
- NI_HASH_ALGORIHMS = {
120
+ NI_HASH_ALGORITHMS = {
115
121
  Digest::SHA256 => 'sha-256'.freeze,
116
122
  Digest::SHA384 => 'sha-384'.freeze,
117
123
  Digest::SHA512 => 'sha-512'.freeze
@@ -137,7 +143,7 @@ module Sprockets
137
143
  raise TypeError, "unknown digest: #{digest.inspect}"
138
144
  end
139
145
 
140
- if hash_name = NI_HASH_ALGORIHMS[digest_class]
146
+ if hash_name = NI_HASH_ALGORITHMS[digest_class]
141
147
  uri = "ni:///#{hash_name};#{pack_urlsafe_base64digest(digest)}"
142
148
  uri << "?ct=#{content_type}" if content_type
143
149
  uri
@@ -36,24 +36,6 @@ module Sprockets
36
36
  class DirectiveProcessor
37
37
  VERSION = '1'
38
38
 
39
- # Directives will only be picked up if they are in the header
40
- # of the source file. C style (/* */), JavaScript (//), and
41
- # Ruby (#) comments are supported.
42
- #
43
- # Directives in comments after the first non-whitespace line
44
- # of code will not be processed.
45
- #
46
- HEADER_PATTERN = /
47
- \A (
48
- (?m:\s*) (
49
- (\/\* (?m:.*?) \*\/) |
50
- (\#\#\# (?m:.*?) \#\#\#) |
51
- (\/\/ .* \n?)+ |
52
- (\# .* \n?)+
53
- )
54
- )+
55
- /x
56
-
57
39
  # Directives are denoted by a `=` followed by the name, then
58
40
  # argument list.
59
41
  #
@@ -67,40 +49,69 @@ module Sprockets
67
49
  ^ \W* = \s* (\w+.*?) (\*\/)? $
68
50
  /x
69
51
 
52
+ def self.instance
53
+ @instance ||= new(
54
+ # Deprecated: Default to C and Ruby comment styles
55
+ comments: ["//", ["/*", "*/"]] + ["#", ["###", "###"]]
56
+ )
57
+ end
58
+
70
59
  def self.call(input)
71
- new.call(input)
60
+ instance.call(input)
61
+ end
62
+
63
+ def initialize(options = {})
64
+ @header_pattern = compile_header_pattern(Array(options[:comments]))
72
65
  end
73
66
 
74
67
  def call(input)
68
+ dup._call(input)
69
+ end
70
+
71
+ def _call(input)
75
72
  @environment = input[:environment]
76
73
  @uri = input[:uri]
77
- @load_path = input[:load_path]
78
74
  @filename = input[:filename]
79
75
  @dirname = File.dirname(@filename)
80
76
  @content_type = input[:content_type]
77
+ @required = Set.new(input[:metadata][:required])
78
+ @stubbed = Set.new(input[:metadata][:stubbed])
79
+ @links = Set.new(input[:metadata][:links])
80
+ @dependencies = Set.new(input[:metadata][:dependencies])
81
81
 
82
- data = input[:data]
83
- result = process_source(data)
84
-
85
- data, directives = result.values_at(:data, :directives)
86
-
87
- @required = Set.new(input[:metadata][:required])
88
- @stubbed = Set.new(input[:metadata][:stubbed])
89
- @links = Set.new(input[:metadata][:links])
90
- @dependency_paths = Set.new(input[:metadata][:dependency_paths])
91
-
82
+ data, directives = process_source(input[:data])
92
83
  process_directives(directives)
93
84
 
94
85
  { data: data,
95
86
  required: @required,
96
87
  stubbed: @stubbed,
97
88
  links: @links,
98
- dependency_paths: @dependency_paths }
89
+ dependencies: @dependencies }
99
90
  end
100
91
 
101
92
  protected
93
+ # Directives will only be picked up if they are in the header
94
+ # of the source file. C style (/* */), JavaScript (//), and
95
+ # Ruby (#) comments are supported.
96
+ #
97
+ # Directives in comments after the first non-whitespace line
98
+ # of code will not be processed.
99
+ def compile_header_pattern(comments)
100
+ re = comments.map { |c|
101
+ case c
102
+ when String
103
+ "(?:#{Regexp.escape(c)}.*\\n?)+"
104
+ when Array
105
+ "(?:#{Regexp.escape(c[0])}(?m:.*?)#{Regexp.escape(c[1])})"
106
+ else
107
+ raise TypeError, "unknown comment type: #{c.class}"
108
+ end
109
+ }.join("|")
110
+ Regexp.compile("\\A(?:(?m:\\s*)(?:#{re}))+")
111
+ end
112
+
102
113
  def process_source(source)
103
- header = source[HEADER_PATTERN, 0] || ""
114
+ header = source[@header_pattern, 0] || ""
104
115
  body = $' || source
105
116
 
106
117
  header, directives = extract_directives(header)
@@ -112,7 +123,7 @@ module Sprockets
112
123
  # Ensure body ends in a new line
113
124
  data << "\n" if data.length > 0 && data[-1] != "\n"
114
125
 
115
- { data: data, directives: directives }
126
+ return data, directives
116
127
  end
117
128
 
118
129
  # Returns an Array of directive structures. Each structure
@@ -193,7 +204,7 @@ module Sprockets
193
204
  # //= require "./bar"
194
205
  #
195
206
  def process_require_directive(path)
196
- @required << locate(path, accept: @content_type, bundle: false)
207
+ @required << resolve(path, accept: @content_type, bundle: false)
197
208
  end
198
209
 
199
210
  # `require_self` causes the body of the current file to be inserted
@@ -220,28 +231,8 @@ module Sprockets
220
231
  # //= require_directory "./javascripts"
221
232
  #
222
233
  def process_require_directory_directive(path = ".")
223
- if @environment.relative_path?(path)
224
- root = expand_relative_path(path)
225
-
226
- unless (stats = @environment.stat(root)) && stats.directory?
227
- raise ArgumentError, "require_directory argument must be a directory"
228
- end
229
-
230
- @dependency_paths << root
231
-
232
- @environment.stat_directory(root).each do |subpath, stat|
233
- if subpath == @filename
234
- next
235
- elsif stat.directory?
236
- next
237
- elsif uri = @environment.locate(subpath, accept: @content_type, bundle: false)
238
- @required << uri
239
- end
240
- end
241
- else
242
- # The path must be relative and start with a `./`.
243
- raise ArgumentError, "require_directory argument must be a relative path"
244
- end
234
+ path = expand_relative_dirname(:require_directory, path)
235
+ require_paths(*@environment.stat_directory_with_dependencies(path))
245
236
  end
246
237
 
247
238
  # `require_tree` requires all the nested files in a directory.
@@ -250,28 +241,8 @@ module Sprockets
250
241
  # //= require_tree "./public"
251
242
  #
252
243
  def process_require_tree_directive(path = ".")
253
- if @environment.relative_path?(path)
254
- root = expand_relative_path(path)
255
-
256
- unless (stats = @environment.stat(root)) && stats.directory?
257
- raise ArgumentError, "require_tree argument must be a directory"
258
- end
259
-
260
- @dependency_paths << root
261
-
262
- @environment.stat_sorted_tree(root).each do |subpath, stat|
263
- if subpath == @filename
264
- next
265
- elsif stat.directory?
266
- @dependency_paths << subpath
267
- elsif uri = @environment.locate(subpath, accept: @content_type, bundle: false)
268
- @required << uri
269
- end
270
- end
271
- else
272
- # The path must be relative and start with a `./`.
273
- raise ArgumentError, "require_tree argument must be a relative path"
274
- end
244
+ path = expand_relative_dirname(:require_tree, path)
245
+ require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
275
246
  end
276
247
 
277
248
  # Allows you to state a dependency on a file without
@@ -287,7 +258,7 @@ module Sprockets
287
258
  # //= depend_on "foo.png"
288
259
  #
289
260
  def process_depend_on_directive(path)
290
- @dependency_paths << resolve(path)
261
+ resolve(path)
291
262
  end
292
263
 
293
264
  # Allows you to state a dependency on an asset without including
@@ -302,9 +273,7 @@ module Sprockets
302
273
  # //= depend_on_asset "bar.js"
303
274
  #
304
275
  def process_depend_on_asset_directive(path)
305
- if asset = @environment.load(locate(path))
306
- @dependency_paths.merge(asset.metadata[:dependency_paths])
307
- end
276
+ load(resolve(path))
308
277
  end
309
278
 
310
279
  # Allows dependency to be excluded from the asset bundle.
@@ -316,7 +285,7 @@ module Sprockets
316
285
  # //= stub "jquery"
317
286
  #
318
287
  def process_stub_directive(path)
319
- @stubbed << locate(path, accept: @content_type, bundle: false)
288
+ @stubbed << resolve(path, accept: @content_type, bundle: false)
320
289
  end
321
290
 
322
291
  # Declares a linked dependency on the target asset.
@@ -328,10 +297,7 @@ module Sprockets
328
297
  # /*= link "logo.png" */
329
298
  #
330
299
  def process_link_directive(path)
331
- if asset = @environment.load(locate(path))
332
- @dependency_paths.merge(asset.metadata[:dependency_paths])
333
- @links << asset.uri
334
- end
300
+ @links << load(resolve(path)).uri
335
301
  end
336
302
 
337
303
  # `link_directory` links all the files inside a single
@@ -340,31 +306,15 @@ module Sprockets
340
306
  #
341
307
  # //= link_directory "./fonts"
342
308
  #
343
- def process_link_directory_directive(path = ".")
344
- if @environment.relative_path?(path)
345
- root = expand_relative_path(path)
346
-
347
- unless (stats = @environment.stat(root)) && stats.directory?
348
- raise ArgumentError, "link_directory argument must be a directory"
349
- end
350
-
351
- @dependency_paths << root
352
-
353
- @environment.stat_directory(root).each do |subpath, stat|
354
- if subpath == @filename
355
- next
356
- elsif stat.directory?
357
- next
358
- elsif uri = @environment.locate(subpath)
359
- asset = @environment.load(uri)
360
- @dependency_paths.merge(asset.metadata[:dependency_paths])
361
- @links << asset.uri
362
- end
363
- end
364
- else
365
- # The path must be relative and start with a `./`.
366
- raise ArgumentError, "link_directory argument must be a relative path"
367
- end
309
+ # Use caution when linking against JS or CSS assets. Include an explicit
310
+ # extension or content type in these cases
311
+ #
312
+ # //= link_directory "./scripts" .js
313
+ #
314
+ def process_link_directory_directive(path = ".", accept = nil)
315
+ path = expand_relative_dirname(:link_directory, path)
316
+ accept = expand_accept_shorthand(accept)
317
+ link_paths(*@environment.stat_directory_with_dependencies(path), accept)
368
318
  end
369
319
 
370
320
  # `link_tree` links all the nested files in a directory.
@@ -372,76 +322,83 @@ module Sprockets
372
322
  #
373
323
  # //= link_tree "./images"
374
324
  #
375
- def process_link_tree_directive(path = ".")
376
- if @environment.relative_path?(path)
377
- root = expand_relative_path(path)
378
-
379
- unless (stats = @environment.stat(root)) && stats.directory?
380
- raise ArgumentError, "link_tree argument must be a directory"
381
- end
325
+ # Use caution when linking against JS or CSS assets. Include an explicit
326
+ # extension or content type in these cases
327
+ #
328
+ # //= link_tree "./styles" .css
329
+ #
330
+ def process_link_tree_directive(path = ".", accept = nil)
331
+ path = expand_relative_dirname(:link_tree, path)
332
+ accept = expand_accept_shorthand(accept)
333
+ link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept)
334
+ end
382
335
 
383
- @dependency_paths << root
384
-
385
- @environment.stat_sorted_tree(root).each do |subpath, stat|
386
- if subpath == @filename
387
- next
388
- elsif stat.directory?
389
- @dependency_paths << subpath
390
- elsif uri = @environment.locate(subpath)
391
- asset = @environment.load(uri)
392
- @dependency_paths.merge(asset.metadata[:dependency_paths])
393
- @links << asset.uri
394
- end
395
- end
336
+ private
337
+ def expand_accept_shorthand(accept)
338
+ if accept.nil?
339
+ nil
340
+ elsif accept.include?("/")
341
+ accept
342
+ elsif accept.start_with?(".")
343
+ @environment.mime_exts[accept]
396
344
  else
397
- # The path must be relative and start with a `./`.
398
- raise ArgumentError, "link_tree argument must be a relative path"
345
+ @environment.mime_exts[".#{accept}"]
399
346
  end
400
347
  end
401
348
 
402
- private
403
- def expand_relative_path(path)
404
- File.expand_path(path, @dirname)
349
+ def require_paths(paths, deps)
350
+ resolve_paths(paths, deps, accept: @content_type, bundle: false) do |uri|
351
+ @required << uri
352
+ end
405
353
  end
406
354
 
407
- def locate(path, options = {})
408
- _resolve(:locate, path, options)
355
+ def link_paths(paths, deps, accept)
356
+ resolve_paths(paths, deps, accept: accept) do |uri|
357
+ @links << load(uri).uri
358
+ end
409
359
  end
410
360
 
411
- def resolve(path, options = {})
412
- _resolve(:resolve, path, options)
361
+ def resolve_paths(paths, deps, options = {})
362
+ @dependencies.merge(deps)
363
+ paths.each do |subpath, stat|
364
+ next if subpath == @filename || stat.directory?
365
+ uri, deps = @environment.resolve(subpath, options.merge(compat: false))
366
+ @dependencies.merge(deps)
367
+ yield uri if uri
368
+ end
413
369
  end
414
370
 
415
- # TODO: Cleanup relative resolver logic shared between context.
416
- def _resolve(method, path, options = {})
417
- if @environment.absolute_path?(path)
418
- raise FileOutsidePaths, "can't require absolute file: #{path}"
419
- elsif @environment.relative_path?(path)
420
- path = expand_relative_path(path)
421
- if logical_path = @environment.split_subpath(@load_path, path)
422
- if filename = @environment.send(method, logical_path, options.merge(load_paths: [@load_path]))
423
- filename
424
- else
425
- accept = options[:accept]
426
- message = "couldn't find file '#{logical_path}' under '#{@load_path}'"
427
- message << " with type '#{accept}'" if accept
428
- raise FileNotFound, message
429
- end
371
+ def expand_relative_dirname(directive, path)
372
+ if @environment.relative_path?(path)
373
+ path = File.expand_path(path, @dirname)
374
+ stat = @environment.stat(path)
375
+
376
+ if stat && stat.directory?
377
+ path
430
378
  else
431
- raise FileOutsidePaths, "#{path} isn't under path: #{@load_path}"
379
+ raise ArgumentError, "#{directive} argument must be a directory"
432
380
  end
433
381
  else
434
- filename = @environment.send(method, path, options)
382
+ # The path must be relative and start with a `./`.
383
+ raise ArgumentError, "#{directive} argument must be a relative path"
435
384
  end
385
+ end
436
386
 
437
- if filename
438
- filename
439
- else
440
- accept = options[:accept]
441
- message = "couldn't find file '#{path}'"
442
- message << " with type '#{accept}'" if accept
443
- raise FileNotFound, message
387
+ def load(uri)
388
+ asset = @environment.load(uri)
389
+ @dependencies.merge(asset.metadata[:dependencies])
390
+ asset
391
+ end
392
+
393
+ def resolve(path, options = {})
394
+ # Prevent absolute paths in directives
395
+ if @environment.absolute_path?(path)
396
+ raise FileOutsidePaths, "can't require absolute file: #{path}"
444
397
  end
398
+
399
+ uri, deps = @environment.resolve!(path, options.merge(base_path: @dirname))
400
+ @dependencies.merge(deps)
401
+ uri
445
402
  end
446
403
  end
447
404
  end