lib/uglifier.rb in uglifier-1.3.0 vs lib/uglifier.rb in uglifier-2.0.0
- old
+ new
@@ -3,34 +3,55 @@
require "execjs"
require "multi_json"
class Uglifier
Error = ExecJS::Error
- # MultiJson.engine = :json_gem
# Default options for compilation
DEFAULTS = {
- :mangle => true, # Mangle variable and function names, use :vars to skip function mangling
- :toplevel => false, # Mangle top-level variable names
- :except => ["$super"], # Variable names to be excluded from mangling
- :max_line_length => 32 * 1024, # Maximum line length
- :squeeze => true, # Squeeze code resulting in smaller, but less-readable code
- :seqs => true, # Reduce consecutive statements in blocks into single statement
- :dead_code => true, # Remove dead code (e.g. after return)
- :lift_vars => false, # Lift all var declarations at the start of the scope
- :unsafe => false, # Optimizations known to be unsafe in some situations
- :copyright => true, # Show copyright message
- :ascii_only => false, # Encode non-ASCII characters as Unicode code points
- :inline_script => false, # Escape </script
- :quote_keys => false, # Quote keys in object literals
+ :output => {
+ :ascii_only => false, # Escape non-ASCII characterss
+ :comments => :copyright, # Preserve comments (:all, :jsdoc, :copyright, :none)
+ :inline_script => false, # Escape occurrences of </script in strings
+ :quote_keys => false, # Quote keys in object literals
+ :max_line_len => 32 * 1024, # Maximum line length in minified code
+ :ie_proof => true, # Output block brakcets around do-while loops
+ :bracketize => false, # Bracketize if, for, do, while or with statements, even if their body is a single statement
+ :semicolons => true, # Separate statements with semicolons
+ :preserve_line => false, # Preserve line numbers in outputs
+ :beautify => false, # Beautify output
+ :indent_level => 4, # Indent level in spaces
+ :indent_start => 0, # Starting indent level
+ :space_colon => false, # Insert space before colons (only with beautifier)
+ :width => 80 # Specify line width when beautifier is used (only with beautifier)
+ },
+ :mangle => {
+ :except => ["$super"] # Argument names to be excluded from mangling
+ }, # Mangle variable and function names, set to false to skip mangling
+ :compress => {
+ :sequences => true, # Allow statements to be joined by commas
+ :properties => true, # Rewrite property access using the dot notation
+ :dead_code => true, # Remove unreachable code
+ :drop_debugger => true, # Remove debugger; statements
+ :unsafe => false, # Apply "unsafe" transformations
+ :conditionals => true, # Optimize for if-s and conditional expressions
+ :comparisons => true, # Apply binary node optimizations for comparisons
+ :evaluate => true, # Attempt to evaluate constant expressions
+ :booleans => true, # Various optimizations to boolean contexts
+ :loops => true, # Optimize lops when condition can be statically determined
+ :unused => true, # Drop unreferenced functions and variables
+ :hoist_funs => true, # Hoist function declarations
+ :hoist_vars => false, # Hoist var declarations
+ :if_return => true, # Optimizations for if/return and if/continue
+ :join_vars => true, # Join consecutive var statements
+ :cascade => true # Cascade sequences
+ }, # Apply transformations to code, set to false to skip
:define => {}, # Define values for symbol replacement
- :beautify => false, # Ouput indented code
- :beautify_options => {
- :indent_level => 4,
- :indent_start => 0,
- :space_colon => false
- }
+ :source_filename => nil, # The filename of the input file
+ :source_root => nil, # The URL of the directory which contains :source_filename
+ :output_filename => nil, # The filename or URL where the minified output can be found
+ :input_source_map => nil # The contents of the source map describing the input
}
SourcePath = File.expand_path("../uglify.js", __FILE__)
ES5FallbackPath = File.expand_path("../es5.js", __FILE__)
@@ -42,122 +63,196 @@
# Returns minified code as String
def self.compile(source, options = {})
self.new(options).compile(source)
end
+ # Minifies JavaScript code and generates a source map using implicit context.
+ #
+ # source should be a String or IO object containing valid JavaScript.
+ # options contain optional overrides to Uglifier::DEFAULTS
+ #
+ # Returns a pair of [minified code as String, source map as a String]
+ def self.compile_with_map(source, options = {})
+ self.new(options).compile_with_map(source)
+ end
+
# Initialize new context for Uglifier with given options
#
# options - Hash of options to override Uglifier::DEFAULTS
def initialize(options = {})
- @options = DEFAULTS.merge(options)
+ (options.keys - DEFAULTS.keys - [:comments, :squeeze])[0..1].each do |missing|
+ raise ArgumentError.new("Invalid option: #{missing}")
+ end
+ @options = options
@context = ExecJS.compile(File.open(ES5FallbackPath, "r:UTF-8").read + File.open(SourcePath, "r:UTF-8").read)
end
# Minifies JavaScript code
#
# source should be a String or IO object containing valid JavaScript.
#
# Returns minified code as String
def compile(source)
- source = source.respond_to?(:read) ? source.read : source.to_s
+ really_compile(source, false)
+ end
+ alias_method :compress, :compile
- js = []
- js << "var result = '';"
- js << "var source = #{json_encode(source)};"
- js << "var ast = UglifyJS.parser.parse(source);"
+ # Minifies JavaScript code and generates a source map
+ #
+ # source should be a String or IO object containing valid JavaScript.
+ #
+ # Returns a pair of [minified code as String, source map as a String]
+ def compile_with_map(source)
+ really_compile(source, true)
+ end
- if @options[:lift_vars]
- js << "ast = UglifyJS.uglify.ast_lift_variables(ast);"
- end
+ private
- if @options[:copyright]
- js << <<-JS
- var comments = UglifyJS.parser.tokenizer(source)().comments_before;
- for (var i = 0; i < comments.length; i++) {
- var c = comments[i];
- result += (c.type == "comment1") ? "//"+c.value+"\\n" : "/*"+c.value+"*/\\n";
+ # Minifies JavaScript code
+ #
+ # source should be a String or IO object containing valid JavaScript.
+ def really_compile(source, generate_map)
+ source = source.respond_to?(:read) ? source.read : source.to_s
+
+ js = <<-JS
+ function comments(option) {
+ if (Object.prototype.toString.call(option) === '[object Array]') {
+ return new RegExp(option[0], option[1]);
+ } else if (option == "jsdoc") {
+ return function(node, comment) {
+ if (comment.type == "comment2") {
+ return /@preserve|@license|@cc_on/i.test(comment.value);
+ } else {
+ return false;
+ }
+ }
+ } else {
+ return option;
+ }
}
- JS
- end
- js << "ast = UglifyJS.uglify.ast_mangle(ast, #{json_encode(mangle_options)});"
+ var options = %s;
+ var source = options.source;
+ var ast = UglifyJS.parse(source, options.parse_options);
+ ast.figure_out_scope();
- if @options[:squeeze]
- js << "ast = UglifyJS.uglify.ast_squeeze(ast, #{json_encode(squeeze_options)});"
- end
+ if (options.compress) {
+ var compressor = UglifyJS.Compressor(options.compress);
+ ast = ast.transform(compressor);
+ ast.figure_out_scope();
+ }
- if @options[:unsafe]
- js << "ast = UglifyJS.uglify.ast_squeeze_more(ast);"
- end
+ if (options.mangle) {
+ ast.compute_char_frequency();
+ ast.mangle_names(options.mangle);
+ }
- js << "result += UglifyJS.uglify.gen_code(ast, #{json_encode(gen_code_options)});"
+ var gen_code_options = options.output;
+ gen_code_options.comments = comments(options.output.comments);
- if !@options[:beautify] && @options[:max_line_length]
- js << "result = UglifyJS.uglify.split_lines(result, #{@options[:max_line_length].to_i})"
- end
+ if (options.generate_map) {
+ var source_map = UglifyJS.SourceMap(options.source_map_options);
+ gen_code_options.source_map = source_map;
+ }
- js << "return result + ';';"
+ var stream = UglifyJS.OutputStream(gen_code_options);
- @context.exec js.join("\n")
+ ast.print(stream);
+ if (options.generate_map) {
+ return [stream.toString(), source_map.toString()];
+ } else {
+ return stream.toString();
+ }
+ JS
+
+ @context.exec(js % json_encode(
+ :source => source,
+ :output => output_options,
+ :compress => compressor_options,
+ :mangle => mangle_options,
+ :parse_options => parse_options,
+ :source_map_options => source_map_options,
+ :generate_map => (!!generate_map)
+ ))
end
- alias_method :compress, :compile
- private
-
def mangle_options
- {
- "mangle" => @options[:mangle],
- "toplevel" => @options[:toplevel],
- "defines" => defines,
- "except" => @options[:except],
- "no_functions" => @options[:mangle] == :vars
- }
+ conditional_option(@options[:mangle], DEFAULTS[:mangle])
end
- def squeeze_options
- {
- "make_seqs" => @options[:seqs],
- "dead_code" => @options[:dead_code],
- "keep_comps" => !@options[:unsafe]
- }
+ def compressor_options
+ defaults = conditional_option(DEFAULTS[:compress],
+ :global_defs => @options[:define] || {})
+ conditional_option(@options[:compress] || @options[:squeeze], defaults)
end
- def defines
- Hash[(@options[:define] || {}).map do |k, v|
- token = if v.is_a? Numeric
- ['num', v]
- elsif [true, false].include?(v)
- ['name', v.to_s]
- elsif v == nil
- ['name', 'null']
- else
- ['string', v.to_s]
- end
- [k, token]
- end]
+ def comment_options
+ val = if @options.has_key?(:output) && @options[:output].has_key?(:comments)
+ @options[:output][:comments]
+ elsif @options.has_key?(:comments)
+ @options[:comments]
+ else
+ DEFAULTS[:output][:comments]
+ end
+
+ case val
+ when :all, true
+ true
+ when :jsdoc
+ "jsdoc"
+ when :copyright
+ encode_regexp(/Copyright/i)
+ when Regexp
+ encode_regexp(val)
+ else
+ false
+ end
end
- def gen_code_options
- options = {
- :ascii_only => @options[:ascii_only],
- :inline_script => @options[:inline_script],
- :quote_keys => @options[:quote_keys]
+ def output_options
+ DEFAULTS[:output].merge(@options[:output] || {}).merge(
+ :comments => comment_options)
+ end
+
+ def source_map_options
+ {
+ :file => @options[:output_filename],
+ :root => @options[:source_root],
+ :orig => @options[:input_source_map]
}
+ end
- if @options[:beautify]
- options.merge(:beautify => true).merge(@options[:beautify_options])
- else
- options
- end
+ def parse_options
+ {:filename => @options[:source_filename]}
end
# MultiJson API detection
if MultiJson.respond_to? :dump
def json_encode(obj)
MultiJson.dump(obj)
end
else
def json_encode(obj)
MultiJson.encode(obj)
+ end
+ end
+
+ def encode_regexp(regexp)
+ modifiers = if regexp.casefold?
+ "i"
+ else
+ ""
+ end
+
+ [regexp.source, modifiers]
+ end
+
+ def conditional_option(value, defaults)
+ if value == true || value == nil
+ defaults
+ elsif value
+ defaults.merge(value)
+ else
+ false
end
end
end