sprockets 2.2.3 → 2.12.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +135 -4
- data/bin/sprockets +8 -0
- data/lib/rake/sprocketstask.rb +24 -13
- data/lib/sprockets.rb +52 -8
- data/lib/sprockets/asset.rb +17 -8
- data/lib/sprockets/asset_attributes.rb +15 -4
- data/lib/sprockets/base.rb +155 -15
- data/lib/sprockets/bundled_asset.rb +5 -6
- data/lib/sprockets/caching.rb +39 -39
- data/lib/sprockets/closure_compressor.rb +22 -0
- data/lib/sprockets/compressing.rb +73 -0
- data/lib/sprockets/context.rb +57 -3
- data/lib/sprockets/directive_processor.rb +3 -1
- data/lib/sprockets/engines.rb +4 -4
- data/lib/sprockets/environment.rb +12 -15
- data/lib/sprockets/errors.rb +1 -0
- data/lib/sprockets/index.rb +1 -0
- data/lib/sprockets/jst_processor.rb +2 -6
- data/lib/sprockets/manifest.rb +77 -19
- data/lib/sprockets/mime.rb +5 -4
- data/lib/sprockets/{trail.rb → paths.rb} +5 -37
- data/lib/sprockets/processed_asset.rb +1 -1
- data/lib/sprockets/processing.rb +3 -77
- data/lib/sprockets/processor.rb +1 -1
- data/lib/sprockets/sass_cache_store.rb +29 -0
- data/lib/sprockets/sass_compressor.rb +27 -0
- data/lib/sprockets/sass_functions.rb +70 -0
- data/lib/sprockets/sass_importer.rb +30 -0
- data/lib/sprockets/sass_template.rb +66 -0
- data/lib/sprockets/scss_template.rb +13 -0
- data/lib/sprockets/server.rb +1 -1
- data/lib/sprockets/static_asset.rb +4 -1
- data/lib/sprockets/uglifier_compressor.rb +29 -0
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +27 -0
- metadata +88 -7
data/lib/sprockets/base.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
require 'sprockets/asset_attributes'
|
2
2
|
require 'sprockets/bundled_asset'
|
3
3
|
require 'sprockets/caching'
|
4
|
+
require 'sprockets/errors'
|
4
5
|
require 'sprockets/processed_asset'
|
5
|
-
require 'sprockets/processing'
|
6
6
|
require 'sprockets/server'
|
7
7
|
require 'sprockets/static_asset'
|
8
|
-
require '
|
8
|
+
require 'multi_json'
|
9
9
|
require 'pathname'
|
10
10
|
|
11
11
|
module Sprockets
|
12
12
|
# `Base` class for `Environment` and `Index`.
|
13
13
|
class Base
|
14
|
-
include Caching, Processing,
|
14
|
+
include Caching, Paths, Mime, Processing, Compressing, Engines, Server
|
15
15
|
|
16
16
|
# Returns a `Digest` implementation class.
|
17
17
|
#
|
18
18
|
# Defaults to `Digest::MD5`.
|
19
19
|
attr_reader :digest_class
|
20
20
|
|
21
|
-
# Assign a `Digest` implementation class. This
|
21
|
+
# Assign a `Digest` implementation class. This may be any Ruby
|
22
22
|
# `Digest::` implementation such as `Digest::MD5` or
|
23
23
|
# `Digest::SHA1`.
|
24
24
|
#
|
@@ -98,6 +98,114 @@ module Sprockets
|
|
98
98
|
@cache = cache
|
99
99
|
end
|
100
100
|
|
101
|
+
def prepend_path(path)
|
102
|
+
# Overrides the global behavior to expire the index
|
103
|
+
expire_index!
|
104
|
+
super
|
105
|
+
end
|
106
|
+
|
107
|
+
def append_path(path)
|
108
|
+
# Overrides the global behavior to expire the index
|
109
|
+
expire_index!
|
110
|
+
super
|
111
|
+
end
|
112
|
+
|
113
|
+
def clear_paths
|
114
|
+
# Overrides the global behavior to expire the index
|
115
|
+
expire_index!
|
116
|
+
super
|
117
|
+
end
|
118
|
+
|
119
|
+
# Finds the expanded real path for a given logical path by
|
120
|
+
# searching the environment's paths.
|
121
|
+
#
|
122
|
+
# resolve("application.js")
|
123
|
+
# # => "/path/to/app/javascripts/application.js.coffee"
|
124
|
+
#
|
125
|
+
# A `FileNotFound` exception is raised if the file does not exist.
|
126
|
+
def resolve(logical_path, options = {})
|
127
|
+
# If a block is given, preform an iterable search
|
128
|
+
if block_given?
|
129
|
+
args = attributes_for(logical_path).search_paths + [options]
|
130
|
+
@trail.find(*args) do |path|
|
131
|
+
pathname = Pathname.new(path)
|
132
|
+
if %w( .bower.json bower.json component.json ).include?(pathname.basename.to_s)
|
133
|
+
bower = json_decode(pathname.read)
|
134
|
+
case bower['main']
|
135
|
+
when String
|
136
|
+
yield pathname.dirname.join(bower['main'])
|
137
|
+
when Array
|
138
|
+
extname = File.extname(logical_path)
|
139
|
+
bower['main'].each do |fn|
|
140
|
+
if extname == "" || extname == File.extname(fn)
|
141
|
+
yield pathname.dirname.join(fn)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
yield pathname
|
147
|
+
end
|
148
|
+
end
|
149
|
+
else
|
150
|
+
resolve(logical_path, options) do |pathname|
|
151
|
+
return pathname
|
152
|
+
end
|
153
|
+
raise FileNotFound, "couldn't find file '#{logical_path}'"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Register a new mime type.
|
158
|
+
def register_mime_type(mime_type, ext)
|
159
|
+
# Overrides the global behavior to expire the index
|
160
|
+
expire_index!
|
161
|
+
@trail.append_extension(ext)
|
162
|
+
super
|
163
|
+
end
|
164
|
+
|
165
|
+
# Registers a new Engine `klass` for `ext`.
|
166
|
+
def register_engine(ext, klass)
|
167
|
+
# Overrides the global behavior to expire the index
|
168
|
+
expire_index!
|
169
|
+
add_engine_to_trail(ext, klass)
|
170
|
+
super
|
171
|
+
end
|
172
|
+
|
173
|
+
def register_preprocessor(mime_type, klass, &block)
|
174
|
+
# Overrides the global behavior to expire the index
|
175
|
+
expire_index!
|
176
|
+
super
|
177
|
+
end
|
178
|
+
|
179
|
+
def unregister_preprocessor(mime_type, klass)
|
180
|
+
# Overrides the global behavior to expire the index
|
181
|
+
expire_index!
|
182
|
+
super
|
183
|
+
end
|
184
|
+
|
185
|
+
def register_postprocessor(mime_type, klass, &block)
|
186
|
+
# Overrides the global behavior to expire the index
|
187
|
+
expire_index!
|
188
|
+
super
|
189
|
+
end
|
190
|
+
|
191
|
+
def unregister_postprocessor(mime_type, klass)
|
192
|
+
# Overrides the global behavior to expire the index
|
193
|
+
expire_index!
|
194
|
+
super
|
195
|
+
end
|
196
|
+
|
197
|
+
def register_bundle_processor(mime_type, klass, &block)
|
198
|
+
# Overrides the global behavior to expire the index
|
199
|
+
expire_index!
|
200
|
+
super
|
201
|
+
end
|
202
|
+
|
203
|
+
def unregister_bundle_processor(mime_type, klass)
|
204
|
+
# Overrides the global behavior to expire the index
|
205
|
+
expire_index!
|
206
|
+
super
|
207
|
+
end
|
208
|
+
|
101
209
|
# Return an `Index`. Must be implemented by the subclass.
|
102
210
|
def index
|
103
211
|
raise NotImplementedError
|
@@ -113,14 +221,14 @@ module Sprockets
|
|
113
221
|
#
|
114
222
|
# Subclasses may cache this method.
|
115
223
|
def entries(pathname)
|
116
|
-
trail.entries(pathname)
|
224
|
+
@trail.entries(pathname)
|
117
225
|
end
|
118
226
|
|
119
227
|
# Works like `File.stat`.
|
120
228
|
#
|
121
229
|
# Subclasses may cache this method.
|
122
230
|
def stat(path)
|
123
|
-
trail.stat(path)
|
231
|
+
@trail.stat(path)
|
124
232
|
end
|
125
233
|
|
126
234
|
# Read and compute digest of filename.
|
@@ -153,7 +261,7 @@ module Sprockets
|
|
153
261
|
# Find asset by logical path or expanded path.
|
154
262
|
def find_asset(path, options = {})
|
155
263
|
logical_path = path
|
156
|
-
pathname = Pathname.new(path)
|
264
|
+
pathname = Pathname.new(path).cleanpath
|
157
265
|
|
158
266
|
if pathname.absolute?
|
159
267
|
return unless stat(pathname)
|
@@ -161,6 +269,16 @@ module Sprockets
|
|
161
269
|
else
|
162
270
|
begin
|
163
271
|
pathname = resolve(logical_path)
|
272
|
+
|
273
|
+
# If logical path is missing a mime type extension, append
|
274
|
+
# the absolute path extname so it has one.
|
275
|
+
#
|
276
|
+
# Ensures some consistency between finding "foo/bar" vs
|
277
|
+
# "foo/bar.js".
|
278
|
+
if File.extname(logical_path) == ""
|
279
|
+
expanded_logical_path = attributes_for(pathname).logical_path
|
280
|
+
logical_path += File.extname(expanded_logical_path)
|
281
|
+
end
|
164
282
|
rescue FileNotFound
|
165
283
|
return nil
|
166
284
|
end
|
@@ -210,13 +328,20 @@ module Sprockets
|
|
210
328
|
nil
|
211
329
|
end
|
212
330
|
|
213
|
-
def each_logical_path(*args)
|
331
|
+
def each_logical_path(*args, &block)
|
214
332
|
return to_enum(__method__, *args) unless block_given?
|
215
333
|
filters = args.flatten
|
216
334
|
files = {}
|
217
335
|
each_file do |filename|
|
218
336
|
if logical_path = logical_path_for_filename(filename, filters)
|
219
|
-
|
337
|
+
unless files[logical_path]
|
338
|
+
if block.arity == 2
|
339
|
+
yield logical_path, filename.to_s
|
340
|
+
else
|
341
|
+
yield logical_path
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
220
345
|
files[logical_path] = true
|
221
346
|
end
|
222
347
|
end
|
@@ -275,14 +400,14 @@ module Sprockets
|
|
275
400
|
def logical_path_for_filename(filename, filters)
|
276
401
|
logical_path = attributes_for(filename).logical_path.to_s
|
277
402
|
|
278
|
-
if matches_filter(filters, logical_path)
|
403
|
+
if matches_filter(filters, logical_path, filename)
|
279
404
|
return logical_path
|
280
405
|
end
|
281
406
|
|
282
407
|
# If filename is an index file, retest with alias
|
283
408
|
if File.basename(logical_path)[/[^\.]+/, 0] == 'index'
|
284
409
|
path = logical_path.sub(/\/index\./, '.')
|
285
|
-
if matches_filter(filters, path)
|
410
|
+
if matches_filter(filters, path, filename)
|
286
411
|
return path
|
287
412
|
end
|
288
413
|
end
|
@@ -290,18 +415,33 @@ module Sprockets
|
|
290
415
|
nil
|
291
416
|
end
|
292
417
|
|
293
|
-
def matches_filter(filters, filename)
|
418
|
+
def matches_filter(filters, logical_path, filename)
|
294
419
|
return true if filters.empty?
|
295
420
|
|
296
421
|
filters.any? do |filter|
|
297
422
|
if filter.is_a?(Regexp)
|
298
|
-
filter.match(
|
423
|
+
filter.match(logical_path)
|
299
424
|
elsif filter.respond_to?(:call)
|
300
|
-
filter.
|
425
|
+
if filter.arity == 1
|
426
|
+
filter.call(logical_path)
|
427
|
+
else
|
428
|
+
filter.call(logical_path, filename.to_s)
|
429
|
+
end
|
301
430
|
else
|
302
|
-
File.fnmatch(filter.to_s,
|
431
|
+
File.fnmatch(filter.to_s, logical_path)
|
303
432
|
end
|
304
433
|
end
|
305
434
|
end
|
435
|
+
|
436
|
+
# Feature detect newer MultiJson API
|
437
|
+
if MultiJson.respond_to?(:dump)
|
438
|
+
def json_decode(obj)
|
439
|
+
MultiJson.load(obj)
|
440
|
+
end
|
441
|
+
else
|
442
|
+
def json_decode(obj)
|
443
|
+
MultiJson.decode(obj)
|
444
|
+
end
|
445
|
+
end
|
306
446
|
end
|
307
447
|
end
|
@@ -13,20 +13,19 @@ module Sprockets
|
|
13
13
|
def initialize(environment, logical_path, pathname)
|
14
14
|
super(environment, logical_path, pathname)
|
15
15
|
|
16
|
-
@processed_asset
|
17
|
-
@required_assets
|
18
|
-
|
19
|
-
@source = ""
|
16
|
+
@processed_asset = environment.find_asset(pathname, :bundle => false)
|
17
|
+
@required_assets = @processed_asset.required_assets
|
18
|
+
@dependency_paths = @processed_asset.dependency_paths
|
20
19
|
|
21
20
|
# Explode Asset into parts and gather the dependency bodies
|
22
|
-
to_a.
|
21
|
+
@source = to_a.map { |dependency| dependency.to_s }.join
|
23
22
|
|
24
23
|
# Run bundle processors on concatenated source
|
25
24
|
context = environment.context_class.new(environment, logical_path, pathname)
|
26
25
|
@source = context.evaluate(pathname, :data => @source,
|
27
26
|
:processors => environment.bundle_processors(content_type))
|
28
27
|
|
29
|
-
@mtime = to_a.map(&:mtime).max
|
28
|
+
@mtime = (to_a + @dependency_paths).map(&:mtime).max
|
30
29
|
@length = Rack::Utils.bytesize(source)
|
31
30
|
@digest = environment.digest.update(source).hexdigest
|
32
31
|
end
|
data/lib/sprockets/caching.rb
CHANGED
@@ -2,6 +2,45 @@ module Sprockets
|
|
2
2
|
# `Caching` is an internal mixin whose public methods are exposed on
|
3
3
|
# the `Environment` and `Index` classes.
|
4
4
|
module Caching
|
5
|
+
# Low level cache getter for `key`. Checks a number of supported
|
6
|
+
# cache interfaces.
|
7
|
+
def cache_get(key)
|
8
|
+
# `Cache#get(key)` for Memcache
|
9
|
+
if cache.respond_to?(:get)
|
10
|
+
cache.get(key)
|
11
|
+
|
12
|
+
# `Cache#[key]` so `Hash` can be used
|
13
|
+
elsif cache.respond_to?(:[])
|
14
|
+
cache[key]
|
15
|
+
|
16
|
+
# `Cache#read(key)` for `ActiveSupport::Cache` support
|
17
|
+
elsif cache.respond_to?(:read)
|
18
|
+
cache.read(key)
|
19
|
+
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Low level cache setter for `key`. Checks a number of supported
|
26
|
+
# cache interfaces.
|
27
|
+
def cache_set(key, value)
|
28
|
+
# `Cache#set(key, value)` for Memcache
|
29
|
+
if cache.respond_to?(:set)
|
30
|
+
cache.set(key, value)
|
31
|
+
|
32
|
+
# `Cache#[key]=value` so `Hash` can be used
|
33
|
+
elsif cache.respond_to?(:[]=)
|
34
|
+
cache[key] = value
|
35
|
+
|
36
|
+
# `Cache#write(key, value)` for `ActiveSupport::Cache` support
|
37
|
+
elsif cache.respond_to?(:write)
|
38
|
+
cache.write(key, value)
|
39
|
+
end
|
40
|
+
|
41
|
+
value
|
42
|
+
end
|
43
|
+
|
5
44
|
protected
|
6
45
|
# Cache helper method. Takes a `path` argument which maybe a
|
7
46
|
# logical path or fully expanded path. The `&block` is passed
|
@@ -53,44 +92,5 @@ module Sprockets
|
|
53
92
|
cache_set(expand_cache_key(key), hash)
|
54
93
|
hash
|
55
94
|
end
|
56
|
-
|
57
|
-
# Low level cache getter for `key`. Checks a number of supported
|
58
|
-
# cache interfaces.
|
59
|
-
def cache_get(key)
|
60
|
-
# `Cache#get(key)` for Memcache
|
61
|
-
if cache.respond_to?(:get)
|
62
|
-
cache.get(key)
|
63
|
-
|
64
|
-
# `Cache#[key]` so `Hash` can be used
|
65
|
-
elsif cache.respond_to?(:[])
|
66
|
-
cache[key]
|
67
|
-
|
68
|
-
# `Cache#read(key)` for `ActiveSupport::Cache` support
|
69
|
-
elsif cache.respond_to?(:read)
|
70
|
-
cache.read(key)
|
71
|
-
|
72
|
-
else
|
73
|
-
nil
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Low level cache setter for `key`. Checks a number of supported
|
78
|
-
# cache interfaces.
|
79
|
-
def cache_set(key, value)
|
80
|
-
# `Cache#set(key, value)` for Memcache
|
81
|
-
if cache.respond_to?(:set)
|
82
|
-
cache.set(key, value)
|
83
|
-
|
84
|
-
# `Cache#[key]=value` so `Hash` can be used
|
85
|
-
elsif cache.respond_to?(:[]=)
|
86
|
-
cache[key] = value
|
87
|
-
|
88
|
-
# `Cache#write(key, value)` for `ActiveSupport::Cache` support
|
89
|
-
elsif cache.respond_to?(:write)
|
90
|
-
cache.write(key, value)
|
91
|
-
end
|
92
|
-
|
93
|
-
value
|
94
|
-
end
|
95
95
|
end
|
96
96
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
class ClosureCompressor < Tilt::Template
|
5
|
+
self.default_mime_type = 'application/javascript'
|
6
|
+
|
7
|
+
def self.engine_initialized?
|
8
|
+
defined?(::Closure::Compiler)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_engine
|
12
|
+
require_template_library 'closure-compiler'
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepare
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(context, locals, &block)
|
19
|
+
Closure::Compiler.new.compile(data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Sprockets
|
2
|
+
# `Compressing` is an internal mixin whose public methods are exposed on
|
3
|
+
# the `Environment` and `Index` classes.
|
4
|
+
module Compressing
|
5
|
+
def compressors
|
6
|
+
deep_copy_hash(@compressors)
|
7
|
+
end
|
8
|
+
|
9
|
+
def register_compressor(mime_type, sym, klass)
|
10
|
+
@compressors[mime_type][sym] = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return CSS compressor or nil if none is set
|
14
|
+
def css_compressor
|
15
|
+
@css_compressor if defined? @css_compressor
|
16
|
+
end
|
17
|
+
|
18
|
+
# Assign a compressor to run on `text/css` assets.
|
19
|
+
#
|
20
|
+
# The compressor object must respond to `compress`.
|
21
|
+
def css_compressor=(compressor)
|
22
|
+
unregister_bundle_processor 'text/css', css_compressor if css_compressor
|
23
|
+
@css_compressor = nil
|
24
|
+
return unless compressor
|
25
|
+
|
26
|
+
if compressor.is_a?(Symbol)
|
27
|
+
compressor = compressors['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
|
28
|
+
end
|
29
|
+
|
30
|
+
if compressor.respond_to?(:compress)
|
31
|
+
klass = Class.new(Processor) do
|
32
|
+
@name = "css_compressor"
|
33
|
+
@processor = proc { |context, data| compressor.compress(data) }
|
34
|
+
end
|
35
|
+
@css_compressor = :css_compressor
|
36
|
+
else
|
37
|
+
@css_compressor = klass = compressor
|
38
|
+
end
|
39
|
+
|
40
|
+
register_bundle_processor 'text/css', klass
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return JS compressor or nil if none is set
|
44
|
+
def js_compressor
|
45
|
+
@js_compressor if defined? @js_compressor
|
46
|
+
end
|
47
|
+
|
48
|
+
# Assign a compressor to run on `application/javascript` assets.
|
49
|
+
#
|
50
|
+
# The compressor object must respond to `compress`.
|
51
|
+
def js_compressor=(compressor)
|
52
|
+
unregister_bundle_processor 'application/javascript', js_compressor if js_compressor
|
53
|
+
@js_compressor = nil
|
54
|
+
return unless compressor
|
55
|
+
|
56
|
+
if compressor.is_a?(Symbol)
|
57
|
+
compressor = compressors['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
|
58
|
+
end
|
59
|
+
|
60
|
+
if compressor.respond_to?(:compress)
|
61
|
+
klass = Class.new(Processor) do
|
62
|
+
@name = "js_compressor"
|
63
|
+
@processor = proc { |context, data| compressor.compress(data) }
|
64
|
+
end
|
65
|
+
@js_compressor = :js_compressor
|
66
|
+
else
|
67
|
+
@js_compressor = klass = compressor
|
68
|
+
end
|
69
|
+
|
70
|
+
register_bundle_processor 'application/javascript', klass
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|