uglifier 2.7.1 → 3.1.13

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.
@@ -1,4 +1,4 @@
1
1
  class Uglifier
2
2
  # Current version of Uglifier.
3
- VERSION = "2.7.1"
3
+ VERSION = "3.1.13"
4
4
  end
data/lib/uglifier.js ADDED
@@ -0,0 +1,120 @@
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
+ UglifyJS.base54.reset();
38
+ var ast = UglifyJS.parse(source, options.parse_options);
39
+
40
+ if (options.compress) {
41
+ var compress = { warnings: false };
42
+ UglifyJS.merge(compress, options.compress);
43
+ ast.figure_out_scope(options.mangle);
44
+ var compressor = UglifyJS.Compressor(compress);
45
+ ast = compressor.compress(ast);
46
+ ast.figure_out_scope();
47
+ }
48
+
49
+ if (options.mangle) {
50
+ ast.figure_out_scope(options.mangle);
51
+ ast.compute_char_frequency();
52
+ ast.mangle_names(options.mangle);
53
+ }
54
+
55
+ if (options.mangle_properties) {
56
+ var regex = regexOption(options.mangle_properties);
57
+ UglifyJS.mangle_properties(ast, {
58
+ reserved: [],
59
+ only_cache: false,
60
+ regex: regex,
61
+ debug: options.mangle_properties.debug,
62
+ ignore_quoted: options.mangle_properties.ignore_quoted
63
+ });
64
+ }
65
+
66
+ if (options.enclose) {
67
+ ast = ast.wrap_enclose(options.enclose);
68
+ }
69
+ return ast;
70
+ }
71
+
72
+ function copySourcesContent(sourceMap, options) {
73
+ sourceMap.get().setSourceContent(options.parse_options.filename, options.source);
74
+
75
+ var original = options.source_map_options.orig;
76
+
77
+ if (original && original.sources && original.sourcesContent) {
78
+ for(var i = 0; i < original.sources.length; i++) {
79
+ sourceMap.get().setSourceContent(original.sources[i], original.sourcesContent[i]);
80
+ }
81
+ }
82
+ }
83
+
84
+ function uglifier(options) {
85
+ var source = options.source;
86
+ var ast = parse(source, options);
87
+ var source_map;
88
+
89
+ var gen_code_options = options.output;
90
+ gen_code_options.comments = comments(options.output.comments);
91
+
92
+ if (options.generate_map) {
93
+ source_map = UglifyJS.SourceMap(options.source_map_options);
94
+ gen_code_options.source_map = source_map;
95
+
96
+ if (options.source_map_options.sources_content) {
97
+ copySourcesContent(source_map, options);
98
+ }
99
+ }
100
+
101
+ var stream = UglifyJS.OutputStream(gen_code_options);
102
+ ast.print(stream);
103
+
104
+ if (options.source_map_options.map_url) {
105
+ stream += "\n//# sourceMappingURL=" + options.source_map_options.map_url;
106
+ }
107
+
108
+ if (options.source_map_options.url) {
109
+ stream += "\n//# sourceURL=" + options.source_map_options.url;
110
+ }
111
+
112
+ if (options.generate_map) {
113
+ if (options.source_map_options.sources_content) {
114
+ source_map.get().setSourceContent(options.parse_options.filename, options.source);
115
+ }
116
+ return [stream.toString(), source_map.toString()];
117
+ } else {
118
+ return stream.toString();
119
+ }
120
+ }
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 = {
@@ -98,48 +38,75 @@ class Uglifier
98
38
  :indent_start => 0, # Starting indent level
99
39
  :space_colon => false, # Insert space before colons (only with beautifier)
100
40
  :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.
41
+ :preamble => nil, # Preamble for the generated JS file. Can be used to insert any code or comment.
42
+ :wrap_iife => false # Wrap IIFEs in parenthesis. Note: this disables the negate_iife compression option.
102
43
  },
103
44
  :mangle => {
104
45
  :eval => false, # Mangle names when eval of when is used in scope
105
46
  :except => ["$super"], # Argument names to be excluded from mangling
106
47
  :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
48
+ :toplevel => false, # Mangle names declared in the toplevel scope
49
+ :properties => false, # Mangle property names
50
+ :keep_fnames => false # Do not modify function names
108
51
  }, # Mangle variable and function names, set to false to skip mangling
52
+ :mangle_properties => false, # Mangle property names
109
53
  :compress => {
110
54
  :sequences => true, # Allow statements to be joined by commas
111
55
  :properties => true, # Rewrite property access using the dot notation
112
56
  :dead_code => true, # Remove unreachable code
113
57
  :drop_debugger => true, # Remove debugger; statements
114
58
  :unsafe => false, # Apply "unsafe" transformations
59
+ :unsafe_comps => false, # Reverse < and <= to > and >= to allow improved compression. This might be unsafe when an at least one of two operands is an object with computed values due the use of methods like get, or valueOf. This could cause change in execution order after operands in the comparison are switching. Compression only works if both comparisons and unsafe_comps are both set to true.
60
+ :unsafe_proto => false, # Optimize expressions like Array.prototype.slice.call(a) into [].slice.call(a)
115
61
  :conditionals => true, # Optimize for if-s and conditional expressions
116
62
  :comparisons => true, # Apply binary node optimizations for comparisons
117
63
  :evaluate => true, # Attempt to evaluate constant expressions
118
64
  :booleans => true, # Various optimizations to boolean contexts
119
65
  :loops => true, # Optimize loops when condition can be statically determined
120
- :unused => true, # Drop unreferenced functions and variables
66
+ :unused => true, # Drop unreferenced functions and variables (simple direct variable assignments do not count as references unless set to `"keep_assign"`)
67
+ :toplevel => false, # Drop unreferenced top-level functions and variables
68
+ :top_retain => [], # prevent specific toplevel functions and variables from `unused` removal
121
69
  :hoist_funs => true, # Hoist function declarations
122
70
  :hoist_vars => false, # Hoist var declarations
123
71
  :if_return => true, # Optimizations for if/return and if/continue
124
72
  :join_vars => true, # Join consecutive var statements
125
73
  :cascade => true, # Cascade sequences
74
+ :collapse_vars => true, # Collapse single-use var and const definitions when possible.
75
+ :reduce_vars => false, # Collapse variables assigned with and used as constant values.
126
76
  :negate_iife => true, # Negate immediately invoked function expressions to avoid extra parens
127
77
  :pure_getters => false, # Assume that object property access does not have any side-effects
128
78
  :pure_funcs => nil, # List of functions without side-effects. Can safely discard function calls when the result value is not used
129
79
  :drop_console => false, # Drop calls to console.* functions
130
80
  :angular => false, # Process @ngInject annotations
131
- :keep_fargs => false # Preserve unused function arguments
81
+ :keep_fargs => false, # Preserve unused function arguments
82
+ :keep_fnames => false, # Do not drop names in function definitions
83
+ :passes => 1 # Number of times to run compress. Raising the number of passes will increase compress time, but can produce slightly smaller code.
132
84
  }, # Apply transformations to code, set to false to skip
133
85
  :define => {}, # Define values for symbol replacement
134
86
  :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
87
+ :keep_fnames => false, # Generate code safe for the poor souls relying on Function.prototype.name at run-time. Sets both compress and mangle keep_fanems to true.
139
88
  :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
89
+ :source_map => false # Generate source map
90
+ }
91
+
92
+ LEGACY_OPTIONS = [:comments, :squeeze, :copyright, :mangle]
93
+
94
+ MANGLE_PROPERTIES_DEFAULTS = {
95
+ :regex => nil, # A regular expression to filter property names to be mangled
96
+ :ignore_quoted => false, # Only mangle unquoted property names
97
+ :debug => false # Mangle names with the original name still present
142
98
  }
99
+
100
+ SOURCE_MAP_DEFAULTS = {
101
+ :map_url => false, # Url for source mapping to be appended in minified source
102
+ :url => false, # Url for original source to be appended in minified source
103
+ :sources_content => false, # Include original source content in map
104
+ :filename => nil, # The filename of the input file
105
+ :root => nil, # The URL of the directory which contains :filename
106
+ :output_filename => nil, # The filename or URL where the minified output can be found
107
+ :input_source_map => nil # The contents of the source map describing the input
108
+ }
109
+
143
110
  # rubocop:enable LineLength
144
111
 
145
112
  # Minifies JavaScript code using implicit context.
@@ -164,7 +131,7 @@ class Uglifier
164
131
  #
165
132
  # @param options [Hash] optional overrides to +Uglifier::DEFAULTS+
166
133
  def initialize(options = {})
167
- (options.keys - DEFAULTS.keys - [:comments, :squeeze, :copyright])[0..1].each do |missing|
134
+ (options.keys - DEFAULTS.keys - LEGACY_OPTIONS)[0..1].each do |missing|
168
135
  raise ArgumentError, "Invalid option: #{missing}"
169
136
  end
170
137
  @options = options
@@ -176,7 +143,14 @@ class Uglifier
176
143
  # @param source [IO, String] valid JS source code.
177
144
  # @return [String] minified code.
178
145
  def compile(source)
179
- run_uglifyjs(source, false)
146
+ if @options[:source_map]
147
+ compiled, source_map = run_uglifyjs(source, true)
148
+ source_map_uri = Base64.strict_encode64(source_map)
149
+ source_map_mime = "application/json;charset=utf-8;base64"
150
+ compiled + "\n//# sourceMappingURL=data:#{source_map_mime},#{source_map_uri}"
151
+ else
152
+ run_uglifyjs(source, false)
153
+ end
180
154
  end
181
155
  alias_method :compress, :compile
182
156
 
@@ -191,25 +165,29 @@ class Uglifier
191
165
  private
192
166
 
193
167
  def uglifyjs_source
194
- [ES5FallbackPath, SplitFallbackPath, SourcePath].map do |file|
195
- File.open(file, "r:UTF-8") { |f| f.read }
168
+ [ES5FallbackPath, SplitFallbackPath, SourceMapPath, SourcePath,
169
+ UglifyJSWrapperPath].map do |file|
170
+ File.open(file, "r:UTF-8", &:read)
196
171
  end.join("\n")
197
172
  end
198
173
 
199
174
  # Run UglifyJS for given source code
200
- def run_uglifyjs(source, generate_map)
175
+ def run_uglifyjs(input, generate_map)
176
+ source = read_source(input)
177
+ input_map = input_source_map(source, generate_map)
201
178
  options = {
202
- :source => read_source(source),
179
+ :source => source,
203
180
  :output => output_options,
204
181
  :compress => compressor_options,
205
182
  :mangle => mangle_options,
183
+ :mangle_properties => mangle_properties_options,
206
184
  :parse_options => parse_options,
207
- :source_map_options => source_map_options,
185
+ :source_map_options => source_map_options(input_map),
208
186
  :generate_map => generate_map,
209
187
  :enclose => enclose_options
210
188
  }
211
189
 
212
- @context.call(Uglifier::JS, options)
190
+ @context.call("uglifier", options)
213
191
  end
214
192
 
215
193
  def read_source(source)
@@ -221,16 +199,49 @@ class Uglifier
221
199
  end
222
200
 
223
201
  def mangle_options
224
- conditional_option(@options[:mangle], DEFAULTS[:mangle])
202
+ defaults = conditional_option(
203
+ DEFAULTS[:mangle],
204
+ :keep_fnames => keep_fnames?(:mangle)
205
+ )
206
+
207
+ conditional_option(
208
+ @options.fetch(:mangle, DEFAULTS[:mangle]),
209
+ defaults,
210
+ :keep_fnames => keep_fnames?(:mangle)
211
+ )
212
+ end
213
+
214
+ def mangle_properties_options
215
+ mangle_options = @options.fetch(:mangle_properties, DEFAULTS[:mangle_properties])
216
+ options = conditional_option(mangle_options, MANGLE_PROPERTIES_DEFAULTS)
217
+ if options && options[:regex]
218
+ options.merge(:regex => encode_regexp(options[:regex]))
219
+ else
220
+ options
221
+ end
225
222
  end
226
223
 
227
224
  def compressor_options
228
225
  defaults = conditional_option(
229
226
  DEFAULTS[:compress],
230
227
  :global_defs => @options[:define] || {},
231
- :screw_ie8 => @options[:screw_ie8] || DEFAULTS[:screw_ie8]
228
+ :screw_ie8 => screw_ie8?
229
+ )
230
+
231
+ conditional_option(
232
+ @options[:compress] || @options[:squeeze],
233
+ defaults,
234
+ { :keep_fnames => keep_fnames?(:compress) }.merge(negate_iife_block)
232
235
  )
233
- conditional_option(@options[:compress] || @options[:squeeze], defaults)
236
+ end
237
+
238
+ # Prevent negate_iife when wrap_iife is true
239
+ def negate_iife_block
240
+ if output_options[:wrap_iife]
241
+ { :negate_iife => false }
242
+ else
243
+ {}
244
+ end
234
245
  end
235
246
 
236
247
  def comment_options
@@ -269,24 +280,40 @@ class Uglifier
269
280
 
270
281
  def screw_ie8?
271
282
  if (@options[:output] || {}).has_key?(:ie_proof)
272
- false
283
+ !@options[:output][:ie_proof]
273
284
  else
274
- @options[:screw_ie8] || DEFAULTS[:screw_ie8]
285
+ @options.fetch(:screw_ie8, DEFAULTS[:screw_ie8])
275
286
  end
276
287
  end
277
288
 
278
- def source_map_options
289
+ def keep_fnames?(type)
290
+ if @options[:keep_fnames] || DEFAULTS[:keep_fnames]
291
+ true
292
+ else
293
+ @options[type].respond_to?(:[]) && @options[type][:keep_fnames] ||
294
+ DEFAULTS[type].respond_to?(:[]) && DEFAULTS[type][:keep_fnames]
295
+ end
296
+ end
297
+
298
+ def source_map_options(input_map)
299
+ options = conditional_option(@options[:source_map], SOURCE_MAP_DEFAULTS) || SOURCE_MAP_DEFAULTS
300
+
279
301
  {
280
- :file => @options[:output_filename],
281
- :root => @options[:source_root],
282
- :orig => @options[:input_source_map],
283
- :map_url => @options[:source_map_url],
284
- :url => @options[:source_url]
302
+ :file => options[:output_filename],
303
+ :root => options.fetch(:root) { input_map ? input_map["sourceRoot"] : nil },
304
+ :orig => input_map,
305
+ :map_url => options[:map_url],
306
+ :url => options[:url],
307
+ :sources_content => options[:sources_content]
285
308
  }
286
309
  end
287
310
 
288
311
  def parse_options
289
- { :filename => @options[:source_filename] }
312
+ if @options[:source_map].respond_to?(:[])
313
+ { :filename => @options[:source_map][:filename] }
314
+ else
315
+ {}
316
+ end
290
317
  end
291
318
 
292
319
  def enclose_options
@@ -309,13 +336,48 @@ class Uglifier
309
336
  [regexp.source, modifiers]
310
337
  end
311
338
 
312
- def conditional_option(value, defaults)
339
+ def conditional_option(value, defaults, overrides = {})
313
340
  if value == true || value.nil?
314
- defaults
341
+ defaults.merge(overrides)
315
342
  elsif value
316
- defaults.merge(value)
343
+ defaults.merge(value).merge(overrides)
317
344
  else
318
345
  false
319
346
  end
320
347
  end
348
+
349
+ def sanitize_map_root(map)
350
+ if map.nil?
351
+ nil
352
+ elsif map.is_a? String
353
+ sanitize_map_root(JSON.parse(map))
354
+ elsif map["sourceRoot"] == ""
355
+ map.merge("sourceRoot" => nil)
356
+ else
357
+ map
358
+ end
359
+ end
360
+
361
+ def extract_source_mapping_url(source)
362
+ comment_start = %r{(?://|/\*\s*)}
363
+ comment_end = %r{\s*(?:\r?\n?\*/|$)?}
364
+ source_mapping_regex = /#{comment_start}[@#]\ssourceMappingURL=\s*(\S*?)#{comment_end}/
365
+ rest = /\s#{comment_start}[@#]\s[a-zA-Z]+=\s*(?:\S*?)#{comment_end}/
366
+ regex = /#{source_mapping_regex}(?:#{rest})*\Z/m
367
+ match = regex.match(source)
368
+ match && match[1]
369
+ end
370
+
371
+ def input_source_map(source, generate_map)
372
+ return nil unless generate_map
373
+ source_map_options = @options[:source_map].is_a?(Hash) ? @options[:source_map] : {}
374
+ sanitize_map_root(source_map_options.fetch(:input_source_map) do
375
+ url = extract_source_mapping_url(source)
376
+ if url && url.start_with?("data:")
377
+ Base64.strict_decode64(url.split(",", 2)[-1])
378
+ end
379
+ end)
380
+ rescue ArgumentError, JSON::ParserError
381
+ nil
382
+ end
321
383
  end