123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560 |
- #! /usr/bin/env node
- // -*- js -*-
-
- "use strict";
-
- var UglifyJS = require("../tools/node");
- var sys = require("util");
- var yargs = require("yargs");
- var fs = require("fs");
- var path = require("path");
- var async = require("async");
- var acorn;
- var ARGS = yargs
- .usage("$0 input1.js [input2.js ...] [options]\n\
- 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`.\
- ")
- .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-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", "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).")
- .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. \
- 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. \
- Use -c with no argument to use the default compression options.")
- .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.")
- .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")
- .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")
- .describe("mangle-regex", "Only mangle property names matching the regex")
- .describe("name-cache", "File to hold mangled names mappings")
-
- .alias("p", "prefix")
- .alias("o", "output")
- .alias("v", "verbose")
- .alias("b", "beautify")
- .alias("m", "mangle")
- .alias("c", "compress")
- .alias("d", "define")
- .alias("r", "reserved")
- .alias("V", "version")
- .alias("e", "enclose")
- .alias("q", "quotes")
-
- .string("source-map")
- .string("source-map-root")
- .string("source-map-url")
- .string("b")
- .string("beautify")
- .string("m")
- .string("mangle")
- .string("c")
- .string("compress")
- .string("d")
- .string("define")
- .string("e")
- .string("enclose")
- .string("comments")
- .string("wrap")
- .string("p")
- .string("prefix")
- .string("name-cache")
- .array("reserved-file")
-
- .boolean("expr")
- .boolean("source-map-include-sources")
- .boolean("screw-ie8")
- .boolean("export-all")
- .boolean("self")
- .boolean("v")
- .boolean("verbose")
- .boolean("stats")
- .boolean("acorn")
- .boolean("spidermonkey")
- .boolean("lint")
- .boolean("V")
- .boolean("version")
- .boolean("noerr")
- .boolean("bare-returns")
- .boolean("keep-fnames")
- .boolean("mangle-props")
- .boolean("reserve-domprops")
-
- .wrap(80)
-
- .argv
- ;
-
- normalize(ARGS);
-
- if (ARGS.noerr) {
- UglifyJS.DefaultsError.croak = function(msg, defs) {
- print_error("WARN: " + msg);
- };
- }
-
- if (ARGS.version || ARGS.V) {
- var json = require("../package.json");
- print(json.name + ' ' + json.version);
- 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");
- }
-
- var COMPRESS = getOptions("c", true);
- var MANGLE = getOptions("m", true);
- var BEAUTIFY = getOptions("b", true);
- var RESERVED = null;
-
- if (ARGS.reserved_file) ARGS.reserved_file.forEach(function(filename){
- RESERVED = UglifyJS.readReservedFile(filename, RESERVED);
- });
-
- if (ARGS.reserve_domprops) {
- RESERVED = UglifyJS.readDefaultReservedFile(RESERVED);
- }
-
- if (ARGS.d) {
- if (COMPRESS) COMPRESS.global_defs = getOptions("d");
- }
-
- if (ARGS.r) {
- if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
- }
-
- if (RESERVED && MANGLE) {
- if (!MANGLE.except) MANGLE.except = RESERVED.vars;
- else MANGLE.except = MANGLE.except.concat(RESERVED.vars);
- }
-
- function readNameCache(key) {
- return UglifyJS.readNameCache(ARGS.name_cache, key);
- }
-
- function writeNameCache(key, cache) {
- 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;
- }
-
- var OUTPUT_OPTIONS = {
- beautify : BEAUTIFY ? true : false,
- preamble : ARGS.preamble || null,
- quote_style : ARGS.quotes != null ? ARGS.quotes : 0
- };
-
- if (ARGS.screw_ie8) {
- if (COMPRESS) COMPRESS.screw_ie8 = true;
- if (MANGLE) MANGLE.screw_ie8 = true;
- OUTPUT_OPTIONS.screw_ie8 = true;
- }
-
- if (ARGS.keep_fnames) {
- if (COMPRESS) COMPRESS.keep_fnames = true;
- if (MANGLE) MANGLE.keep_fnames = true;
- }
-
- if (BEAUTIFY)
- UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
-
- if (ARGS.comments != null) {
- if (/^\/.*\/[a-zA-Z]*$/.test(ARGS.comments)) {
- try {
- OUTPUT_OPTIONS.comments = extractRegex(ARGS.comments);
- } catch (e) {
- print_error("ERROR: Invalid --comments: " + e.message);
- }
- } 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
- return /@preserve|@license|@cc_on/i.test(text);
- }
- }
- }
- }
-
- var files = ARGS._.slice();
-
- 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";
- ARGS.export_all = true;
- }
-
- 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;
- var P_RELATIVE = ARGS.p && ARGS.p == "relative";
- var SOURCES_CONTENT = {};
-
- var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
- 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) {
- if (P_RELATIVE) {
- file = path.relative(path.dirname(ARGS.source_map), file).replace(/\\/g, '/');
- } 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 {
- try {
- TOPLEVEL = UglifyJS.parse(code, {
- filename : file,
- toplevel : TOPLEVEL,
- expression : ARGS.expr,
- bare_returns : ARGS.bare_returns,
- });
- } 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);
- 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;
- 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
- });
- 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: ARGS.screw_ie8, cache: TL_CACHE });
- if (ARGS.lint) {
- TOPLEVEL.scope_warnings();
- }
- });
- }
-
- if (COMPRESS) {
- time_it("squeeze", function(){
- TOPLEVEL = TOPLEVEL.transform(compressor);
- });
- }
-
- if (SCOPE_IS_NEEDED) {
- time_it("scope", function(){
- TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.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]);
- }
- }
- }
-
- time_it("generate", function(){
- TOPLEVEL.print(output);
- });
-
- output = output.get();
-
- if (SOURCE_MAP) {
- 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(x, constants) {
- x = ARGS[x];
- if (x == null) return null;
- var ret = {};
- if (x !== "") {
- var ast;
- try {
- ast = UglifyJS.parse(x, { expression: true });
- } catch(ex) {
- if (ex instanceof UglifyJS.JS_Parse_Error) {
- print_error("Error parsing arguments in: " + 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 in: " + 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);
- }
|