uglifier 2.2.1 → 2.7.2
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 +7 -0
- data/.gitignore +44 -0
- data/.rubocop.yml +27 -0
- data/.travis.yml +24 -16
- data/CHANGELOG.md +63 -0
- data/CONTRIBUTING.md +36 -11
- data/Gemfile +5 -22
- data/README.md +36 -10
- data/Rakefile +13 -15
- data/gemfiles/alaska +4 -0
- data/gemfiles/rubyracer +4 -0
- data/gemfiles/rubyrhino +4 -0
- data/lib/es5.js +1 -1
- data/lib/uglifier/version.rb +4 -0
- data/lib/uglifier.rb +167 -136
- data/lib/uglify.js +599 -256
- data/spec/source_map_spec.rb +60 -36
- data/spec/spec_helper.rb +13 -2
- data/spec/uglifier_spec.rb +120 -63
- data/uglifier.gemspec +28 -68
- metadata +72 -85
- data/VERSION +0 -1
data/lib/uglifier.rb
CHANGED
@@ -1,15 +1,91 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require "execjs"
|
4
|
-
require "
|
4
|
+
require "json"
|
5
|
+
require "uglifier/version"
|
5
6
|
|
7
|
+
# A wrapper around the UglifyJS interface
|
6
8
|
class Uglifier
|
9
|
+
# Error class for compilation errors.
|
7
10
|
Error = ExecJS::Error
|
11
|
+
# JavaScript code to call UglifyJS
|
12
|
+
JS = <<-JS
|
13
|
+
(function(options) {
|
14
|
+
function comments(option) {
|
15
|
+
if (Object.prototype.toString.call(option) === '[object Array]') {
|
16
|
+
return new RegExp(option[0], option[1]);
|
17
|
+
} else if (option == "jsdoc") {
|
18
|
+
return function(node, comment) {
|
19
|
+
if (comment.type == "comment2") {
|
20
|
+
return /@preserve|@license|@cc_on/i.test(comment.value);
|
21
|
+
} else {
|
22
|
+
return false;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
} else {
|
26
|
+
return option;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
var source = options.source;
|
31
|
+
var ast = UglifyJS.parse(source, options.parse_options);
|
32
|
+
ast.figure_out_scope();
|
33
|
+
|
34
|
+
if (options.compress) {
|
35
|
+
var compressor = UglifyJS.Compressor(options.compress);
|
36
|
+
ast = ast.transform(compressor);
|
37
|
+
ast.figure_out_scope();
|
38
|
+
}
|
39
|
+
|
40
|
+
if (options.mangle) {
|
41
|
+
ast.compute_char_frequency();
|
42
|
+
ast.mangle_names(options.mangle);
|
43
|
+
}
|
44
|
+
|
45
|
+
if (options.enclose) {
|
46
|
+
ast = ast.wrap_enclose(options.enclose);
|
47
|
+
}
|
48
|
+
|
49
|
+
var gen_code_options = options.output;
|
50
|
+
gen_code_options.comments = comments(options.output.comments);
|
51
|
+
|
52
|
+
if (options.generate_map) {
|
53
|
+
var source_map = UglifyJS.SourceMap(options.source_map_options);
|
54
|
+
gen_code_options.source_map = source_map;
|
55
|
+
}
|
56
|
+
|
57
|
+
var stream = UglifyJS.OutputStream(gen_code_options);
|
58
|
+
|
59
|
+
ast.print(stream);
|
60
|
+
|
61
|
+
if (options.source_map_options.map_url) {
|
62
|
+
stream += "\\n//# sourceMappingURL=" + options.source_map_options.map_url;
|
63
|
+
}
|
64
|
+
|
65
|
+
if (options.source_map_options.url) {
|
66
|
+
stream += "\\n//# sourceURL=" + options.source_map_options.url;
|
67
|
+
}
|
68
|
+
|
69
|
+
if (options.generate_map) {
|
70
|
+
return [stream.toString(), source_map.toString()];
|
71
|
+
} else {
|
72
|
+
return stream.toString();
|
73
|
+
}
|
74
|
+
})
|
75
|
+
JS
|
76
|
+
|
77
|
+
# UglifyJS source path
|
78
|
+
SourcePath = File.expand_path("../uglify.js", __FILE__)
|
79
|
+
# ES5 shims source path
|
80
|
+
ES5FallbackPath = File.expand_path("../es5.js", __FILE__)
|
81
|
+
# String.split shim source path
|
82
|
+
SplitFallbackPath = File.expand_path("../split.js", __FILE__)
|
8
83
|
|
9
84
|
# Default options for compilation
|
10
85
|
DEFAULTS = {
|
86
|
+
# rubocop:disable LineLength
|
11
87
|
:output => {
|
12
|
-
:ascii_only =>
|
88
|
+
:ascii_only => true, # Escape non-ASCII characterss
|
13
89
|
:comments => :copyright, # Preserve comments (:all, :jsdoc, :copyright, :none)
|
14
90
|
:inline_script => false, # Escape occurrences of </script in strings
|
15
91
|
:quote_keys => false, # Quote keys in object literals
|
@@ -21,10 +97,14 @@ class Uglifier
|
|
21
97
|
:indent_level => 4, # Indent level in spaces
|
22
98
|
:indent_start => 0, # Starting indent level
|
23
99
|
:space_colon => false, # Insert space before colons (only with beautifier)
|
24
|
-
:width => 80 # Specify line width when beautifier is used (only with beautifier)
|
100
|
+
:width => 80, # Specify line width when beautifier is used (only with beautifier)
|
101
|
+
:preamble => nil # Preamble for the generated JS file. Can be used to insert any code or comment.
|
25
102
|
},
|
26
103
|
:mangle => {
|
27
|
-
:
|
104
|
+
:eval => false, # Mangle names when eval of when is used in scope
|
105
|
+
:except => ["$super"], # Argument names to be excluded from mangling
|
106
|
+
:sort => false, # Assign shorter names to most frequently used variables. Often results in bigger output after gzip.
|
107
|
+
:toplevel => false # Mangle names declared in the toplevel scope
|
28
108
|
}, # Mangle variable and function names, set to false to skip mangling
|
29
109
|
:compress => {
|
30
110
|
:sequences => true, # Allow statements to be joined by commas
|
@@ -36,14 +116,19 @@ class Uglifier
|
|
36
116
|
:comparisons => true, # Apply binary node optimizations for comparisons
|
37
117
|
:evaluate => true, # Attempt to evaluate constant expressions
|
38
118
|
:booleans => true, # Various optimizations to boolean contexts
|
39
|
-
:loops => true, # Optimize
|
119
|
+
:loops => true, # Optimize loops when condition can be statically determined
|
40
120
|
:unused => true, # Drop unreferenced functions and variables
|
41
121
|
:hoist_funs => true, # Hoist function declarations
|
42
122
|
:hoist_vars => false, # Hoist var declarations
|
43
123
|
:if_return => true, # Optimizations for if/return and if/continue
|
44
124
|
:join_vars => true, # Join consecutive var statements
|
45
125
|
:cascade => true, # Cascade sequences
|
46
|
-
:negate_iife => true # Negate immediately
|
126
|
+
:negate_iife => true, # Negate immediately invoked function expressions to avoid extra parens
|
127
|
+
:pure_getters => false, # Assume that object property access does not have any side-effects
|
128
|
+
:pure_funcs => nil, # List of functions without side-effects. Can safely discard function calls when the result value is not used
|
129
|
+
:drop_console => false, # Drop calls to console.* functions
|
130
|
+
:angular => false, # Process @ngInject annotations
|
131
|
+
:keep_fargs => false # Preserve unused function arguments
|
47
132
|
}, # Apply transformations to code, set to false to skip
|
48
133
|
:define => {}, # Define values for symbol replacement
|
49
134
|
:enclose => false, # Enclose in output function wrapper, define replacements as key-value pairs
|
@@ -51,138 +136,88 @@ class Uglifier
|
|
51
136
|
:source_root => nil, # The URL of the directory which contains :source_filename
|
52
137
|
:output_filename => nil, # The filename or URL where the minified output can be found
|
53
138
|
:input_source_map => nil, # The contents of the source map describing the input
|
54
|
-
:screw_ie8 => false #
|
139
|
+
:screw_ie8 => false, # Don't bother to generate safe code for IE8
|
140
|
+
:source_map_url => false, # Url for source mapping to be appended in minified source
|
141
|
+
:source_url => false # Url for original source to be appended in minified source
|
55
142
|
}
|
56
|
-
|
57
|
-
SourcePath = File.expand_path("../uglify.js", __FILE__)
|
58
|
-
ES5FallbackPath = File.expand_path("../es5.js", __FILE__)
|
59
|
-
SplitFallbackPath = File.expand_path("../split.js", __FILE__)
|
143
|
+
# rubocop:enable LineLength
|
60
144
|
|
61
145
|
# Minifies JavaScript code using implicit context.
|
62
146
|
#
|
63
|
-
# source
|
64
|
-
# options
|
65
|
-
#
|
66
|
-
# Returns minified code as String
|
147
|
+
# @param source [IO, String] valid JS source code.
|
148
|
+
# @param options [Hash] optional overrides to +Uglifier::DEFAULTS+
|
149
|
+
# @return [String] minified code.
|
67
150
|
def self.compile(source, options = {})
|
68
|
-
|
151
|
+
new(options).compile(source)
|
69
152
|
end
|
70
153
|
|
71
154
|
# Minifies JavaScript code and generates a source map using implicit context.
|
72
155
|
#
|
73
|
-
# source
|
74
|
-
# options
|
75
|
-
#
|
76
|
-
# Returns a pair of [minified code as String, source map as a String]
|
156
|
+
# @param source [IO, String] valid JS source code.
|
157
|
+
# @param options [Hash] optional overrides to +Uglifier::DEFAULTS+
|
158
|
+
# @return [Array(String, String)] minified code and source map.
|
77
159
|
def self.compile_with_map(source, options = {})
|
78
|
-
|
160
|
+
new(options).compile_with_map(source)
|
79
161
|
end
|
80
162
|
|
81
163
|
# Initialize new context for Uglifier with given options
|
82
164
|
#
|
83
|
-
# options
|
165
|
+
# @param options [Hash] optional overrides to +Uglifier::DEFAULTS+
|
84
166
|
def initialize(options = {})
|
85
167
|
(options.keys - DEFAULTS.keys - [:comments, :squeeze, :copyright])[0..1].each do |missing|
|
86
|
-
raise ArgumentError
|
168
|
+
raise ArgumentError, "Invalid option: #{missing}"
|
87
169
|
end
|
88
170
|
@options = options
|
89
|
-
@context = ExecJS.compile(
|
90
|
-
File.open(SplitFallbackPath, "r:UTF-8").read +
|
91
|
-
File.open(SourcePath, "r:UTF-8").read)
|
171
|
+
@context = ExecJS.compile(uglifyjs_source)
|
92
172
|
end
|
93
173
|
|
94
174
|
# Minifies JavaScript code
|
95
175
|
#
|
96
|
-
# source
|
97
|
-
#
|
98
|
-
# Returns minified code as String
|
176
|
+
# @param source [IO, String] valid JS source code.
|
177
|
+
# @return [String] minified code.
|
99
178
|
def compile(source)
|
100
|
-
|
179
|
+
run_uglifyjs(source, false)
|
101
180
|
end
|
102
181
|
alias_method :compress, :compile
|
103
182
|
|
104
183
|
# Minifies JavaScript code and generates a source map
|
105
184
|
#
|
106
|
-
# source
|
107
|
-
#
|
108
|
-
# Returns a pair of [minified code as String, source map as a String]
|
185
|
+
# @param source [IO, String] valid JS source code.
|
186
|
+
# @return [Array(String, String)] minified code and source map.
|
109
187
|
def compile_with_map(source)
|
110
|
-
|
188
|
+
run_uglifyjs(source, true)
|
111
189
|
end
|
112
190
|
|
113
191
|
private
|
114
192
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
js = <<-JS
|
122
|
-
function comments(option) {
|
123
|
-
if (Object.prototype.toString.call(option) === '[object Array]') {
|
124
|
-
return new RegExp(option[0], option[1]);
|
125
|
-
} else if (option == "jsdoc") {
|
126
|
-
return function(node, comment) {
|
127
|
-
if (comment.type == "comment2") {
|
128
|
-
return /@preserve|@license|@cc_on/i.test(comment.value);
|
129
|
-
} else {
|
130
|
-
return false;
|
131
|
-
}
|
132
|
-
}
|
133
|
-
} else {
|
134
|
-
return option;
|
135
|
-
}
|
136
|
-
}
|
137
|
-
|
138
|
-
var options = %s;
|
139
|
-
var source = options.source;
|
140
|
-
var ast = UglifyJS.parse(source, options.parse_options);
|
141
|
-
ast.figure_out_scope();
|
142
|
-
|
143
|
-
if (options.compress) {
|
144
|
-
var compressor = UglifyJS.Compressor(options.compress);
|
145
|
-
ast = ast.transform(compressor);
|
146
|
-
ast.figure_out_scope();
|
147
|
-
}
|
148
|
-
|
149
|
-
if (options.mangle) {
|
150
|
-
ast.compute_char_frequency();
|
151
|
-
ast.mangle_names(options.mangle);
|
152
|
-
}
|
153
|
-
|
154
|
-
if (options.enclose) {
|
155
|
-
ast = ast.wrap_enclose(options.enclose);
|
156
|
-
}
|
157
|
-
|
158
|
-
var gen_code_options = options.output;
|
159
|
-
gen_code_options.comments = comments(options.output.comments);
|
160
|
-
|
161
|
-
if (options.generate_map) {
|
162
|
-
var source_map = UglifyJS.SourceMap(options.source_map_options);
|
163
|
-
gen_code_options.source_map = source_map;
|
164
|
-
}
|
165
|
-
|
166
|
-
var stream = UglifyJS.OutputStream(gen_code_options);
|
167
|
-
|
168
|
-
ast.print(stream);
|
169
|
-
if (options.generate_map) {
|
170
|
-
return [stream.toString(), source_map.toString()];
|
171
|
-
} else {
|
172
|
-
return stream.toString();
|
173
|
-
}
|
174
|
-
JS
|
193
|
+
def uglifyjs_source
|
194
|
+
[ES5FallbackPath, SplitFallbackPath, SourcePath].map do |file|
|
195
|
+
File.open(file, "r:UTF-8") { |f| f.read }
|
196
|
+
end.join("\n")
|
197
|
+
end
|
175
198
|
|
176
|
-
|
177
|
-
|
199
|
+
# Run UglifyJS for given source code
|
200
|
+
def run_uglifyjs(source, generate_map)
|
201
|
+
options = {
|
202
|
+
:source => read_source(source),
|
178
203
|
:output => output_options,
|
179
204
|
:compress => compressor_options,
|
180
205
|
:mangle => mangle_options,
|
181
206
|
:parse_options => parse_options,
|
182
207
|
:source_map_options => source_map_options,
|
183
|
-
:generate_map =>
|
208
|
+
:generate_map => generate_map,
|
184
209
|
:enclose => enclose_options
|
185
|
-
|
210
|
+
}
|
211
|
+
|
212
|
+
@context.call(Uglifier::JS, options)
|
213
|
+
end
|
214
|
+
|
215
|
+
def read_source(source)
|
216
|
+
if source.respond_to?(:read)
|
217
|
+
source.read
|
218
|
+
else
|
219
|
+
source.to_s
|
220
|
+
end
|
186
221
|
end
|
187
222
|
|
188
223
|
def mangle_options
|
@@ -190,7 +225,8 @@ class Uglifier
|
|
190
225
|
end
|
191
226
|
|
192
227
|
def compressor_options
|
193
|
-
defaults = conditional_option(
|
228
|
+
defaults = conditional_option(
|
229
|
+
DEFAULTS[:compress],
|
194
230
|
:global_defs => @options[:define] || {},
|
195
231
|
:screw_ie8 => @options[:screw_ie8] || DEFAULTS[:screw_ie8]
|
196
232
|
)
|
@@ -198,53 +234,59 @@ class Uglifier
|
|
198
234
|
end
|
199
235
|
|
200
236
|
def comment_options
|
201
|
-
|
202
|
-
@options[:output][:comments]
|
203
|
-
elsif @options.has_key?(:comments)
|
204
|
-
@options[:comments]
|
205
|
-
elsif @options[:copyright] == false
|
206
|
-
:none
|
207
|
-
else
|
208
|
-
DEFAULTS[:output][:comments]
|
209
|
-
end
|
210
|
-
|
211
|
-
case val
|
237
|
+
case comment_setting
|
212
238
|
when :all, true
|
213
239
|
true
|
214
240
|
when :jsdoc
|
215
241
|
"jsdoc"
|
216
242
|
when :copyright
|
217
|
-
encode_regexp(/Copyright/i)
|
243
|
+
encode_regexp(/(^!)|Copyright/i)
|
218
244
|
when Regexp
|
219
|
-
encode_regexp(
|
245
|
+
encode_regexp(comment_setting)
|
220
246
|
else
|
221
247
|
false
|
222
248
|
end
|
223
249
|
end
|
224
250
|
|
225
|
-
def
|
226
|
-
|
227
|
-
|
251
|
+
def comment_setting
|
252
|
+
if @options.has_key?(:output) && @options[:output].has_key?(:comments)
|
253
|
+
@options[:output][:comments]
|
254
|
+
elsif @options.has_key?(:comments)
|
255
|
+
@options[:comments]
|
256
|
+
elsif @options[:copyright] == false
|
257
|
+
:none
|
228
258
|
else
|
229
|
-
|
259
|
+
DEFAULTS[:output][:comments]
|
230
260
|
end
|
261
|
+
end
|
231
262
|
|
263
|
+
def output_options
|
232
264
|
DEFAULTS[:output].merge(@options[:output] || {}).merge(
|
233
265
|
:comments => comment_options,
|
234
|
-
:screw_ie8 => screw_ie8
|
235
|
-
).reject { |key,
|
266
|
+
:screw_ie8 => screw_ie8?
|
267
|
+
).reject { |key, _| key == :ie_proof }
|
268
|
+
end
|
269
|
+
|
270
|
+
def screw_ie8?
|
271
|
+
if (@options[:output] || {}).has_key?(:ie_proof)
|
272
|
+
false
|
273
|
+
else
|
274
|
+
@options[:screw_ie8] || DEFAULTS[:screw_ie8]
|
275
|
+
end
|
236
276
|
end
|
237
277
|
|
238
278
|
def source_map_options
|
239
279
|
{
|
240
280
|
:file => @options[:output_filename],
|
241
281
|
:root => @options[:source_root],
|
242
|
-
:orig => @options[:input_source_map]
|
282
|
+
:orig => @options[:input_source_map],
|
283
|
+
:map_url => @options[:source_map_url],
|
284
|
+
:url => @options[:source_url]
|
243
285
|
}
|
244
286
|
end
|
245
287
|
|
246
288
|
def parse_options
|
247
|
-
{:filename => @options[:source_filename]}
|
289
|
+
{ :filename => @options[:source_filename] }
|
248
290
|
end
|
249
291
|
|
250
292
|
def enclose_options
|
@@ -257,29 +299,18 @@ class Uglifier
|
|
257
299
|
end
|
258
300
|
end
|
259
301
|
|
260
|
-
# MultiJson API detection
|
261
|
-
if MultiJson.respond_to? :dump
|
262
|
-
def json_encode(obj)
|
263
|
-
MultiJson.dump(obj)
|
264
|
-
end
|
265
|
-
else
|
266
|
-
def json_encode(obj)
|
267
|
-
MultiJson.encode(obj)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
302
|
def encode_regexp(regexp)
|
272
303
|
modifiers = if regexp.casefold?
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
304
|
+
"i"
|
305
|
+
else
|
306
|
+
""
|
307
|
+
end
|
277
308
|
|
278
309
|
[regexp.source, modifiers]
|
279
310
|
end
|
280
311
|
|
281
312
|
def conditional_option(value, defaults)
|
282
|
-
if value == true || value
|
313
|
+
if value == true || value.nil?
|
283
314
|
defaults
|
284
315
|
elsif value
|
285
316
|
defaults.merge(value)
|