UglifyJS/bin/uglifyjs

594 lines
19 KiB
Plaintext
Raw Normal View History

#! /usr/bin/env node
// -*- js -*-
2012-10-02 09:45:31 +00:00
"use strict";
var UglifyJS = require("../tools/node");
var sys = require("util");
2015-01-01 06:04:54 +00:00
var yargs = require("yargs");
var fs = require("fs");
2013-08-07 08:43:47 +00:00
var path = require("path");
var async = require("async");
var acorn;
var screw_ie8 = true;
2015-01-01 06:04:54 +00:00
var ARGS = yargs
.usage("$0 input1.js [input2.js ...] [options]\n\
2012-09-03 16:43:46 +00:00
Use a single dash to read input from the standard input.\
\n\n\
NOTE: by default there is no mangling/compression.\n\
Without [options] it will simply parse input files and dump the AST\n\
with whitespace and comments discarded. To achieve compression and\n\
mangling you need to use `-c` and `-m`.\
2012-09-03 07:14:15 +00:00
")
.describe("source-map", "Specify an output file where to generate source map.")
.describe("source-map-root", "The path to the original source to be included in the source map.")
.describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.")
.describe("source-map-inline", "Write base64-encoded source map to the end of js output. Disabled by default")
.describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.")
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
.describe("screw-ie8", "Do not support Internet Explorer 6-8 quirks. This flag is enabled by default.")
.describe("support-ie8", "Support non-standard Internet Explorer 6-8 javascript. Note: may break standards compliant `catch` identifiers.")
.describe("expr", "Parse a single expression, rather than a program (for parsing JSON)")
.describe("p", "Skip prefix for original filenames that appear in source maps. \
2013-08-07 08:43:47 +00:00
For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \
You can also specify -p relative, which will make UglifyJS figure out itself the relative paths between original sources, \
the source map and the output file.")
.describe("o", "Output file (default STDOUT).")
.describe("b", "Beautify output/specify output options.")
.describe("m", "Mangle names/pass mangler options.")
.describe("r", "Reserved names to exclude from mangling.")
.describe("c", "Enable compressor/pass compressor options. \
Pass options like -c hoist_vars=false,if_return=false. \
2012-10-02 18:08:16 +00:00
Use -c with no argument to use the default compression options.")
2012-10-02 10:20:07 +00:00
.describe("d", "Global definitions")
.describe("e", "Embed everything in a big function, with a configurable parameter/argument list.")
.describe("comments", "Preserve copyright comments in the output. \
By default this works like Google Closure, keeping JSDoc-style comments that contain \"@license\" or \"@preserve\". \
You can optionally pass one of the following arguments to this flag:\n\
- \"all\" to keep all comments\n\
- a valid JS regexp (needs to start with a slash) to keep only comments that match.\n\
\
Note that currently not *all* comments can be kept when compression is on, \
because of dead code removal or cascading statements into sequences.")
.describe("preamble", "Preamble to prepend to the output. You can use this to insert a \
comment, for example for licensing information. This will not be \
parsed, but the source map will adjust for its presence.")
.describe("stats", "Display operations run time on STDERR.")
.describe("acorn", "Use Acorn for parsing.")
2013-05-02 09:15:33 +00:00
.describe("spidermonkey", "Assume input files are SpiderMonkey AST format (as JSON).")
.describe("self", "Build itself (UglifyJS2) as a library (implies --wrap=UglifyJS --export-all)")
.describe("wrap", "Embed everything in a big function, making the “exports” and “global” variables available. \
You need to pass an argument to this option to specify the name that your module will take when included in, say, a browser.")
.describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.")
.describe("lint", "Display some scope warnings")
.describe("v", "Verbose")
2013-01-03 10:06:07 +00:00
.describe("V", "Print version number and exit.")
.describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.")
.describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.")
.describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.")
.describe("quotes", "Quote style (0 - auto, 1 - single, 2 - double, 3 - original)")
.describe("reserved-file", "File containing reserved names")
.describe("reserve-domprops", "Make (most?) DOM properties reserved for --mangle-props")
.describe("mangle-props", "Mangle property names (0 - disabled, 1 - mangle all properties, 2 - mangle unquoted properies)")
2015-06-09 10:21:22 +00:00
.describe("mangle-regex", "Only mangle property names matching the regex")
.describe("name-cache", "File to hold mangled names mappings")
.describe("pure-funcs", "List of functions that can be safely removed if their return value is not used")
.describe("dump-spidermonkey-ast", "Dump SpiderMonkey AST to stdout.")
.describe("wrap-iife", "Wrap IIFEs in parenthesis. Note: this disables the negate_iife compression option")
2012-09-03 07:14:15 +00:00
.alias("p", "prefix")
.alias("o", "output")
.alias("v", "verbose")
.alias("b", "beautify")
.alias("m", "mangle")
.alias("c", "compress")
2012-10-02 10:20:07 +00:00
.alias("d", "define")
.alias("r", "reserved")
2013-01-03 10:06:07 +00:00
.alias("V", "version")
.alias("e", "enclose")
.alias("q", "quotes")
2012-09-03 07:14:15 +00:00
2012-10-02 18:08:16 +00:00
.string("source-map")
.string("source-map-root")
.string("source-map-url")
.string("b")
2015-03-29 10:50:00 +00:00
.string("beautify")
.string("m")
2015-03-29 10:50:00 +00:00
.string("mangle")
.string("c")
2015-03-29 10:50:00 +00:00
.string("compress")
2012-10-02 10:20:07 +00:00
.string("d")
2015-03-29 10:50:00 +00:00
.string("define")
.string("e")
2015-03-29 10:50:00 +00:00
.string("enclose")
.string("comments")
.string("wrap")
2013-08-07 08:43:47 +00:00
.string("p")
2015-03-29 10:50:00 +00:00
.string("prefix")
.string("name-cache")
2015-03-18 09:53:17 +00:00
.array("reserved-file")
.array("pure-funcs")
.boolean("expr")
.boolean("source-map-inline")
.boolean("source-map-include-sources")
.boolean("screw-ie8")
.boolean("support-ie8")
.boolean("export-all")
.boolean("self")
2012-09-03 09:03:45 +00:00
.boolean("v")
2015-03-29 10:50:00 +00:00
.boolean("verbose")
2012-09-05 15:19:24 +00:00
.boolean("stats")
.boolean("acorn")
.boolean("spidermonkey")
.boolean("dump-spidermonkey-ast")
.boolean("lint")
2013-01-03 10:22:37 +00:00
.boolean("V")
2015-03-29 10:50:00 +00:00
.boolean("version")
.boolean("noerr")
.boolean("bare-returns")
2015-01-05 09:20:00 +00:00
.boolean("keep-fnames")
.boolean("reserve-domprops")
.boolean("wrap-iife")
2012-09-03 09:03:45 +00:00
.wrap(80)
.argv
;
normalize(ARGS);
if (ARGS.noerr) {
UglifyJS.DefaultsError.croak = function(msg, defs) {
print_error("WARN: " + msg);
};
}
2013-01-03 10:06:07 +00:00
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
print(json.name + ' ' + json.version);
2013-01-03 10:06:07 +00:00
process.exit(0);
}
if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast();
print(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
process.exit(0);
}
if (ARGS.h || ARGS.help) {
print(yargs.help());
process.exit(0);
}
if (ARGS.acorn) {
acorn = require("acorn");
2012-09-13 16:45:16 +00:00
}
var COMPRESS = getOptions("c", true);
var MANGLE = getOptions("m", true);
var BEAUTIFY = getOptions("b", true);
2015-03-18 09:53:17 +00:00
var RESERVED = null;
if (ARGS.reserved_file) ARGS.reserved_file.forEach(function(filename){
RESERVED = UglifyJS.readReservedFile(filename, RESERVED);
});
if (ARGS.reserve_domprops) {
2015-03-22 09:04:28 +00:00
RESERVED = UglifyJS.readDefaultReservedFile(RESERVED);
}
if (ARGS.d) {
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
2012-10-02 10:20:07 +00:00
}
if (ARGS.pure_funcs) {
if (COMPRESS) COMPRESS.pure_funcs = ARGS.pure_funcs;
}
if (ARGS.r) {
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
}
2015-03-18 09:53:17 +00:00
if (RESERVED && MANGLE) {
if (!MANGLE.except) MANGLE.except = RESERVED.vars;
else MANGLE.except = MANGLE.except.concat(RESERVED.vars);
}
function readNameCache(key) {
2015-03-17 08:05:49 +00:00
return UglifyJS.readNameCache(ARGS.name_cache, key);
}
function writeNameCache(key, cache) {
2015-03-17 08:05:49 +00:00
return UglifyJS.writeNameCache(ARGS.name_cache, key, cache);
}
function extractRegex(str) {
if (/^\/.*\/[a-zA-Z]*$/.test(str)) {
var regex_pos = str.lastIndexOf("/");
return new RegExp(str.substr(1, regex_pos - 1), str.substr(regex_pos + 1));
} else {
throw new Error("Invalid regular expression: " + str);
}
}
if (ARGS.quotes === true) {
ARGS.quotes = 3;
}
if (ARGS.mangle_props === true) {
ARGS.mangle_props = 1;
} else if (ARGS.mangle_props === "unquoted") {
ARGS.mangle_props = 2;
}
var OUTPUT_OPTIONS = {
beautify : BEAUTIFY ? true : false,
preamble : ARGS.preamble || null,
quote_style : ARGS.quotes != null ? ARGS.quotes : 0
};
if (ARGS.mangle_props == 2) {
OUTPUT_OPTIONS.keep_quoted_props = true;
if (COMPRESS && !("properties" in COMPRESS))
COMPRESS.properties = false;
}
if (ARGS.support_ie8 === true && ARGS.screw_ie8 !== true) {
screw_ie8 = false;
}
if (COMPRESS) COMPRESS.screw_ie8 = screw_ie8;
if (MANGLE) MANGLE.screw_ie8 = screw_ie8;
OUTPUT_OPTIONS.screw_ie8 = screw_ie8;
if (ARGS.keep_fnames) {
if (COMPRESS) COMPRESS.keep_fnames = true;
if (MANGLE) MANGLE.keep_fnames = true;
}
if (ARGS.wrap_iife) {
if (COMPRESS) COMPRESS.negate_iife = false;
OUTPUT_OPTIONS.wrap_iife = true;
}
if (BEAUTIFY)
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
if (ARGS.comments === "") {
OUTPUT_OPTIONS.comments = "some";
} else {
OUTPUT_OPTIONS.comments = ARGS.comments;
}
var files = ARGS._.slice();
if (process.platform === "win32")
files = UglifyJS.simple_glob(files);
if (ARGS.self) {
if (files.length > 0) {
print_error("WARN: Ignoring input files since --self was passed");
}
files = UglifyJS.FILES;
if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
}
var ORIG_MAP = ARGS.in_source_map;
if (ORIG_MAP) {
ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
if (files.length == 0) {
print_error("INFO: Using file from the input source map: " + ORIG_MAP.file);
files = [ ORIG_MAP.file ];
}
if (ARGS.source_map_root == null) {
ARGS.source_map_root = ORIG_MAP.sourceRoot;
}
}
if (files.length == 0) {
files = [ "-" ];
}
if (files.indexOf("-") >= 0 && ARGS.source_map) {
print_error("ERROR: Source map doesn't work with input from STDIN");
process.exit(1);
}
if (files.filter(function(el){ return el == "-" }).length > 1) {
print_error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
process.exit(1);
}
var STATS = {};
var OUTPUT_FILE = ARGS.o;
var TOPLEVEL = null;
2013-08-07 08:43:47 +00:00
var P_RELATIVE = ARGS.p && ARGS.p == "relative";
var SOURCES_CONTENT = {};
var SOURCE_MAP = (ARGS.source_map || ARGS.source_map_inline) ? UglifyJS.SourceMap({
2013-08-07 08:43:47 +00:00
file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE,
root: ARGS.source_map_root,
orig: ORIG_MAP,
}) : null;
OUTPUT_OPTIONS.source_map = SOURCE_MAP;
try {
var output = UglifyJS.OutputStream(OUTPUT_OPTIONS);
var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS);
} catch(ex) {
if (ex instanceof UglifyJS.DefaultsError) {
print_error(ex.msg);
print_error("Supported options:");
print_error(sys.inspect(ex.defs));
process.exit(1);
}
}
async.eachLimit(files, 1, function (file, cb) {
read_whole_file(file, function (err, code) {
if (err) {
print_error("ERROR: can't read file: " + file);
process.exit(1);
}
if (ARGS.p != null) {
2013-08-07 08:43:47 +00:00
if (P_RELATIVE) {
file = path.relative(path.dirname(ARGS.source_map), file).replace(/\\/g, '/');
2013-08-07 08:43:47 +00:00
} else {
var p = parseInt(ARGS.p, 10);
if (!isNaN(p)) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
}
}
}
SOURCES_CONTENT[file] = code;
time_it("parse", function(){
if (ARGS.spidermonkey) {
var program = JSON.parse(code);
if (!TOPLEVEL) TOPLEVEL = program;
else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
}
else if (ARGS.acorn) {
TOPLEVEL = acorn.parse(code, {
locations : true,
sourceFile : file,
program : TOPLEVEL
});
}
else {
2013-09-02 06:55:34 +00:00
try {
TOPLEVEL = UglifyJS.parse(code, {
filename : file,
toplevel : TOPLEVEL,
expression : ARGS.expr,
bare_returns : ARGS.bare_returns,
2013-09-02 06:55:34 +00:00
});
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Parse error at " + file + ":" + ex.line + "," + ex.col);
print_error(ex.message);
print_error(ex.stack);
2013-09-02 06:55:34 +00:00
process.exit(1);
}
throw ex;
}
};
});
cb();
});
}, function () {
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
});
if (ARGS.wrap != null) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
if (ARGS.enclose != null) {
var arg_parameter_list = ARGS.enclose;
2013-07-28 08:11:11 +00:00
if (arg_parameter_list === true) {
arg_parameter_list = [];
}
else if (!(arg_parameter_list instanceof Array)) {
arg_parameter_list = [arg_parameter_list];
}
TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
}
if (ARGS.mangle_props || ARGS.name_cache) (function(){
var reserved = RESERVED ? RESERVED.props : null;
var cache = readNameCache("props");
var regex;
try {
regex = ARGS.mangle_regex ? extractRegex(ARGS.mangle_regex) : null;
} catch (e) {
print_error("ERROR: Invalid --mangle-regex: " + e.message);
process.exit(1);
}
TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, {
reserved : reserved,
cache : cache,
only_cache : !ARGS.mangle_props,
regex : regex,
ignore_quoted : ARGS.mangle_props == 2
});
writeNameCache("props", cache);
})();
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint
var TL_CACHE = readNameCache("vars");
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE });
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
});
}
2012-09-26 08:24:04 +00:00
if (COMPRESS) {
time_it("squeeze", function(){
TOPLEVEL = compressor.compress(TOPLEVEL);
});
}
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: screw_ie8, cache: TL_CACHE });
if (MANGLE && !TL_CACHE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
if (MANGLE) time_it("mangle", function(){
MANGLE.cache = TL_CACHE;
TOPLEVEL.mangle_names(MANGLE);
});
writeNameCache("vars", TL_CACHE);
if (ARGS.source_map_include_sources) {
for (var file in SOURCES_CONTENT) {
if (SOURCES_CONTENT.hasOwnProperty(file)) {
SOURCE_MAP.get().setSourceContent(file, SOURCES_CONTENT[file]);
}
}
}
if (ARGS.dump_spidermonkey_ast) {
print(JSON.stringify(TOPLEVEL.to_mozilla_ast(), null, 2));
} else {
time_it("generate", function(){
TOPLEVEL.print(output);
});
output = output.get();
2012-09-03 07:14:15 +00:00
if (SOURCE_MAP) {
if (ARGS.source_map_inline) {
var base64_string = new Buffer(SOURCE_MAP.toString()).toString('base64');
output += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + base64_string;
} else {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
var source_map_url = ARGS.source_map_url || (
P_RELATIVE
? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map)
: ARGS.source_map
);
output += "\n//# sourceMappingURL=" + source_map_url;
}
}
if (OUTPUT_FILE) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
print(output);
}
}
if (ARGS.stats) {
print_error(UglifyJS.string_template("Timing information (compressed {count} files):", {
count: files.length
}));
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
print_error(UglifyJS.string_template("- {name}: {time}s", {
name: i,
time: (STATS[i] / 1000).toFixed(3)
}));
}
}
});
/* -----[ functions ]----- */
function normalize(o) {
for (var i in o) if (o.hasOwnProperty(i) && /-/.test(i)) {
o[i.replace(/-/g, "_")] = o[i];
delete o[i];
}
}
function getOptions(flag, constants) {
var x = ARGS[flag];
if (x == null || x === false) return null;
var ret = {};
2015-03-18 09:53:17 +00:00
if (x !== "") {
if (Array.isArray(x)) x = x.map(function (v) { return "(" + v + ")"; }).join(", ");
var ast;
try {
ast = UglifyJS.parse(x, { expression: true });
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Error parsing arguments for flag `" + flag + "': " + x);
process.exit(1);
}
}
ast.walk(new UglifyJS.TreeWalker(function(node){
if (node instanceof UglifyJS.AST_Seq) return; // descend
if (node instanceof UglifyJS.AST_Assign) {
var name = node.left.print_to_string({ beautify: false }).replace(/-/g, "_");
var value = node.right;
if (constants)
value = new Function("return (" + value.print_to_string() + ")")();
ret[name] = value;
return true; // no descend
}
if (node instanceof UglifyJS.AST_Symbol || node instanceof UglifyJS.AST_Binary) {
var name = node.print_to_string({ beautify: false }).replace(/-/g, "_");
ret[name] = true;
return true; // no descend
}
print_error(node.TYPE)
print_error("Error parsing arguments for flag `" + flag + "': " + x);
process.exit(1);
}));
}
return ret;
}
function read_whole_file(filename, cb) {
if (filename == "-") {
var chunks = [];
process.stdin.setEncoding('utf-8');
process.stdin.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function () {
cb(null, chunks.join(""));
});
process.openStdin();
} else {
fs.readFile(filename, "utf-8", cb);
}
}
function time_it(name, cont) {
var t1 = new Date().getTime();
var ret = cont();
if (ARGS.stats) {
var spent = new Date().getTime() - t1;
if (STATS[name]) STATS[name] += spent;
else STATS[name] = spent;
}
return ret;
}
function print_error(msg) {
console.error("%s", msg);
}
function print(txt) {
console.log("%s", txt);
}