sprockets 2.3.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sprockets might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/LICENSE +2 -2
- data/README.md +332 -115
- data/bin/sprockets +8 -0
- data/lib/rake/sprocketstask.rb +25 -13
- data/lib/sprockets/asset.rb +143 -205
- data/lib/sprockets/autoload/closure.rb +7 -0
- data/lib/sprockets/autoload/coffee_script.rb +7 -0
- data/lib/sprockets/autoload/eco.rb +7 -0
- data/lib/sprockets/autoload/ejs.rb +7 -0
- data/lib/sprockets/autoload/sass.rb +7 -0
- data/lib/sprockets/autoload/uglifier.rb +7 -0
- data/lib/sprockets/autoload/yui.rb +7 -0
- data/lib/sprockets/autoload.rb +11 -0
- data/lib/sprockets/base.rb +49 -257
- data/lib/sprockets/bower.rb +58 -0
- data/lib/sprockets/bundle.rb +65 -0
- data/lib/sprockets/cache/file_store.rb +165 -14
- data/lib/sprockets/cache/memory_store.rb +66 -0
- data/lib/sprockets/cache/null_store.rb +46 -0
- data/lib/sprockets/cache.rb +234 -0
- data/lib/sprockets/cached_environment.rb +69 -0
- data/lib/sprockets/closure_compressor.rb +53 -0
- data/lib/sprockets/coffee_script_processor.rb +25 -0
- data/lib/sprockets/coffee_script_template.rb +6 -0
- data/lib/sprockets/compressing.rb +74 -0
- data/lib/sprockets/configuration.rb +83 -0
- data/lib/sprockets/context.rb +125 -131
- data/lib/sprockets/dependencies.rb +73 -0
- data/lib/sprockets/digest_utils.rb +156 -0
- data/lib/sprockets/directive_processor.rb +209 -211
- data/lib/sprockets/eco_processor.rb +32 -0
- data/lib/sprockets/eco_template.rb +3 -35
- data/lib/sprockets/ejs_processor.rb +31 -0
- data/lib/sprockets/ejs_template.rb +3 -34
- data/lib/sprockets/encoding_utils.rb +258 -0
- data/lib/sprockets/engines.rb +45 -38
- data/lib/sprockets/environment.rb +17 -67
- data/lib/sprockets/erb_processor.rb +30 -0
- data/lib/sprockets/erb_template.rb +6 -0
- data/lib/sprockets/errors.rb +6 -13
- data/lib/sprockets/file_reader.rb +15 -0
- data/lib/sprockets/http_utils.rb +115 -0
- data/lib/sprockets/jst_processor.rb +35 -19
- data/lib/sprockets/legacy.rb +314 -0
- data/lib/sprockets/legacy_proc_processor.rb +35 -0
- data/lib/sprockets/legacy_tilt_processor.rb +29 -0
- data/lib/sprockets/loader.rb +176 -0
- data/lib/sprockets/manifest.rb +179 -98
- data/lib/sprockets/manifest_utils.rb +45 -0
- data/lib/sprockets/mime.rb +114 -32
- data/lib/sprockets/path_dependency_utils.rb +85 -0
- data/lib/sprockets/path_digest_utils.rb +47 -0
- data/lib/sprockets/path_utils.rb +282 -0
- data/lib/sprockets/paths.rb +81 -0
- data/lib/sprockets/processing.rb +157 -189
- data/lib/sprockets/processor_utils.rb +103 -0
- data/lib/sprockets/resolve.rb +208 -0
- data/lib/sprockets/sass_cache_store.rb +19 -15
- data/lib/sprockets/sass_compressor.rb +59 -0
- data/lib/sprockets/sass_functions.rb +2 -0
- data/lib/sprockets/sass_importer.rb +2 -29
- data/lib/sprockets/sass_processor.rb +285 -0
- data/lib/sprockets/sass_template.rb +4 -44
- data/lib/sprockets/server.rb +109 -84
- data/lib/sprockets/transformers.rb +145 -0
- data/lib/sprockets/uglifier_compressor.rb +63 -0
- data/lib/sprockets/uri_utils.rb +190 -0
- data/lib/sprockets/utils.rb +193 -44
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +65 -0
- data/lib/sprockets.rb +144 -53
- metadata +248 -238
- data/lib/sprockets/asset_attributes.rb +0 -126
- data/lib/sprockets/bundled_asset.rb +0 -79
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/index.rb +0 -99
- data/lib/sprockets/processed_asset.rb +0 -152
- data/lib/sprockets/processor.rb +0 -32
- data/lib/sprockets/safety_colons.rb +0 -28
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -57
- data/lib/sprockets/trail.rb +0 -90
data/lib/sprockets/server.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'time'
|
2
|
-
require '
|
2
|
+
require 'rack/utils'
|
3
3
|
|
4
4
|
module Sprockets
|
5
5
|
# `Server` is a concern mixed into `Environment` and
|
6
|
-
# `
|
6
|
+
# `CachedEnvironment` that provides a Rack compatible `call`
|
7
7
|
# interface and url generation helpers.
|
8
8
|
module Server
|
9
9
|
# `call` implements the Rack 1.x specification which accepts an
|
@@ -23,59 +23,84 @@ module Sprockets
|
|
23
23
|
start_time = Time.now.to_f
|
24
24
|
time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
|
25
25
|
|
26
|
-
|
26
|
+
if env['REQUEST_METHOD'] != 'GET'
|
27
|
+
return method_not_allowed_response
|
28
|
+
end
|
27
29
|
|
28
|
-
|
29
|
-
env['rack.session.options'] ||= {}
|
30
|
-
env['rack.session.options'][:defer] = true
|
31
|
-
env['rack.session.options'][:skip] = true
|
30
|
+
msg = "Served asset #{env['PATH_INFO']} -"
|
32
31
|
|
33
32
|
# Extract the path from everything after the leading slash
|
34
|
-
path = unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
|
35
|
-
|
36
|
-
# URLs containing a `".."` are rejected for security reasons.
|
37
|
-
if forbidden_request?(path)
|
38
|
-
return forbidden_response
|
39
|
-
end
|
33
|
+
path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
|
40
34
|
|
41
35
|
# Strip fingerprint
|
42
36
|
if fingerprint = path_fingerprint(path)
|
43
37
|
path = path.sub("-#{fingerprint}", '')
|
44
38
|
end
|
45
39
|
|
40
|
+
# URLs containing a `".."` are rejected for security reasons.
|
41
|
+
if forbidden_request?(path)
|
42
|
+
return forbidden_response
|
43
|
+
end
|
44
|
+
|
46
45
|
# Look up the asset.
|
47
|
-
|
46
|
+
options = {}
|
47
|
+
options[:pipeline] = :self if body_only?(env)
|
48
48
|
|
49
|
-
|
50
|
-
if asset.nil?
|
51
|
-
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
|
49
|
+
asset = find_asset(path, options)
|
52
50
|
|
53
|
-
|
54
|
-
|
51
|
+
# 2.x/3.x compatibility hack. Just ignore fingerprints on ?body=1 requests.
|
52
|
+
# 3.x/4.x prefers strong validation of fingerprint to body contents, but
|
53
|
+
# 2.x just ignored it.
|
54
|
+
if asset && parse_asset_uri(asset.uri)[1][:pipeline] == "self"
|
55
|
+
fingerprint = nil
|
56
|
+
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
58
|
+
if fingerprint
|
59
|
+
if_match = fingerprint
|
60
|
+
elsif env['HTTP_IF_MATCH']
|
61
|
+
if_match = env['HTTP_IF_MATCH'][/^"(\w+)"$/, 1]
|
62
|
+
end
|
59
63
|
|
60
|
-
|
61
|
-
|
64
|
+
if env['HTTP_IF_NONE_MATCH']
|
65
|
+
if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1]
|
66
|
+
end
|
62
67
|
|
68
|
+
if asset.nil?
|
69
|
+
status = :not_found
|
70
|
+
elsif fingerprint && asset.etag != fingerprint
|
71
|
+
status = :not_found
|
72
|
+
elsif if_match && asset.etag != if_match
|
73
|
+
status = :precondition_failed
|
74
|
+
elsif if_none_match && asset.etag == if_none_match
|
75
|
+
status = :not_modified
|
63
76
|
else
|
64
|
-
|
77
|
+
status = :ok
|
78
|
+
end
|
65
79
|
|
66
|
-
|
80
|
+
case status
|
81
|
+
when :ok
|
82
|
+
logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
|
67
83
|
ok_response(asset, env)
|
84
|
+
when :not_modified
|
85
|
+
logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
|
86
|
+
not_modified_response(env, if_none_match)
|
87
|
+
when :not_found
|
88
|
+
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
|
89
|
+
not_found_response
|
90
|
+
when :precondition_failed
|
91
|
+
logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)"
|
92
|
+
precondition_failed_response
|
68
93
|
end
|
69
94
|
rescue Exception => e
|
70
95
|
logger.error "Error compiling asset #{path}:"
|
71
96
|
logger.error "#{e.class.name}: #{e.message}"
|
72
97
|
|
73
|
-
case
|
74
|
-
when "
|
98
|
+
case File.extname(path)
|
99
|
+
when ".js"
|
75
100
|
# Re-throw JavaScript asset exceptions to the browser
|
76
101
|
logger.info "#{msg} 500 Internal Server Error\n\n"
|
77
102
|
return javascript_exception_response(e)
|
78
|
-
when "
|
103
|
+
when ".css"
|
79
104
|
# Display CSS asset exceptions in the browser
|
80
105
|
logger.info "#{msg} 500 Internal Server Error\n\n"
|
81
106
|
return css_exception_response(e)
|
@@ -90,7 +115,17 @@ module Sprockets
|
|
90
115
|
#
|
91
116
|
# http://example.org/assets/../../../etc/passwd
|
92
117
|
#
|
93
|
-
path.include?("..")
|
118
|
+
path.include?("..") || absolute_path?(path)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns a 200 OK response tuple
|
122
|
+
def ok_response(asset, env)
|
123
|
+
[ 200, headers(env, asset, asset.length), asset ]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns a 304 Not Modified response tuple
|
127
|
+
def not_modified_response(env, etag)
|
128
|
+
[ 304, cache_headers(env, etag), [] ]
|
94
129
|
end
|
95
130
|
|
96
131
|
# Returns a 403 Forbidden response tuple
|
@@ -103,12 +138,20 @@ module Sprockets
|
|
103
138
|
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
|
104
139
|
end
|
105
140
|
|
141
|
+
def method_not_allowed_response
|
142
|
+
[ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
|
143
|
+
end
|
144
|
+
|
145
|
+
def precondition_failed_response
|
146
|
+
[ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
|
147
|
+
end
|
148
|
+
|
106
149
|
# Returns a JavaScript response that re-throws a Ruby exception
|
107
150
|
# in the browser
|
108
151
|
def javascript_exception_response(exception)
|
109
|
-
err = "#{exception.class.name}: #{exception.message}"
|
152
|
+
err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})"
|
110
153
|
body = "throw Error(#{err.inspect})"
|
111
|
-
[ 200, { "Content-Type" => "application/javascript", "Content-Length" =>
|
154
|
+
[ 200, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ]
|
112
155
|
end
|
113
156
|
|
114
157
|
# Returns a CSS response that hides all elements on the page and
|
@@ -161,7 +204,7 @@ module Sprockets
|
|
161
204
|
}
|
162
205
|
CSS
|
163
206
|
|
164
|
-
[ 200, { "Content-Type" => "text/css;charset=utf-8", "Content-Length" =>
|
207
|
+
[ 200, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ]
|
165
208
|
end
|
166
209
|
|
167
210
|
# Escape special characters for use inside a CSS content("...") string
|
@@ -173,75 +216,57 @@ module Sprockets
|
|
173
216
|
gsub('/', '\\\\002f ')
|
174
217
|
end
|
175
218
|
|
176
|
-
# Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest
|
177
|
-
def etag_match?(asset, env)
|
178
|
-
env["HTTP_IF_NONE_MATCH"] == etag(asset)
|
179
|
-
end
|
180
|
-
|
181
219
|
# Test if `?body=1` or `body=true` query param is set
|
182
220
|
def body_only?(env)
|
183
221
|
env["QUERY_STRING"].to_s =~ /body=(1|t)/
|
184
222
|
end
|
185
223
|
|
186
|
-
|
187
|
-
|
188
|
-
[ 304, {}, [] ]
|
189
|
-
end
|
224
|
+
def cache_headers(env, etag)
|
225
|
+
headers = {}
|
190
226
|
|
191
|
-
|
192
|
-
|
193
|
-
[
|
227
|
+
# Set caching headers
|
228
|
+
headers["Cache-Control"] = "public"
|
229
|
+
headers["ETag"] = %("#{etag}")
|
230
|
+
|
231
|
+
# If the request url contains a fingerprint, set a long
|
232
|
+
# expires on the response
|
233
|
+
if path_fingerprint(env["PATH_INFO"])
|
234
|
+
headers["Cache-Control"] << ", max-age=31536000"
|
235
|
+
|
236
|
+
# Otherwise set `must-revalidate` since the asset could be modified.
|
237
|
+
else
|
238
|
+
headers["Cache-Control"] << ", must-revalidate"
|
239
|
+
headers["Vary"] = "Accept-Encoding"
|
240
|
+
end
|
241
|
+
|
242
|
+
headers
|
194
243
|
end
|
195
244
|
|
196
245
|
def headers(env, asset, length)
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
# If the request url contains a fingerprint, set a long
|
208
|
-
# expires on the response
|
209
|
-
if path_fingerprint(env["PATH_INFO"])
|
210
|
-
headers["Cache-Control"] << ", max-age=31536000"
|
211
|
-
|
212
|
-
# Otherwise set `must-revalidate` since the asset could be modified.
|
213
|
-
else
|
214
|
-
headers["Cache-Control"] << ", must-revalidate"
|
246
|
+
headers = {}
|
247
|
+
|
248
|
+
# Set content length header
|
249
|
+
headers["Content-Length"] = length.to_s
|
250
|
+
|
251
|
+
# Set content type header
|
252
|
+
if type = asset.content_type
|
253
|
+
# Set charset param for text/* mime types
|
254
|
+
if type.start_with?("text/") && asset.charset
|
255
|
+
type += "; charset=#{asset.charset}"
|
215
256
|
end
|
257
|
+
headers["Content-Type"] = type
|
216
258
|
end
|
259
|
+
|
260
|
+
headers.merge(cache_headers(env, asset.etag))
|
217
261
|
end
|
218
262
|
|
219
|
-
# Gets
|
263
|
+
# Gets ETag fingerprint.
|
220
264
|
#
|
221
265
|
# "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
|
222
266
|
# # => "0aa2105d29558f3eb790d411d7d8fb66"
|
223
267
|
#
|
224
268
|
def path_fingerprint(path)
|
225
|
-
path[/-([0-9a-f]{7,
|
226
|
-
end
|
227
|
-
|
228
|
-
# URI.unescape is deprecated on 1.9. We need to use URI::Parser
|
229
|
-
# if its available.
|
230
|
-
if defined? URI::DEFAULT_PARSER
|
231
|
-
def unescape(str)
|
232
|
-
str = URI::DEFAULT_PARSER.unescape(str)
|
233
|
-
str.force_encoding(Encoding.default_internal) if Encoding.default_internal
|
234
|
-
str
|
235
|
-
end
|
236
|
-
else
|
237
|
-
def unescape(str)
|
238
|
-
URI.unescape(str)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
# Helper to quote the assets digest for use as an ETag.
|
243
|
-
def etag(asset)
|
244
|
-
%("#{asset.digest}")
|
269
|
+
path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
|
245
270
|
end
|
246
271
|
end
|
247
272
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'sprockets/http_utils'
|
2
|
+
require 'sprockets/processor_utils'
|
3
|
+
require 'sprockets/utils'
|
4
|
+
|
5
|
+
module Sprockets
|
6
|
+
module Transformers
|
7
|
+
include HTTPUtils, ProcessorUtils, Utils
|
8
|
+
|
9
|
+
# Public: Two level mapping of a source mime type to a target mime type.
|
10
|
+
#
|
11
|
+
# environment.transformers
|
12
|
+
# # => { 'text/coffeescript' => {
|
13
|
+
# 'application/javascript' => ConvertCoffeeScriptToJavaScript
|
14
|
+
# }
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
def transformers
|
18
|
+
config[:transformers]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Register a transformer from and to a mime type.
|
22
|
+
#
|
23
|
+
# from - String mime type
|
24
|
+
# to - String mime type
|
25
|
+
# proc - Callable block that accepts an input Hash.
|
26
|
+
#
|
27
|
+
# Examples
|
28
|
+
#
|
29
|
+
# register_transformer 'text/coffeescript', 'application/javascript',
|
30
|
+
# ConvertCoffeeScriptToJavaScript
|
31
|
+
#
|
32
|
+
# register_transformer 'image/svg+xml', 'image/png', ConvertSvgToPng
|
33
|
+
#
|
34
|
+
# Returns nothing.
|
35
|
+
def register_transformer(from, to, proc)
|
36
|
+
self.config = hash_reassoc(config, :registered_transformers, from) do |transformers|
|
37
|
+
transformers.merge(to => proc)
|
38
|
+
end
|
39
|
+
compute_transformers!
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Resolve target mime type that the source type should be
|
43
|
+
# transformed to.
|
44
|
+
#
|
45
|
+
# type - String from mime type
|
46
|
+
# accept - String accept type list (default: '*/*')
|
47
|
+
#
|
48
|
+
# Examples
|
49
|
+
#
|
50
|
+
# resolve_transform_type('text/plain', 'text/plain')
|
51
|
+
# # => 'text/plain'
|
52
|
+
#
|
53
|
+
# resolve_transform_type('image/svg+xml', 'image/png, image/*')
|
54
|
+
# # => 'image/png'
|
55
|
+
#
|
56
|
+
# resolve_transform_type('text/css', 'image/png')
|
57
|
+
# # => nil
|
58
|
+
#
|
59
|
+
# Returns String mime type or nil is no type satisfied the accept value.
|
60
|
+
def resolve_transform_type(type, accept)
|
61
|
+
find_best_mime_type_match(accept || '*/*', [type].compact + config[:transformers][type].keys)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Internal: Expand accept type list to include possible transformed types.
|
65
|
+
#
|
66
|
+
# parsed_accepts - Array of accept q values
|
67
|
+
#
|
68
|
+
# Examples
|
69
|
+
#
|
70
|
+
# expand_transform_accepts([['application/javascript', 1.0]])
|
71
|
+
# # => [['application/javascript', 1.0], ['text/coffeescript', 0.8]]
|
72
|
+
#
|
73
|
+
# Returns an expanded Array of q values.
|
74
|
+
def expand_transform_accepts(parsed_accepts)
|
75
|
+
accepts = []
|
76
|
+
parsed_accepts.each do |(type, q)|
|
77
|
+
accepts.push([type, q])
|
78
|
+
config[:inverted_transformers][type].each do |subtype|
|
79
|
+
accepts.push([subtype, q * 0.8])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
accepts
|
83
|
+
end
|
84
|
+
|
85
|
+
# Internal: Compose multiple transformer steps into a single processor
|
86
|
+
# function.
|
87
|
+
#
|
88
|
+
# transformers - Two level Hash of a source mime type to a target mime type
|
89
|
+
# types - Array of mime type steps
|
90
|
+
#
|
91
|
+
# Returns Processor.
|
92
|
+
def compose_transformers(transformers, types)
|
93
|
+
if types.length < 2
|
94
|
+
raise ArgumentError, "too few transform types: #{types.inspect}"
|
95
|
+
end
|
96
|
+
|
97
|
+
i = 0
|
98
|
+
processors = []
|
99
|
+
|
100
|
+
loop do
|
101
|
+
src = types[i]
|
102
|
+
dst = types[i+1]
|
103
|
+
break unless src && dst
|
104
|
+
|
105
|
+
unless processor = transformers[src][dst]
|
106
|
+
raise ArgumentError, "missing transformer for type: #{src} to #{dst}"
|
107
|
+
end
|
108
|
+
processors.concat config[:postprocessors][src]
|
109
|
+
processors << processor
|
110
|
+
processors.concat config[:preprocessors][dst]
|
111
|
+
|
112
|
+
i += 1
|
113
|
+
end
|
114
|
+
|
115
|
+
if processors.size > 1
|
116
|
+
compose_processors(*processors.reverse)
|
117
|
+
elsif processors.size == 1
|
118
|
+
processors.first
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def compute_transformers!
|
124
|
+
registered_transformers = self.config[:registered_transformers]
|
125
|
+
transformers = Hash.new { {} }
|
126
|
+
inverted_transformers = Hash.new { Set.new }
|
127
|
+
|
128
|
+
registered_transformers.keys.flat_map do |key|
|
129
|
+
dfs_paths([key]) { |k| registered_transformers[k].keys }
|
130
|
+
end.each do |types|
|
131
|
+
src, dst = types.first, types.last
|
132
|
+
processor = compose_transformers(registered_transformers, types)
|
133
|
+
|
134
|
+
transformers[src] = {} unless transformers.key?(src)
|
135
|
+
transformers[src][dst] = processor
|
136
|
+
|
137
|
+
inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst)
|
138
|
+
inverted_transformers[dst] << src
|
139
|
+
end
|
140
|
+
|
141
|
+
self.config = hash_reassoc(config, :transformers) { transformers }
|
142
|
+
self.config = hash_reassoc(config, :inverted_transformers) { inverted_transformers }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'sprockets/autoload'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# Public: Uglifier/Uglify compressor.
|
5
|
+
#
|
6
|
+
# To accept the default options
|
7
|
+
#
|
8
|
+
# environment.register_bundle_processor 'application/javascript',
|
9
|
+
# Sprockets::UglifierCompressor
|
10
|
+
#
|
11
|
+
# Or to pass options to the Uglifier class.
|
12
|
+
#
|
13
|
+
# environment.register_bundle_processor 'application/javascript',
|
14
|
+
# Sprockets::UglifierCompressor.new(comments: :copyright)
|
15
|
+
#
|
16
|
+
class UglifierCompressor
|
17
|
+
VERSION = '1'
|
18
|
+
|
19
|
+
# Public: Return singleton instance with default options.
|
20
|
+
#
|
21
|
+
# Returns UglifierCompressor object.
|
22
|
+
def self.instance
|
23
|
+
@instance ||= new
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.call(input)
|
27
|
+
instance.call(input)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.cache_key
|
31
|
+
instance.cache_key
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :cache_key
|
35
|
+
|
36
|
+
def initialize(options = {})
|
37
|
+
# Feature detect Uglifier 2.0 option support
|
38
|
+
if Autoload::Uglifier::DEFAULTS[:copyright]
|
39
|
+
# Uglifier < 2.x
|
40
|
+
options[:copyright] ||= false
|
41
|
+
else
|
42
|
+
# Uglifier >= 2.x
|
43
|
+
options[:copyright] ||= :none
|
44
|
+
end
|
45
|
+
|
46
|
+
@uglifier = Autoload::Uglifier.new(options)
|
47
|
+
|
48
|
+
@cache_key = [
|
49
|
+
self.class.name,
|
50
|
+
Autoload::Uglifier::VERSION,
|
51
|
+
VERSION,
|
52
|
+
options
|
53
|
+
].freeze
|
54
|
+
end
|
55
|
+
|
56
|
+
def call(input)
|
57
|
+
data = input[:data]
|
58
|
+
input[:cache].fetch(@cache_key + [data]) do
|
59
|
+
@uglifier.compile(data)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# Internal: Asset URI related parsing utilities. Mixed into Environment.
|
5
|
+
#
|
6
|
+
# An Asset URI identifies the compiled Asset result. It shares the file:
|
7
|
+
# scheme and requires an absolute path.
|
8
|
+
#
|
9
|
+
# Other query parameters
|
10
|
+
#
|
11
|
+
# type - String output content type. Otherwise assumed from file extension.
|
12
|
+
# This maybe different than the extension if the asset is transformed
|
13
|
+
# from one content type to another. For an example .coffee -> .js.
|
14
|
+
#
|
15
|
+
# id - Unique fingerprint of the entire asset and all its metadata. Assets
|
16
|
+
# will only have the same id if they serialize to an identical value.
|
17
|
+
#
|
18
|
+
# pipeline - String name of pipeline.
|
19
|
+
#
|
20
|
+
# encoding - A content encoding such as "gzip" or "deflate". NOT a charset
|
21
|
+
# like "utf-8".
|
22
|
+
#
|
23
|
+
module URIUtils
|
24
|
+
extend self
|
25
|
+
|
26
|
+
# Internal: Parse URI into component parts.
|
27
|
+
#
|
28
|
+
# uri - String uri
|
29
|
+
#
|
30
|
+
# Returns Array of components.
|
31
|
+
def split_uri(uri)
|
32
|
+
URI.split(uri)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Internal: Join URI component parts into String.
|
36
|
+
#
|
37
|
+
# Returns String.
|
38
|
+
def join_uri(scheme, userinfo, host, port, registry, path, opaque, query, fragment)
|
39
|
+
URI::Generic.new(scheme, userinfo, host, port, registry, path, opaque, query, fragment).to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Parse file: URI into component parts.
|
43
|
+
#
|
44
|
+
# uri - String uri
|
45
|
+
#
|
46
|
+
# Returns [scheme, host, path, query].
|
47
|
+
def split_file_uri(uri)
|
48
|
+
scheme, _, host, _, _, path, _, query, _ = URI.split(uri)
|
49
|
+
|
50
|
+
path = URI::Generic::DEFAULT_PARSER.unescape(path)
|
51
|
+
path.force_encoding(Encoding::UTF_8)
|
52
|
+
|
53
|
+
# Hack for parsing Windows "file:///C:/Users/IEUser" paths
|
54
|
+
path = path.gsub(/^\/([a-zA-Z]:)/, '\1')
|
55
|
+
|
56
|
+
[scheme, host, path, query]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Internal: Join file: URI component parts into String.
|
60
|
+
#
|
61
|
+
# Returns String.
|
62
|
+
def join_file_uri(scheme, host, path, query)
|
63
|
+
str = "#{scheme}://"
|
64
|
+
str << host if host
|
65
|
+
path = "/#{path}" unless path.start_with?("/")
|
66
|
+
str << URI::Generic::DEFAULT_PARSER.escape(path)
|
67
|
+
str << "?#{query}" if query
|
68
|
+
str
|
69
|
+
end
|
70
|
+
|
71
|
+
# Internal: Check if String is a valid Asset URI.
|
72
|
+
#
|
73
|
+
# str - Possible String asset URI.
|
74
|
+
#
|
75
|
+
# Returns true or false.
|
76
|
+
def valid_asset_uri?(str)
|
77
|
+
# Quick prefix check before attempting a full parse
|
78
|
+
str.start_with?("file://") && parse_asset_uri(str) ? true : false
|
79
|
+
rescue URI::InvalidURIError
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
# Internal: Parse Asset URI.
|
84
|
+
#
|
85
|
+
# Examples
|
86
|
+
#
|
87
|
+
# parse("file:///tmp/js/application.coffee?type=application/javascript")
|
88
|
+
# # => "/tmp/js/application.coffee", {type: "application/javascript"}
|
89
|
+
#
|
90
|
+
# uri - String asset URI
|
91
|
+
#
|
92
|
+
# Returns String path and Hash of symbolized parameters.
|
93
|
+
def parse_asset_uri(uri)
|
94
|
+
scheme, _, path, query = split_file_uri(uri)
|
95
|
+
|
96
|
+
unless scheme == 'file'
|
97
|
+
raise URI::InvalidURIError, "expected file:// scheme: #{uri}"
|
98
|
+
end
|
99
|
+
|
100
|
+
return path, parse_uri_query_params(query)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Internal: Build Asset URI.
|
104
|
+
#
|
105
|
+
# Examples
|
106
|
+
#
|
107
|
+
# build("/tmp/js/application.coffee", type: "application/javascript")
|
108
|
+
# # => "file:///tmp/js/application.coffee?type=application/javascript"
|
109
|
+
#
|
110
|
+
# path - String file path
|
111
|
+
# params - Hash of optional parameters
|
112
|
+
#
|
113
|
+
# Returns String URI.
|
114
|
+
def build_asset_uri(path, params = {})
|
115
|
+
join_file_uri("file", nil, path, encode_uri_query_params(params))
|
116
|
+
end
|
117
|
+
|
118
|
+
# Internal: Parse file-digest dependency URI.
|
119
|
+
#
|
120
|
+
# Examples
|
121
|
+
#
|
122
|
+
# parse("file-digest:/tmp/js/application.js")
|
123
|
+
# # => "/tmp/js/application.js"
|
124
|
+
#
|
125
|
+
# uri - String file-digest URI
|
126
|
+
#
|
127
|
+
# Returns String path.
|
128
|
+
def parse_file_digest_uri(uri)
|
129
|
+
scheme, _, path, _ = split_file_uri(uri)
|
130
|
+
|
131
|
+
unless scheme == 'file-digest'
|
132
|
+
raise URI::InvalidURIError, "expected file-digest scheme: #{uri}"
|
133
|
+
end
|
134
|
+
|
135
|
+
path
|
136
|
+
end
|
137
|
+
|
138
|
+
# Internal: Build file-digest dependency URI.
|
139
|
+
#
|
140
|
+
# Examples
|
141
|
+
#
|
142
|
+
# build("/tmp/js/application.js")
|
143
|
+
# # => "file-digest:/tmp/js/application.js"
|
144
|
+
#
|
145
|
+
# path - String file path
|
146
|
+
#
|
147
|
+
# Returns String URI.
|
148
|
+
def build_file_digest_uri(path)
|
149
|
+
join_file_uri("file-digest", nil, path, nil)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Internal: Serialize hash of params into query string.
|
153
|
+
#
|
154
|
+
# params - Hash of params to serialize
|
155
|
+
#
|
156
|
+
# Returns String query or nil if empty.
|
157
|
+
def encode_uri_query_params(params)
|
158
|
+
query = []
|
159
|
+
|
160
|
+
params.each do |key, value|
|
161
|
+
case value
|
162
|
+
when Integer
|
163
|
+
query << "#{key}=#{value}"
|
164
|
+
when String, Symbol
|
165
|
+
query << "#{key}=#{URI::Generic::DEFAULT_PARSER.escape(value.to_s)}"
|
166
|
+
when TrueClass
|
167
|
+
query << "#{key}"
|
168
|
+
when FalseClass, NilClass
|
169
|
+
else
|
170
|
+
raise TypeError, "unexpected type: #{value.class}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
"#{query.join('&')}" if query.any?
|
175
|
+
end
|
176
|
+
|
177
|
+
# Internal: Parse query string into hash of params
|
178
|
+
#
|
179
|
+
# query - String query string
|
180
|
+
#
|
181
|
+
# Return Hash of params.
|
182
|
+
def parse_uri_query_params(query)
|
183
|
+
query.to_s.split('&').reduce({}) do |h, p|
|
184
|
+
k, v = p.split('=', 2)
|
185
|
+
v = URI::Generic::DEFAULT_PARSER.unescape(v) if v
|
186
|
+
h.merge(k.to_sym => v || true)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|