uglifier 2.2.1 → 2.7.2

Sign up to get free protection for your applications and to get access to all the features.
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)