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.
data/lib/uglifier.rb CHANGED
@@ -1,15 +1,91 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require "execjs"
4
- require "multi_json"
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 => false, # Escape non-ASCII characterss
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
- :except => ["$super"] # Argument names to be excluded from mangling
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 lops when condition can be statically determined
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 invoke function expressions
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 # Generate safe code for IE8
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 should be a String or IO object containing valid JavaScript.
64
- # options contain optional overrides to Uglifier::DEFAULTS
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
- self.new(options).compile(source)
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 should be a String or IO object containing valid JavaScript.
74
- # options contain optional overrides to Uglifier::DEFAULTS
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
- self.new(options).compile_with_map(source)
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 - Hash of options to override Uglifier::DEFAULTS
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.new("Invalid option: #{missing}")
168
+ raise ArgumentError, "Invalid option: #{missing}"
87
169
  end
88
170
  @options = options
89
- @context = ExecJS.compile(File.open(ES5FallbackPath, "r:UTF-8").read +
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 should be a String or IO object containing valid JavaScript.
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
- really_compile(source, false)
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 should be a String or IO object containing valid JavaScript.
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
- really_compile(source, true)
188
+ run_uglifyjs(source, true)
111
189
  end
112
190
 
113
191
  private
114
192
 
115
- # Minifies JavaScript code
116
- #
117
- # source should be a String or IO object containing valid JavaScript.
118
- def really_compile(source, generate_map)
119
- source = source.respond_to?(:read) ? source.read : source.to_s
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
- @context.exec(js % json_encode(
177
- :source => source,
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 => (!!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(DEFAULTS[:compress],
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
- val = if @options.has_key?(:output) && @options[:output].has_key?(:comments)
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(val)
245
+ encode_regexp(comment_setting)
220
246
  else
221
247
  false
222
248
  end
223
249
  end
224
250
 
225
- def output_options
226
- screw_ie8 = if (@options[:output] || {}).has_key?(:ie_proof)
227
- false
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
- @options[:screw_ie8] || DEFAULTS[:screw_ie8]
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,value| key == :ie_proof}
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
- "i"
274
- else
275
- ""
276
- end
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 == nil
313
+ if value == true || value.nil?
283
314
  defaults
284
315
  elsif value
285
316
  defaults.merge(value)