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.
@@ -1,4 +1,4 @@
1
1
  class Uglifier
2
2
  # Current version of Uglifier.
3
- VERSION = "2.7.1"
3
+ VERSION = "3.0.1"
4
4
  end
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
- :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
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 - [:comments, :squeeze, :copyright])[0..1].each do |missing|
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
- run_uglifyjs(source, false)
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].map do |file|
195
- File.open(file, "r:UTF-8") { |f| f.read }
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(source, generate_map)
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 => read_source(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(Uglifier::JS, options)
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
- conditional_option(@options[:mangle], DEFAULTS[:mangle])
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 => @options[:screw_ie8] || DEFAULTS[: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
- false
250
+ !@options[:output][:ie_proof]
273
251
  else
274
- @options[:screw_ie8] || DEFAULTS[:screw_ie8]
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 => @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]
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
- { :filename => @options[:source_filename] }
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