uglifier 2.7.1 → 3.0.1
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 +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
|