sprockets 2.2.3 → 2.12.5
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.
- 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
|