2012-09-02 11:32:00 +00:00
#! /usr/bin/env node
// -*- js -*-
2012-10-02 09:45:31 +00:00
"use strict";
2012-09-02 11:32:00 +00:00
var UglifyJS = require("../tools/node");
var sys = require("util");
var optimist = require("optimist");
var fs = require("fs");
2013-08-07 08:43:47 +00:00
var path = require("path");
2013-03-31 09:51:43 +00:00
var async = require("async");
2012-10-05 12:22:12 +00:00
var acorn;
2012-09-02 11:32:00 +00:00
var ARGS = optimist
2012-10-02 13:40:42 +00:00
.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.\
2012-10-02 13:40:42 +00:00
\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
")
2012-09-19 07:22:36 +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.")
2013-05-21 14:46:27 +00:00
.describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.")
2012-09-24 14:02:18 +00:00
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
2013-03-25 15:11:07 +00:00
.describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).")
2013-05-15 10:27:23 +00:00
.describe("expr", "Parse a single expression, rather than a program (for parsing JSON)")
2012-10-02 08:00:47 +00:00
.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.")
2012-10-02 08:00:47 +00:00
.describe("o", "Output file (default STDOUT).")
.describe("b", "Beautify output/specify output options.")
.describe("m", "Mangle names/pass mangler options.")
2012-10-02 11:02:33 +00:00
.describe("r", "Reserved names to exclude from mangling.")
2012-10-02 08:00:47 +00:00
.describe("c", "Enable compressor/pass compressor options. \
2012-09-19 07:22:36 +00:00
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")
2013-03-01 05:21:14 +00:00
.describe("e", "Embed everything in a big function, with a configurable parameter/argument list.")
2012-09-19 07:22:36 +00:00
2012-10-02 13:40:42 +00:00
.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.")
2013-10-29 08:43:43 +00:00
.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.")
2012-10-02 08:00:47 +00:00
.describe("stats", "Display operations run time on STDERR.")
2012-10-05 12:22:12 +00:00
.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).")
2012-10-08 09:55:18 +00:00
.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.")
2012-10-10 08:26:59 +00:00
.describe("lint", "Display some scope warnings")
2012-09-19 07:22:36 +00:00
.describe("v", "Verbose")
2013-01-03 10:06:07 +00:00
.describe("V", "Print version number and exit.")
2012-09-03 07:14:15 +00:00
.alias("p", "prefix")
.alias("o", "output")
.alias("v", "verbose")
.alias("b", "beautify")
2012-10-02 08:00:47 +00:00
.alias("m", "mangle")
.alias("c", "compress")
2012-10-02 10:20:07 +00:00
.alias("d", "define")
2012-10-02 13:40:42 +00:00
.alias("r", "reserved")
2013-01-03 10:06:07 +00:00
.alias("V", "version")
2013-03-01 05:21:14 +00:00
.alias("e", "enclose")
2012-09-03 07:14:15 +00:00
2012-10-02 18:08:16 +00:00
.string("source-map")
.string("source-map-root")
2013-01-20 10:32:07 +00:00
.string("source-map-url")
2012-10-02 08:00:47 +00:00
.string("b")
.string("m")
.string("c")
2012-10-02 10:20:07 +00:00
.string("d")
2013-03-01 05:21:14 +00:00
.string("e")
2012-10-02 13:40:42 +00:00
.string("comments")
2012-10-08 09:55:18 +00:00
.string("wrap")
2013-08-07 08:43:47 +00:00
.string("p")
2013-05-15 10:27:23 +00:00
.boolean("expr")
2013-03-25 15:11:07 +00:00
.boolean("screw-ie8")
2012-10-08 09:55:18 +00:00
.boolean("export-all")
.boolean("self")
2012-09-03 09:03:45 +00:00
.boolean("v")
2012-09-05 15:19:24 +00:00
.boolean("stats")
2012-10-05 12:22:12 +00:00
.boolean("acorn")
.boolean("spidermonkey")
2012-10-10 08:26:59 +00:00
.boolean("lint")
2013-01-03 10:22:37 +00:00
.boolean("V")
2012-09-03 09:03:45 +00:00
2012-09-19 07:22:36 +00:00
.wrap(80)
2012-09-02 11:32:00 +00:00
.argv
;
2012-10-09 10:21:21 +00:00
normalize(ARGS);
2013-01-03 10:06:07 +00:00
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
sys.puts(json.name + ' ' + json.version);
process.exit(0);
}
2012-10-09 10:21:21 +00:00
if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast();
sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
process.exit(0);
}
2012-09-02 11:32:00 +00:00
if (ARGS.h || ARGS.help) {
sys.puts(optimist.help());
process.exit(0);
}
2012-10-05 12:22:12 +00:00
if (ARGS.acorn) {
acorn = require("acorn");
2012-09-13 16:45:16 +00:00
}
2013-04-21 08:35:50 +00:00
var COMPRESS = getOptions("c", true);
var MANGLE = getOptions("m", true);
2012-10-17 12:56:45 +00:00
var BEAUTIFY = getOptions("b", true);
2012-10-02 08:00:47 +00:00
2013-04-20 20:11:05 +00:00
if (ARGS.d) {
2013-04-21 08:35:50 +00:00
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
2012-10-02 10:20:07 +00:00
}
2013-04-20 20:11:05 +00:00
if (ARGS.r) {
2013-04-21 08:35:50 +00:00
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
2013-03-22 16:02:08 +00:00
}
2012-10-02 08:00:47 +00:00
var OUTPUT_OPTIONS = {
2013-10-29 08:43:43 +00:00
beautify: BEAUTIFY ? true : false,
preamble: ARGS.preamble || null,
2012-10-02 08:00:47 +00:00
};
2012-10-02 13:40:42 +00:00
2013-08-19 00:45:06 +00:00
if (ARGS.screw_ie8) {
if (COMPRESS) COMPRESS.screw_ie8 = true;
if (MANGLE) MANGLE.screw_ie8 = true;
OUTPUT_OPTIONS.screw_ie8 = true;
}
2012-10-02 08:00:47 +00:00
if (BEAUTIFY)
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
2012-10-02 13:40:42 +00:00
if (ARGS.comments) {
if (/^\//.test(ARGS.comments)) {
OUTPUT_OPTIONS.comments = new Function("return(" + ARGS.comments + ")")();
} else if (ARGS.comments == "all") {
OUTPUT_OPTIONS.comments = true;
} else {
OUTPUT_OPTIONS.comments = function(node, comment) {
var text = comment.value;
var type = comment.type;
if (type == "comment2") {
// multiline comment
2012-10-18 07:54:10 +00:00
return /@preserve|@license|@cc_on/i.test(text);
2012-10-02 13:40:42 +00:00
}
}
}
}
2012-09-02 11:32:00 +00:00
var files = ARGS._.slice();
2012-10-08 09:55:18 +00:00
if (ARGS.self) {
if (files.length > 0) {
sys.error("WARN: Ignoring input files since --self was passed");
}
files = UglifyJS.FILES;
if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
ARGS.export_all = true;
}
2012-09-24 14:02:18 +00:00
var ORIG_MAP = ARGS.in_source_map;
if (ORIG_MAP) {
ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
if (files.length == 0) {
sys.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) {
2012-09-10 19:40:18 +00:00
files = [ "-" ];
2012-09-24 14:02:18 +00:00
}
2012-09-02 11:32:00 +00:00
if (files.indexOf("-") >= 0 && ARGS.source_map) {
sys.error("ERROR: Source map doesn't work with input from STDIN");
process.exit(1);
}
if (files.filter(function(el){ return el == "-" }).length > 1) {
sys.error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
process.exit(1);
}
var STATS = {};
var OUTPUT_FILE = ARGS.o;
2012-09-21 11:19:05 +00:00
var TOPLEVEL = null;
2013-08-07 08:43:47 +00:00
var P_RELATIVE = ARGS.p && ARGS.p == "relative";
2012-09-02 11:32:00 +00:00
var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
2013-08-07 08:43:47 +00:00
file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE,
2012-09-24 14:02:18 +00:00
root: ARGS.source_map_root,
orig: ORIG_MAP,
2012-09-02 11:32:00 +00:00
}) : null;
2012-10-02 08:00:47 +00:00
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) {
sys.error(ex.msg);
sys.error("Supported options:");
sys.error(sys.inspect(ex.defs));
process.exit(1);
}
}
2012-09-02 11:32:00 +00:00
2013-03-31 09:51:43 +00:00
async.eachLimit(files, 1, function (file, cb) {
read_whole_file(file, function (err, code) {
if (err) {
2013-07-30 09:16:29 +00:00
sys.error("ERROR: can't read file: " + file);
2013-03-31 09:51:43 +00:00
process.exit(1);
2012-10-05 12:22:12 +00:00
}
2013-03-31 09:51:43 +00:00
if (ARGS.p != null) {
2013-08-07 08:43:47 +00:00
if (P_RELATIVE) {
file = path.relative(path.dirname(ARGS.source_map), file);
} else {
var p = parseInt(ARGS.p, 10);
if (!isNaN(p)) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
}
}
2012-10-05 12:22:12 +00:00
}
2013-03-31 09:51:43 +00:00
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,
});
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
sys.error("Parse error at " + file + ":" + ex.line + "," + ex.col);
sys.error(ex.message);
sys.error(ex.stack);
process.exit(1);
}
throw ex;
}
2013-03-31 09:51:43 +00:00
};
});
cb();
});
}, function () {
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
2012-09-21 11:19:05 +00:00
});
2013-03-31 09:51:43 +00:00
if (ARGS.wrap) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
2012-10-05 12:22:12 +00:00
2013-03-31 09:51:43 +00:00
if (ARGS.enclose) {
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)) {
2013-03-31 09:51:43 +00:00
arg_parameter_list = [arg_parameter_list];
}
TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
2013-03-01 05:21:14 +00:00
}
2013-03-31 09:51:43 +00:00
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
2013-03-01 05:21:14 +00:00
2013-03-31 09:51:43 +00:00
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
});
}
2012-09-26 08:24:04 +00:00
2013-03-31 09:51:43 +00:00
if (COMPRESS) {
time_it("squeeze", function(){
TOPLEVEL = TOPLEVEL.transform(compressor);
});
}
2012-09-21 11:19:05 +00:00
2013-03-31 09:51:43 +00:00
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
if (MANGLE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
2012-09-02 11:32:00 +00:00
2013-03-31 09:51:43 +00:00
if (MANGLE) time_it("mangle", function(){
TOPLEVEL.mangle_names(MANGLE);
});
time_it("generate", function(){
TOPLEVEL.print(output);
2012-09-26 08:24:04 +00:00
});
2012-09-21 11:19:05 +00:00
2013-03-31 09:51:43 +00:00
output = output.get();
2012-09-03 07:14:15 +00:00
2013-03-31 09:51:43 +00:00
if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
2013-08-07 08:43:47 +00:00
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;
2013-03-31 09:51:43 +00:00
}
2012-09-02 11:32:00 +00:00
2013-03-31 09:51:43 +00:00
if (OUTPUT_FILE) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
sys.print(output);
sys.error("\n");
}
2012-09-02 11:32:00 +00:00
2013-03-31 09:51:43 +00:00
if (ARGS.stats) {
sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
count: files.length
2012-09-02 11:32:00 +00:00
}));
2013-03-31 09:51:43 +00:00
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
sys.error(UglifyJS.string_template("- {name}: {time}s", {
name: i,
time: (STATS[i] / 1000).toFixed(3)
}));
}
2012-09-02 11:32:00 +00:00
}
2013-03-31 09:51:43 +00:00
});
2012-09-02 11:32:00 +00:00
/* -----[ functions ]----- */
2012-10-05 12:22:12 +00:00
function normalize(o) {
for (var i in o) if (o.hasOwnProperty(i) && /-/.test(i)) {
o[i.replace(/-/g, "_")] = o[i];
delete o[i];
}
}
2012-10-17 12:56:45 +00:00
function getOptions(x, constants) {
2012-10-05 12:22:12 +00:00
x = ARGS[x];
if (!x) return null;
var ret = {};
if (x !== true) {
2012-10-17 12:56:45 +00:00
var ast;
try {
ast = UglifyJS.parse(x);
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
sys.error("Error parsing arguments in: " + x);
process.exit(1);
}
}
ast.walk(new UglifyJS.TreeWalker(function(node){
if (node instanceof UglifyJS.AST_Toplevel) return; // descend
if (node instanceof UglifyJS.AST_SimpleStatement) return; // descend
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
}
sys.error(node.TYPE)
sys.error("Error parsing arguments in: " + x);
process.exit(1);
}));
2012-10-05 12:22:12 +00:00
}
return ret;
}
2013-03-31 09:51:43 +00:00
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);
2012-09-02 11:32:00 +00:00
}
}
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;
2012-10-05 12:22:12 +00:00
}