123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- #! /usr/bin/env node
- // -*- js -*-
- "use strict";
- require("../tools/tty");
- var fs = require("fs");
- var info = require("../package.json");
- var path = require("path");
- var UglifyJS = require("../tools/node");
- var skip_keys = [ "cname", "fixed", "in_arg", "inlined", "length_read", "parent_scope", "redef", "scope", "unused" ];
- var truthy_keys = [ "optional", "pure", "terminal", "uses_arguments", "uses_eval", "uses_with" ];
- var files = {};
- var options = {};
- var short_forms = {
- b: "beautify",
- c: "compress",
- d: "define",
- e: "enclose",
- h: "help",
- m: "mangle",
- o: "output",
- O: "output-opts",
- p: "parse",
- v: "version",
- V: "version",
- };
- var args = process.argv.slice(2);
- var paths = [];
- var output, nameCache;
- var specified = {};
- while (args.length) {
- var arg = args.shift();
- if (arg[0] != "-") {
- paths.push(arg);
- } else if (arg == "--") {
- paths = paths.concat(args);
- break;
- } else if (arg[1] == "-") {
- process_option(arg.slice(2));
- } else [].forEach.call(arg.slice(1), function(letter, index, arg) {
- if (!(letter in short_forms)) fatal("invalid option -" + letter);
- process_option(short_forms[letter], index + 1 < arg.length);
- });
- }
- function process_option(name, no_value) {
- specified[name] = true;
- switch (name) {
- case "help":
- switch (read_value()) {
- case "ast":
- print(UglifyJS.describe_ast());
- break;
- case "options":
- var text = [];
- var toplevels = [];
- var padding = "";
- var defaults = UglifyJS.default_options();
- for (var name in defaults) {
- var option = defaults[name];
- if (option && typeof option == "object") {
- text.push("--" + ({
- output: "beautify",
- sourceMap: "source-map",
- }[name] || name) + " options:");
- text.push(format_object(option));
- text.push("");
- } else {
- if (padding.length < name.length) padding = Array(name.length + 1).join(" ");
- toplevels.push([ {
- keep_fargs: "keep-fargs",
- keep_fnames: "keep-fnames",
- nameCache: "name-cache",
- }[name] || name, option ]);
- }
- }
- toplevels.forEach(function(tokens) {
- text.push("--" + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]);
- });
- print(text.join("\n"));
- break;
- default:
- print([
- "Usage: uglifyjs [files...] [options]",
- "",
- "Options:",
- " -h, --help Print usage information.",
- " `--help options` for details on available options.",
- " -v, -V, --version Print version number.",
- " -p, --parse <options> Specify parser options.",
- " -c, --compress [options] Enable compressor/specify compressor options.",
- " -m, --mangle [options] Mangle names/specify mangler options.",
- " --mangle-props [options] Mangle properties/specify mangler options.",
- " -b, --beautify [options] Beautify output/specify output options.",
- " -O, --output-opts <options> Output options (beautify disabled).",
- " -o, --output <file> Output file (default STDOUT).",
- " --annotations Process and preserve comment annotations.",
- " --no-annotations Ignore and discard comment annotations.",
- " --comments [filter] Preserve copyright comments in the output.",
- " --config-file <file> Read minify() options from JSON file.",
- " -d, --define <expr>[=value] Global definitions.",
- " -e, --enclose [arg[,...][:value[,...]]] Embed everything in a big function, with configurable argument(s) & value(s).",
- " --ie Support non-standard Internet Explorer.",
- " --keep-fargs Do not mangle/drop function arguments.",
- " --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.",
- " --name-cache <file> File to hold mangled name mappings.",
- " --rename Force symbol expansion.",
- " --no-rename Disable symbol expansion.",
- " --self Build UglifyJS as a library (implies --wrap UglifyJS)",
- " --source-map [options] Enable source map/specify source map options.",
- " --timings Display operations run time on STDERR.",
- " --toplevel Compress and/or mangle variables in toplevel scope.",
- " --v8 Support non-standard Chrome & Node.js.",
- " --validate Perform validation during AST manipulations.",
- " --verbose Print diagnostic messages.",
- " --warn Print warning messages.",
- " --webkit Support non-standard Safari/Webkit.",
- " --wrap <name> Embed everything as a function with “exports” corresponding to “name” globally.",
- "",
- "(internal debug use only)",
- " --in-situ Warning: replaces original source files with minified output.",
- " --reduce-test Reduce a standalone test case (assumes cloned repository).",
- ].join("\n"));
- }
- process.exit();
- case "version":
- print(info.name + " " + info.version);
- process.exit();
- case "config-file":
- var config = JSON.parse(read_file(read_value(true)));
- if (config.mangle && config.mangle.properties && config.mangle.properties.regex) {
- config.mangle.properties.regex = UglifyJS.parse(config.mangle.properties.regex, {
- expression: true,
- }).value;
- }
- for (var key in config) if (!(key in options)) options[key] = config[key];
- break;
- case "compress":
- case "mangle":
- options[name] = parse_js(read_value(), options[name]);
- break;
- case "source-map":
- options.sourceMap = parse_js(read_value(), options.sourceMap);
- break;
- case "enclose":
- options[name] = read_value();
- break;
- case "annotations":
- case "ie":
- case "ie8":
- case "timings":
- case "toplevel":
- case "v8":
- case "validate":
- case "webkit":
- options[name] = true;
- break;
- case "no-annotations":
- options.annotations = false;
- break;
- case "keep-fargs":
- options.keep_fargs = true;
- break;
- case "keep-fnames":
- options.keep_fnames = true;
- break;
- case "wrap":
- options[name] = read_value(true);
- break;
- case "verbose":
- options.warnings = "verbose";
- break;
- case "warn":
- if (!options.warnings) options.warnings = true;
- break;
- case "beautify":
- options.output = parse_js(read_value(), options.output);
- if (!("beautify" in options.output)) options.output.beautify = true;
- break;
- case "output-opts":
- options.output = parse_js(read_value(true), options.output);
- break;
- case "comments":
- if (typeof options.output != "object") options.output = {};
- options.output.comments = read_value();
- if (options.output.comments === true) options.output.comments = "some";
- break;
- case "define":
- if (typeof options.compress != "object") options.compress = {};
- options.compress.global_defs = parse_js(read_value(true), options.compress.global_defs, "define");
- break;
- case "mangle-props":
- if (typeof options.mangle != "object") options.mangle = {};
- options.mangle.properties = parse_js(read_value(), options.mangle.properties);
- break;
- case "name-cache":
- nameCache = read_value(true);
- options.nameCache = JSON.parse(read_file(nameCache, "{}"));
- break;
- case "output":
- output = read_value(true);
- break;
- case "parse":
- options.parse = parse_js(read_value(true), options.parse);
- break;
- case "rename":
- options.rename = true;
- break;
- case "no-rename":
- options.rename = false;
- break;
- case "in-situ":
- case "reduce-test":
- case "self":
- break;
- default:
- fatal("invalid option --" + name);
- }
- function read_value(required) {
- if (no_value || !args.length || args[0][0] == "-") {
- if (required) fatal("missing option argument for --" + name);
- return true;
- }
- return args.shift();
- }
- }
- if (!output && options.sourceMap && options.sourceMap.url != "inline") fatal("cannot write source map to STDOUT");
- if (specified["beautify"] && specified["output-opts"]) fatal("--beautify cannot be used with --output-opts");
- [ "compress", "mangle" ].forEach(function(name) {
- if (!(name in options)) options[name] = false;
- });
- if (options.mangle && options.mangle.properties) {
- if (options.mangle.properties.domprops) {
- delete options.mangle.properties.domprops;
- } else {
- if (typeof options.mangle.properties != "object") options.mangle.properties = {};
- if (!Array.isArray(options.mangle.properties.reserved)) options.mangle.properties.reserved = [];
- require("../tools/domprops").forEach(function(name) {
- UglifyJS.push_uniq(options.mangle.properties.reserved, name);
- });
- }
- }
- if (/^ast|spidermonkey$/.test(output)) {
- if (typeof options.output != "object") options.output = {};
- options.output.ast = true;
- options.output.code = false;
- }
- if (options.parse && (options.parse.acorn || options.parse.spidermonkey)
- && options.sourceMap && options.sourceMap.content == "inline") {
- fatal("inline source map only works with built-in parser");
- }
- if (options.warnings) {
- UglifyJS.AST_Node.log_function(print_error, options.warnings == "verbose");
- delete options.warnings;
- }
- var convert_path = function(name) {
- return name;
- };
- if (typeof options.sourceMap == "object" && "base" in options.sourceMap) {
- convert_path = function() {
- var base = options.sourceMap.base;
- delete options.sourceMap.base;
- return function(name) {
- return path.relative(base, name);
- };
- }();
- }
- if (specified["self"]) {
- if (paths.length) UglifyJS.AST_Node.warn("Ignoring input files since --self was passed");
- if (!options.wrap) options.wrap = "UglifyJS";
- paths = UglifyJS.FILES;
- }
- if (specified["in-situ"]) {
- if (output && output != "spidermonkey" || specified["reduce-test"] || specified["self"]) {
- fatal("incompatible options specified");
- }
- paths.forEach(function(name) {
- print(name);
- if (/^ast|spidermonkey$/.test(name)) fatal("invalid file name specified");
- files = {};
- files[convert_path(name)] = read_file(name);
- output = name;
- run();
- });
- } else if (paths.length) {
- simple_glob(paths).forEach(function(name) {
- files[convert_path(name)] = read_file(name);
- });
- run();
- } else {
- var timerId = process.stdin.isTTY && process.argv.length < 3 && setTimeout(function() {
- print_error("Waiting for input... (use `--help` to print usage information)");
- }, 1500);
- var chunks = [];
- process.stdin.setEncoding("utf8");
- process.stdin.once("data", function() {
- clearTimeout(timerId);
- }).on("data", function(chunk) {
- chunks.push(chunk);
- }).on("end", function() {
- files = { STDIN: chunks.join("") };
- run();
- });
- process.stdin.resume();
- }
- function convert_ast(fn) {
- return UglifyJS.AST_Node.from_mozilla_ast(Object.keys(files).reduce(fn, null));
- }
- function run() {
- var content = options.sourceMap && options.sourceMap.content;
- if (content && content != "inline") {
- UglifyJS.AST_Node.info("Using input source map: {content}", {
- content : content,
- });
- options.sourceMap.content = read_file(content, content);
- }
- try {
- if (options.parse) {
- if (options.parse.acorn) {
- var annotations = Object.create(null);
- files = convert_ast(function(toplevel, name) {
- var content = files[name];
- var list = annotations[name] = [];
- var prev = -1;
- return require("acorn").parse(content, {
- allowHashBang: true,
- ecmaVersion: "latest",
- locations: true,
- onComment: function(block, text, start, end) {
- var match = /[@#]__PURE__/.exec(text);
- if (!match) {
- if (start != prev) return;
- match = [ list[prev] ];
- }
- while (/\s/.test(content[end])) end++;
- list[end] = match[0];
- prev = end;
- },
- preserveParens: true,
- program: toplevel,
- sourceFile: name,
- sourceType: "module",
- });
- });
- files.walk(new UglifyJS.TreeWalker(function(node) {
- if (!(node instanceof UglifyJS.AST_Call)) return;
- var list = annotations[node.start.file];
- var pure = list[node.start.pos];
- if (!pure) {
- var tokens = node.start.parens;
- if (tokens) for (var i = 0; !pure && i < tokens.length; i++) {
- pure = list[tokens[i].pos];
- }
- }
- if (pure) node.pure = pure;
- }));
- } else if (options.parse.spidermonkey) {
- files = convert_ast(function(toplevel, name) {
- var obj = JSON.parse(files[name]);
- if (!toplevel) return obj;
- toplevel.body = toplevel.body.concat(obj.body);
- return toplevel;
- });
- }
- }
- } catch (ex) {
- fatal(ex);
- }
- var result;
- if (specified["reduce-test"]) {
- // load on demand - assumes cloned repository
- var reduce_test = require("../test/reduce");
- if (Object.keys(files).length != 1) fatal("can only test on a single file");
- result = reduce_test(files[Object.keys(files)[0]], options, {
- log: print_error,
- verbose: true,
- });
- } else {
- result = UglifyJS.minify(files, options);
- }
- if (result.error) {
- var ex = result.error;
- if (ex.name == "SyntaxError") {
- print_error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col);
- var file = files[ex.filename];
- if (file) {
- var col = ex.col;
- var lines = file.split(/\r?\n/);
- var line = lines[ex.line - 1];
- if (!line && !col) {
- line = lines[ex.line - 2];
- col = line.length;
- }
- if (line) {
- var limit = 70;
- if (col > limit) {
- line = line.slice(col - limit);
- col = limit;
- }
- print_error(line.slice(0, 80));
- print_error(line.slice(0, col).replace(/\S/g, " ") + "^");
- }
- }
- } else if (ex.defs) {
- print_error("Supported options:");
- print_error(format_object(ex.defs));
- }
- fatal(ex);
- } else if (output == "ast") {
- if (!options.compress && !options.mangle) {
- var toplevel = result.ast;
- if (!(toplevel instanceof UglifyJS.AST_Toplevel)) {
- if (!(toplevel instanceof UglifyJS.AST_Statement)) toplevel = new UglifyJS.AST_SimpleStatement({
- body: toplevel,
- });
- toplevel = new UglifyJS.AST_Toplevel({
- body: [ toplevel ],
- });
- }
- toplevel.figure_out_scope({});
- }
- print(JSON.stringify(result.ast, function(key, value) {
- if (value) switch (key) {
- case "enclosed":
- return value.length ? value.map(symdef) : undefined;
- case "functions":
- case "globals":
- case "variables":
- return value.size() ? value.map(symdef) : undefined;
- case "thedef":
- return symdef(value);
- }
- if (skip_property(key, value)) return;
- if (value instanceof UglifyJS.AST_Token) return;
- if (value instanceof UglifyJS.Dictionary) return;
- if (value instanceof UglifyJS.AST_Node) {
- var result = {
- _class: "AST_" + value.TYPE
- };
- value.CTOR.PROPS.forEach(function(prop) {
- result[prop] = value[prop];
- });
- return result;
- }
- return value;
- }, 2));
- } else if (output == "spidermonkey") {
- print(JSON.stringify(result.ast.to_mozilla_ast(), null, 2));
- } else if (output) {
- var code;
- if (result.ast) {
- var opts = {};
- for (var name in options.output) {
- if (!/^ast|code$/.test(name)) opts[name] = options.output[name];
- }
- code = UglifyJS.AST_Node.from_mozilla_ast(result.ast.to_mozilla_ast()).print_to_string(opts);
- } else {
- code = result.code;
- }
- fs.writeFileSync(output, code);
- if (result.map) fs.writeFileSync(output + ".map", result.map);
- } else {
- print(result.code);
- }
- if (nameCache) fs.writeFileSync(nameCache, JSON.stringify(options.nameCache));
- if (result.timings) for (var phase in result.timings) {
- print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s");
- }
- }
- function fatal(message) {
- if (message instanceof Error) {
- message = message.stack.replace(/^\S*?Error:/, "ERROR:")
- } else {
- message = "ERROR: " + message;
- }
- print_error(message);
- process.exit(1);
- }
- // A file glob function that only supports "*" and "?" wildcards in the basename.
- // Example: "foo/bar/*baz??.*.js"
- // Argument `glob` may be a string or an array of strings.
- // Returns an array of strings. Garbage in, garbage out.
- function simple_glob(glob) {
- if (Array.isArray(glob)) {
- return [].concat.apply([], glob.map(simple_glob));
- }
- if (glob.match(/\*|\?/)) {
- var dir = path.dirname(glob);
- try {
- var entries = fs.readdirSync(dir);
- } catch (ex) {}
- if (entries) {
- var pattern = "^" + path.basename(glob)
- .replace(/[.+^$[\]\\(){}]/g, "\\$&")
- .replace(/\*/g, "[^/\\\\]*")
- .replace(/\?/g, "[^/\\\\]") + "$";
- var mod = process.platform === "win32" ? "i" : "";
- var rx = new RegExp(pattern, mod);
- var results = entries.sort().filter(function(name) {
- return rx.test(name);
- }).map(function(name) {
- return path.join(dir, name);
- });
- if (results.length) return results;
- }
- }
- return [ glob ];
- }
- function read_file(path, default_value) {
- try {
- return fs.readFileSync(path, "utf8");
- } catch (ex) {
- if (ex.code == "ENOENT" && default_value != null) return default_value;
- fatal(ex);
- }
- }
- function parse_js(value, options, flag) {
- if (!options || typeof options != "object") options = Object.create(null);
- if (typeof value == "string") try {
- UglifyJS.parse(value, {
- expression: true
- }).walk(new UglifyJS.TreeWalker(function(node) {
- if (node instanceof UglifyJS.AST_Assign) {
- var name = node.left.print_to_string();
- var value = node.right;
- if (flag) {
- options[name] = value;
- } else if (value instanceof UglifyJS.AST_Array) {
- options[name] = value.elements.map(to_string);
- } else {
- options[name] = to_string(value);
- }
- return true;
- }
- if (node instanceof UglifyJS.AST_Symbol || node instanceof UglifyJS.AST_PropAccess) {
- var name = node.print_to_string();
- options[name] = true;
- return true;
- }
- if (!(node instanceof UglifyJS.AST_Sequence)) throw node;
- function to_string(value) {
- return value instanceof UglifyJS.AST_Constant ? value.value : value.print_to_string({
- quote_keys: true
- });
- }
- }));
- } catch (ex) {
- if (flag) {
- fatal("cannot parse arguments for '" + flag + "': " + value);
- } else {
- options[value] = null;
- }
- }
- return options;
- }
- function skip_property(key, value) {
- return skip_keys.indexOf(key) >= 0
- // only skip truthy_keys if their value is falsy
- || truthy_keys.indexOf(key) >= 0 && !value;
- }
- function symdef(def) {
- var ret = (1e6 + def.id) + " " + def.name;
- if (def.mangled_name) ret += " " + def.mangled_name;
- return ret;
- }
- function format_object(obj) {
- var lines = [];
- var padding = "";
- Object.keys(obj).map(function(name) {
- if (padding.length < name.length) padding = Array(name.length + 1).join(" ");
- return [ name, JSON.stringify(obj[name]) ];
- }).forEach(function(tokens) {
- lines.push(" " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]);
- });
- return lines.join("\n");
- }
- function print_error(msg) {
- process.stderr.write(msg);
- process.stderr.write("\n");
- }
- function print(txt) {
- process.stdout.write(txt);
- process.stdout.write("\n");
- }
|