uglifier 2.7.1 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/.travis.yml +9 -8
- data/.yardopts +4 -0
- data/CHANGELOG.md +21 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +1 -5
- data/README.md +21 -10
- data/Rakefile +3 -30
- data/lib/es5.js +1 -1
- data/lib/source-map.js +3055 -0
- data/lib/uglifier/version.rb +1 -1
- data/lib/uglifier.js +114 -0
- data/lib/uglifier.rb +112 -93
- data/lib/uglify.js +55 -1803
- data/spec/source_map_spec.rb +151 -35
- data/spec/spec_helper.rb +11 -2
- data/spec/uglifier_spec.rb +110 -24
- data/uglifier.gemspec +3 -5
- metadata +17 -36
data/lib/uglifier/version.rb
CHANGED
data/lib/uglifier.js
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
// Set source-map.js sourceMap to uglify.js MOZ_SourceMap
|
2
|
+
MOZ_SourceMap = sourceMap;
|
3
|
+
|
4
|
+
function comments(option) {
|
5
|
+
if (Object.prototype.toString.call(option) === '[object Array]') {
|
6
|
+
return new RegExp(option[0], option[1]);
|
7
|
+
} else if (option == "jsdoc") {
|
8
|
+
return function(node, comment) {
|
9
|
+
if (comment.type == "comment2") {
|
10
|
+
return /@preserve|@license|@cc_on/i.test(comment.value);
|
11
|
+
} else {
|
12
|
+
return false;
|
13
|
+
}
|
14
|
+
};
|
15
|
+
} else {
|
16
|
+
return option;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
function readNameCache(key) {
|
21
|
+
return UglifyJS.readNameCache(null, key);
|
22
|
+
}
|
23
|
+
|
24
|
+
function writeNameCache(key, cache) {
|
25
|
+
return UglifyJS.writeNameCache(null, key, cache);
|
26
|
+
}
|
27
|
+
|
28
|
+
function regexOption(options) {
|
29
|
+
if (typeof options === 'object' && options.regex) {
|
30
|
+
return new RegExp(options.regex[0], options.regex[1]);
|
31
|
+
} else {
|
32
|
+
return null;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
function parse(source, options) {
|
37
|
+
var ast = UglifyJS.parse(source, options.parse_options);
|
38
|
+
ast.figure_out_scope();
|
39
|
+
|
40
|
+
if (options.compress) {
|
41
|
+
var compressor = UglifyJS.Compressor(options.compress);
|
42
|
+
ast = ast.transform(compressor);
|
43
|
+
ast.figure_out_scope();
|
44
|
+
}
|
45
|
+
|
46
|
+
if (options.mangle) {
|
47
|
+
ast.compute_char_frequency();
|
48
|
+
ast.mangle_names(options.mangle);
|
49
|
+
}
|
50
|
+
|
51
|
+
if (options.mangle_properties) {
|
52
|
+
var regex = regexOption(options.mangle_properties);
|
53
|
+
UglifyJS.mangle_properties(ast, {
|
54
|
+
reserved: [],
|
55
|
+
only_cache: false,
|
56
|
+
regex: regex
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
if (options.enclose) {
|
61
|
+
ast = ast.wrap_enclose(options.enclose);
|
62
|
+
}
|
63
|
+
return ast;
|
64
|
+
}
|
65
|
+
|
66
|
+
function copySourcesContent(sourceMap, options) {
|
67
|
+
sourceMap.get().setSourceContent(options.parse_options.filename, options.source);
|
68
|
+
|
69
|
+
var original = options.source_map_options.orig;
|
70
|
+
|
71
|
+
if (original && original.sources && original.sourcesContent) {
|
72
|
+
for(var i = 0; i < original.sources.length; i++) {
|
73
|
+
sourceMap.get().setSourceContent(original.sources[i], original.sourcesContent[i]);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
function uglifier(options) {
|
79
|
+
var source = options.source;
|
80
|
+
var ast = parse(source, options);
|
81
|
+
var source_map;
|
82
|
+
|
83
|
+
var gen_code_options = options.output;
|
84
|
+
gen_code_options.comments = comments(options.output.comments);
|
85
|
+
|
86
|
+
if (options.generate_map) {
|
87
|
+
source_map = UglifyJS.SourceMap(options.source_map_options);
|
88
|
+
gen_code_options.source_map = source_map;
|
89
|
+
|
90
|
+
if (options.source_map_options.sources_content) {
|
91
|
+
copySourcesContent(source_map, options);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
var stream = UglifyJS.OutputStream(gen_code_options);
|
96
|
+
ast.print(stream);
|
97
|
+
|
98
|
+
if (options.source_map_options.map_url) {
|
99
|
+
stream += "\n//# sourceMappingURL=" + options.source_map_options.map_url;
|
100
|
+
}
|
101
|
+
|
102
|
+
if (options.source_map_options.url) {
|
103
|
+
stream += "\n//# sourceURL=" + options.source_map_options.url;
|
104
|
+
}
|
105
|
+
|
106
|
+
if (options.generate_map) {
|
107
|
+
if (options.source_map_options.sources_content) {
|
108
|
+
source_map.get().setSourceContent(options.parse_options.filename, options.source);
|
109
|
+
}
|
110
|
+
return [stream.toString(), source_map.toString()];
|
111
|
+
} else {
|
112
|
+
return stream.toString();
|
113
|
+
}
|
114
|
+
}
|
data/lib/uglifier.rb
CHANGED
@@ -1,85 +1,25 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require "execjs"
|
4
3
|
require "json"
|
4
|
+
require "base64"
|
5
|
+
require "execjs"
|
5
6
|
require "uglifier/version"
|
6
7
|
|
7
8
|
# A wrapper around the UglifyJS interface
|
8
9
|
class Uglifier
|
9
10
|
# Error class for compilation errors.
|
10
11
|
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
12
|
|
77
13
|
# UglifyJS source path
|
78
14
|
SourcePath = File.expand_path("../uglify.js", __FILE__)
|
15
|
+
# Source Map path
|
16
|
+
SourceMapPath = File.expand_path("../source-map.js", __FILE__)
|
79
17
|
# ES5 shims source path
|
80
18
|
ES5FallbackPath = File.expand_path("../es5.js", __FILE__)
|
81
19
|
# String.split shim source path
|
82
20
|
SplitFallbackPath = File.expand_path("../split.js", __FILE__)
|
21
|
+
# UglifyJS wrapper path
|
22
|
+
UglifyJSWrapperPath = File.expand_path("../uglifier.js", __FILE__)
|
83
23
|
|
84
24
|
# Default options for compilation
|
85
25
|
DEFAULTS = {
|
@@ -104,8 +44,10 @@ class Uglifier
|
|
104
44
|
:eval => false, # Mangle names when eval of when is used in scope
|
105
45
|
:except => ["$super"], # Argument names to be excluded from mangling
|
106
46
|
: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
|
47
|
+
:toplevel => false, # Mangle names declared in the toplevel scope
|
48
|
+
:properties => false # Mangle property names
|
108
49
|
}, # Mangle variable and function names, set to false to skip mangling
|
50
|
+
:mangle_properties => false, # Mangle property names
|
109
51
|
:compress => {
|
110
52
|
:sequences => true, # Allow statements to be joined by commas
|
111
53
|
:properties => true, # Rewrite property access using the dot notation
|
@@ -123,23 +65,37 @@ class Uglifier
|
|
123
65
|
:if_return => true, # Optimizations for if/return and if/continue
|
124
66
|
:join_vars => true, # Join consecutive var statements
|
125
67
|
:cascade => true, # Cascade sequences
|
68
|
+
:collapse_vars => false, # Collapse single-use var and const definitions when possible.
|
126
69
|
:negate_iife => true, # Negate immediately invoked function expressions to avoid extra parens
|
127
70
|
:pure_getters => false, # Assume that object property access does not have any side-effects
|
128
71
|
:pure_funcs => nil, # List of functions without side-effects. Can safely discard function calls when the result value is not used
|
129
72
|
:drop_console => false, # Drop calls to console.* functions
|
130
73
|
:angular => false, # Process @ngInject annotations
|
131
|
-
:keep_fargs => false # Preserve unused function arguments
|
74
|
+
:keep_fargs => false, # Preserve unused function arguments
|
75
|
+
:keep_fnames => false # Preserve function names
|
132
76
|
}, # Apply transformations to code, set to false to skip
|
133
77
|
:define => {}, # Define values for symbol replacement
|
134
78
|
:enclose => false, # Enclose in output function wrapper, define replacements as key-value pairs
|
135
|
-
:source_filename => nil, # The filename of the input file
|
136
|
-
:source_root => nil, # The URL of the directory which contains :source_filename
|
137
|
-
:output_filename => nil, # The filename or URL where the minified output can be found
|
138
|
-
:input_source_map => nil, # The contents of the source map describing the input
|
139
79
|
:screw_ie8 => false, # Don't bother to generate safe code for IE8
|
140
|
-
:
|
141
|
-
|
80
|
+
:source_map => false # Generate source map
|
81
|
+
}
|
82
|
+
|
83
|
+
LEGACY_OPTIONS = [:comments, :squeeze, :copyright, :mangle]
|
84
|
+
|
85
|
+
MANGLE_PROPERTIES_DEFAULTS = {
|
86
|
+
:regex => nil # A regular expression to filter property names to be mangled
|
142
87
|
}
|
88
|
+
|
89
|
+
SOURCE_MAP_DEFAULTS = {
|
90
|
+
:map_url => false, # Url for source mapping to be appended in minified source
|
91
|
+
:url => false, # Url for original source to be appended in minified source
|
92
|
+
:sources_content => false, # Include original source content in map
|
93
|
+
:filename => nil, # The filename of the input file
|
94
|
+
:root => nil, # The URL of the directory which contains :filename
|
95
|
+
:output_filename => nil, # The filename or URL where the minified output can be found
|
96
|
+
:input_source_map => nil # The contents of the source map describing the input
|
97
|
+
}
|
98
|
+
|
143
99
|
# rubocop:enable LineLength
|
144
100
|
|
145
101
|
# Minifies JavaScript code using implicit context.
|
@@ -164,7 +120,7 @@ class Uglifier
|
|
164
120
|
#
|
165
121
|
# @param options [Hash] optional overrides to +Uglifier::DEFAULTS+
|
166
122
|
def initialize(options = {})
|
167
|
-
(options.keys - DEFAULTS.keys -
|
123
|
+
(options.keys - DEFAULTS.keys - LEGACY_OPTIONS)[0..1].each do |missing|
|
168
124
|
raise ArgumentError, "Invalid option: #{missing}"
|
169
125
|
end
|
170
126
|
@options = options
|
@@ -176,7 +132,14 @@ class Uglifier
|
|
176
132
|
# @param source [IO, String] valid JS source code.
|
177
133
|
# @return [String] minified code.
|
178
134
|
def compile(source)
|
179
|
-
|
135
|
+
if @options[:source_map]
|
136
|
+
compiled, source_map = run_uglifyjs(source, true)
|
137
|
+
source_map_uri = Base64.strict_encode64(source_map)
|
138
|
+
source_map_mime = "application/json;charset=utf-8;base64"
|
139
|
+
compiled + "\n//# sourceMappingURL=data:#{source_map_mime},#{source_map_uri}"
|
140
|
+
else
|
141
|
+
run_uglifyjs(source, false)
|
142
|
+
end
|
180
143
|
end
|
181
144
|
alias_method :compress, :compile
|
182
145
|
|
@@ -191,25 +154,29 @@ class Uglifier
|
|
191
154
|
private
|
192
155
|
|
193
156
|
def uglifyjs_source
|
194
|
-
[ES5FallbackPath, SplitFallbackPath, SourcePath
|
195
|
-
|
157
|
+
[ES5FallbackPath, SplitFallbackPath, SourceMapPath, SourcePath,
|
158
|
+
UglifyJSWrapperPath].map do |file|
|
159
|
+
File.open(file, "r:UTF-8", &:read)
|
196
160
|
end.join("\n")
|
197
161
|
end
|
198
162
|
|
199
163
|
# Run UglifyJS for given source code
|
200
|
-
def run_uglifyjs(
|
164
|
+
def run_uglifyjs(input, generate_map)
|
165
|
+
source = read_source(input)
|
166
|
+
input_map = input_source_map(source, generate_map)
|
201
167
|
options = {
|
202
|
-
:source =>
|
168
|
+
:source => source,
|
203
169
|
:output => output_options,
|
204
170
|
:compress => compressor_options,
|
205
171
|
:mangle => mangle_options,
|
172
|
+
:mangle_properties => mangle_properties_options,
|
206
173
|
:parse_options => parse_options,
|
207
|
-
:source_map_options => source_map_options,
|
174
|
+
:source_map_options => source_map_options(input_map),
|
208
175
|
:generate_map => generate_map,
|
209
176
|
:enclose => enclose_options
|
210
177
|
}
|
211
178
|
|
212
|
-
@context.call(
|
179
|
+
@context.call("uglifier", options)
|
213
180
|
end
|
214
181
|
|
215
182
|
def read_source(source)
|
@@ -221,14 +188,25 @@ class Uglifier
|
|
221
188
|
end
|
222
189
|
|
223
190
|
def mangle_options
|
224
|
-
|
191
|
+
mangle_options = @options.fetch(:mangle, @options[:mangle])
|
192
|
+
conditional_option(mangle_options, DEFAULTS[:mangle])
|
193
|
+
end
|
194
|
+
|
195
|
+
def mangle_properties_options
|
196
|
+
mangle_options = @options.fetch(:mangle_properties, DEFAULTS[:mangle_properties])
|
197
|
+
options = conditional_option(mangle_options, MANGLE_PROPERTIES_DEFAULTS)
|
198
|
+
if options && options[:regex]
|
199
|
+
options.merge(:regex => encode_regexp(options[:regex]))
|
200
|
+
else
|
201
|
+
options
|
202
|
+
end
|
225
203
|
end
|
226
204
|
|
227
205
|
def compressor_options
|
228
206
|
defaults = conditional_option(
|
229
207
|
DEFAULTS[:compress],
|
230
208
|
:global_defs => @options[:define] || {},
|
231
|
-
:screw_ie8 =>
|
209
|
+
:screw_ie8 => screw_ie8?
|
232
210
|
)
|
233
211
|
conditional_option(@options[:compress] || @options[:squeeze], defaults)
|
234
212
|
end
|
@@ -269,24 +247,31 @@ class Uglifier
|
|
269
247
|
|
270
248
|
def screw_ie8?
|
271
249
|
if (@options[:output] || {}).has_key?(:ie_proof)
|
272
|
-
|
250
|
+
!@options[:output][:ie_proof]
|
273
251
|
else
|
274
|
-
@options
|
252
|
+
@options.fetch(:screw_ie8, DEFAULTS[:screw_ie8])
|
275
253
|
end
|
276
254
|
end
|
277
255
|
|
278
|
-
def source_map_options
|
256
|
+
def source_map_options(input_map)
|
257
|
+
options = conditional_option(@options[:source_map], SOURCE_MAP_DEFAULTS)
|
258
|
+
|
279
259
|
{
|
280
|
-
:file =>
|
281
|
-
:root =>
|
282
|
-
:orig =>
|
283
|
-
:map_url =>
|
284
|
-
:url =>
|
260
|
+
:file => options[:output_filename],
|
261
|
+
:root => options.fetch(:root) { input_map ? input_map["sourceRoot"] : nil },
|
262
|
+
:orig => input_map,
|
263
|
+
:map_url => options[:map_url],
|
264
|
+
:url => options[:url],
|
265
|
+
:sources_content => options[:sources_content]
|
285
266
|
}
|
286
267
|
end
|
287
268
|
|
288
269
|
def parse_options
|
289
|
-
|
270
|
+
if @options[:source_map].respond_to?(:[])
|
271
|
+
{ :filename => @options[:source_map][:filename] }
|
272
|
+
else
|
273
|
+
{}
|
274
|
+
end
|
290
275
|
end
|
291
276
|
|
292
277
|
def enclose_options
|
@@ -318,4 +303,38 @@ class Uglifier
|
|
318
303
|
false
|
319
304
|
end
|
320
305
|
end
|
306
|
+
|
307
|
+
def sanitize_map_root(map)
|
308
|
+
if map.nil?
|
309
|
+
nil
|
310
|
+
elsif map.is_a? String
|
311
|
+
sanitize_map_root(JSON.load(map))
|
312
|
+
elsif map["sourceRoot"] == ""
|
313
|
+
map.merge("sourceRoot" => nil)
|
314
|
+
else
|
315
|
+
map
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def extract_source_mapping_url(source)
|
320
|
+
comment_start = %r{(?://|/\*\s*)}
|
321
|
+
comment_end = %r{\s*(?:\r?\n?\*/|$)?}
|
322
|
+
source_mapping_regex = /#{comment_start}[@#]\ssourceMappingURL=\s*(\S*?)#{comment_end}/
|
323
|
+
rest = /\s#{comment_start}[@#]\s[a-zA-Z]+=\s*(?:\S*?)#{comment_end}/
|
324
|
+
regex = /#{source_mapping_regex}(?:#{rest})*\Z/m
|
325
|
+
match = regex.match(source)
|
326
|
+
match && match[1]
|
327
|
+
end
|
328
|
+
|
329
|
+
def input_source_map(source, generate_map)
|
330
|
+
return nil unless generate_map
|
331
|
+
sanitize_map_root(@options.fetch(:source_map, {}).fetch(:input_source_map) do
|
332
|
+
url = extract_source_mapping_url(source)
|
333
|
+
if url && url.start_with?("data:")
|
334
|
+
Base64.strict_decode64(url.split(",", 2)[-1])
|
335
|
+
end
|
336
|
+
end)
|
337
|
+
rescue ArgumentError, JSON::ParserError
|
338
|
+
nil
|
339
|
+
end
|
321
340
|
end
|