uglifyjs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. #! /usr/bin/env node
  2. // -*- js -*-
  3. "use strict";
  4. require("../tools/exit");
  5. var fs = require("fs");
  6. var info = require("../package.json");
  7. var path = require("path");
  8. var UglifyJS = require("../tools/node");
  9. var skip_keys = [ "cname", "inlined", "parent_scope", "scope", "uses_eval", "uses_with" ];
  10. var files = {};
  11. var options = {};
  12. var short_forms = {
  13. b: "beautify",
  14. c: "compress",
  15. d: "define",
  16. e: "enclose",
  17. h: "help",
  18. m: "mangle",
  19. o: "output",
  20. O: "output-opts",
  21. p: "parse",
  22. v: "version",
  23. V: "version",
  24. };
  25. var args = process.argv.slice(2);
  26. var paths = [];
  27. var output, nameCache;
  28. var specified = {};
  29. while (args.length) {
  30. var arg = args.shift();
  31. if (arg[0] != "-") {
  32. paths.push(arg);
  33. } else if (arg == "--") {
  34. paths = paths.concat(args);
  35. break;
  36. } else if (arg[1] == "-") {
  37. process_option(arg.slice(2));
  38. } else [].forEach.call(arg.slice(1), function(letter, index, arg) {
  39. if (!(letter in short_forms)) fatal("invalid option -" + letter);
  40. process_option(short_forms[letter], index + 1 < arg.length);
  41. });
  42. }
  43. function process_option(name, no_value) {
  44. specified[name] = true;
  45. switch (name) {
  46. case "help":
  47. switch (read_value()) {
  48. case "ast":
  49. print(UglifyJS.describe_ast());
  50. break;
  51. case "options":
  52. var text = [];
  53. var toplevels = [];
  54. var padding = "";
  55. var defaults = UglifyJS.default_options();
  56. for (var name in defaults) {
  57. var option = defaults[name];
  58. if (option && typeof option == "object") {
  59. text.push("--" + ({
  60. output: "beautify",
  61. sourceMap: "source-map",
  62. }[name] || name) + " options:");
  63. text.push(format_object(option));
  64. text.push("");
  65. } else {
  66. if (padding.length < name.length) padding = Array(name.length + 1).join(" ");
  67. toplevels.push([ {
  68. keep_fnames: "keep-fnames",
  69. nameCache: "name-cache",
  70. }[name] || name, option ]);
  71. }
  72. }
  73. toplevels.forEach(function(tokens) {
  74. text.push("--" + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]);
  75. });
  76. print(text.join("\n"));
  77. break;
  78. default:
  79. print([
  80. "Usage: uglifyjs [files...] [options]",
  81. "",
  82. "Options:",
  83. " -h, --help Print usage information.",
  84. " `--help options` for details on available options.",
  85. " -v, -V, --version Print version number.",
  86. " -p, --parse <options> Specify parser options.",
  87. " -c, --compress [options] Enable compressor/specify compressor options.",
  88. " -m, --mangle [options] Mangle names/specify mangler options.",
  89. " --mangle-props [options] Mangle properties/specify mangler options.",
  90. " -b, --beautify [options] Beautify output/specify output options.",
  91. " -O, --output-opts <options> Output options (beautify disabled).",
  92. " -o, --output <file> Output file (default STDOUT).",
  93. " --comments [filter] Preserve copyright comments in the output.",
  94. " --config-file <file> Read minify() options from JSON file.",
  95. " -d, --define <expr>[=value] Global definitions.",
  96. " -e, --enclose [arg[,...][:value[,...]]] Embed everything in a big function, with configurable argument(s) & value(s).",
  97. " --ie8 Support non-standard Internet Explorer 8.",
  98. " --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name.",
  99. " --name-cache <file> File to hold mangled name mappings.",
  100. " --rename Force symbol expansion.",
  101. " --no-rename Disable symbol expansion.",
  102. " --self Build UglifyJS as a library (implies --wrap UglifyJS)",
  103. " --source-map [options] Enable source map/specify source map options.",
  104. " --timings Display operations run time on STDERR.",
  105. " --toplevel Compress and/or mangle variables in toplevel scope.",
  106. " --validate Perform validation during AST manipulations.",
  107. " --verbose Print diagnostic messages.",
  108. " --warn Print warning messages.",
  109. " --wrap <name> Embed everything as a function with “exports” corresponding to “name” globally.",
  110. " --reduce-test Reduce a standalone test case (assumes cloned repository).",
  111. ].join("\n"));
  112. }
  113. process.exit();
  114. case "version":
  115. print(info.name + " " + info.version);
  116. process.exit();
  117. case "config-file":
  118. var config = JSON.parse(read_file(read_value(true)));
  119. if (config.mangle && config.mangle.properties && config.mangle.properties.regex) {
  120. config.mangle.properties.regex = UglifyJS.parse(config.mangle.properties.regex, {
  121. expression: true,
  122. }).value;
  123. }
  124. for (var key in config) if (!(key in options)) options[key] = config[key];
  125. break;
  126. case "compress":
  127. case "mangle":
  128. options[name] = parse_js(read_value(), options[name]);
  129. break;
  130. case "source-map":
  131. options.sourceMap = parse_js(read_value(), options.sourceMap);
  132. break;
  133. case "enclose":
  134. options[name] = read_value();
  135. break;
  136. case "ie8":
  137. case "timings":
  138. case "toplevel":
  139. case "validate":
  140. options[name] = true;
  141. break;
  142. case "keep-fnames":
  143. options.keep_fnames = true;
  144. break;
  145. case "wrap":
  146. options[name] = read_value(true);
  147. break;
  148. case "verbose":
  149. options.warnings = "verbose";
  150. break;
  151. case "warn":
  152. if (!options.warnings) options.warnings = true;
  153. break;
  154. case "beautify":
  155. options.output = parse_js(read_value(), options.output);
  156. if (!("beautify" in options.output)) options.output.beautify = true;
  157. break;
  158. case "output-opts":
  159. options.output = parse_js(read_value(true), options.output);
  160. break;
  161. case "comments":
  162. if (typeof options.output != "object") options.output = {};
  163. options.output.comments = read_value();
  164. if (options.output.comments === true) options.output.comments = "some";
  165. break;
  166. case "define":
  167. if (typeof options.compress != "object") options.compress = {};
  168. options.compress.global_defs = parse_js(read_value(true), options.compress.global_defs, "define");
  169. break;
  170. case "mangle-props":
  171. if (typeof options.mangle != "object") options.mangle = {};
  172. options.mangle.properties = parse_js(read_value(), options.mangle.properties);
  173. break;
  174. case "name-cache":
  175. nameCache = read_value(true);
  176. options.nameCache = JSON.parse(read_file(nameCache, "{}"));
  177. break;
  178. case "output":
  179. output = read_value(true);
  180. break;
  181. case "parse":
  182. options.parse = parse_js(read_value(true), options.parse);
  183. break;
  184. case "rename":
  185. options.rename = true;
  186. break;
  187. case "no-rename":
  188. options.rename = false;
  189. break;
  190. case "reduce-test":
  191. case "self":
  192. break;
  193. default:
  194. fatal("invalid option --" + name);
  195. }
  196. function read_value(required) {
  197. if (no_value || !args.length || args[0][0] == "-") {
  198. if (required) fatal("missing option argument for --" + name);
  199. return true;
  200. }
  201. return args.shift();
  202. }
  203. }
  204. if (!output && options.sourceMap && options.sourceMap.url != "inline") fatal("cannot write source map to STDOUT");
  205. if (specified["beautify"] && specified["output-opts"]) fatal("--beautify cannot be used with --output-opts");
  206. [ "compress", "mangle" ].forEach(function(name) {
  207. if (!(name in options)) options[name] = false;
  208. });
  209. if (options.mangle && options.mangle.properties) {
  210. if (options.mangle.properties.domprops) {
  211. delete options.mangle.properties.domprops;
  212. } else {
  213. if (typeof options.mangle.properties != "object") options.mangle.properties = {};
  214. if (!Array.isArray(options.mangle.properties.reserved)) options.mangle.properties.reserved = [];
  215. require("../tools/domprops").forEach(function(name) {
  216. UglifyJS.push_uniq(options.mangle.properties.reserved, name);
  217. });
  218. }
  219. }
  220. if (output == "ast") options.output = {
  221. ast: true,
  222. code: false,
  223. };
  224. if (options.parse && (options.parse.acorn || options.parse.spidermonkey)
  225. && options.sourceMap && options.sourceMap.content == "inline") {
  226. fatal("inline source map only works with built-in parser");
  227. }
  228. if (options.warnings) {
  229. UglifyJS.AST_Node.log_function(print_error, options.warnings == "verbose");
  230. delete options.warnings;
  231. }
  232. var convert_path = function(name) {
  233. return name;
  234. };
  235. if (typeof options.sourceMap == "object" && "base" in options.sourceMap) {
  236. convert_path = function() {
  237. var base = options.sourceMap.base;
  238. delete options.sourceMap.base;
  239. return function(name) {
  240. return path.relative(base, name);
  241. };
  242. }();
  243. }
  244. if (specified["self"]) {
  245. if (paths.length) UglifyJS.AST_Node.warn("Ignoring input files since --self was passed");
  246. if (!options.wrap) options.wrap = "UglifyJS";
  247. paths = UglifyJS.FILES;
  248. }
  249. if (paths.length) {
  250. simple_glob(paths).forEach(function(name) {
  251. files[convert_path(name)] = read_file(name);
  252. });
  253. run();
  254. } else {
  255. var chunks = [];
  256. process.stdin.setEncoding("utf8");
  257. process.stdin.on("data", function(chunk) {
  258. chunks.push(chunk);
  259. }).on("end", function() {
  260. files = [ chunks.join("") ];
  261. run();
  262. });
  263. process.stdin.resume();
  264. }
  265. function convert_ast(fn) {
  266. return UglifyJS.AST_Node.from_mozilla_ast(Object.keys(files).reduce(fn, null));
  267. }
  268. function run() {
  269. var content = options.sourceMap && options.sourceMap.content;
  270. if (content && content != "inline") {
  271. UglifyJS.AST_Node.info("Using input source map: " + content);
  272. options.sourceMap.content = read_file(content, content);
  273. }
  274. try {
  275. if (options.parse) {
  276. if (options.parse.acorn) {
  277. files = convert_ast(function(toplevel, name) {
  278. return require("acorn").parse(files[name], {
  279. locations: true,
  280. program: toplevel,
  281. sourceFile: name
  282. });
  283. });
  284. } else if (options.parse.spidermonkey) {
  285. files = convert_ast(function(toplevel, name) {
  286. var obj = JSON.parse(files[name]);
  287. if (!toplevel) return obj;
  288. toplevel.body = toplevel.body.concat(obj.body);
  289. return toplevel;
  290. });
  291. }
  292. }
  293. } catch (ex) {
  294. fatal(ex);
  295. }
  296. var result;
  297. if (specified["reduce-test"]) {
  298. // load on demand - assumes cloned repository
  299. var reduce_test = require("../test/reduce");
  300. if (Object.keys(files).length != 1) fatal("can only test on a single file");
  301. result = reduce_test(files[Object.keys(files)[0]], options, {
  302. log: print_error,
  303. verbose: true,
  304. });
  305. } else {
  306. result = UglifyJS.minify(files, options);
  307. }
  308. if (result.error) {
  309. var ex = result.error;
  310. if (ex.name == "SyntaxError") {
  311. print_error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col);
  312. var file = files[ex.filename];
  313. if (file) {
  314. var col = ex.col;
  315. var lines = file.split(/\r?\n/);
  316. var line = lines[ex.line - 1];
  317. if (!line && !col) {
  318. line = lines[ex.line - 2];
  319. col = line.length;
  320. }
  321. if (line) {
  322. var limit = 70;
  323. if (col > limit) {
  324. line = line.slice(col - limit);
  325. col = limit;
  326. }
  327. print_error(line.slice(0, 80));
  328. print_error(line.slice(0, col).replace(/\S/g, " ") + "^");
  329. }
  330. }
  331. } else if (ex.defs) {
  332. print_error("Supported options:");
  333. print_error(format_object(ex.defs));
  334. }
  335. fatal(ex);
  336. } else if (output == "ast") {
  337. if (!options.compress && !options.mangle) result.ast.figure_out_scope({});
  338. print(JSON.stringify(result.ast, function(key, value) {
  339. if (value) switch (key) {
  340. case "thedef":
  341. return symdef(value);
  342. case "enclosed":
  343. return value.length ? value.map(symdef) : undefined;
  344. case "variables":
  345. case "functions":
  346. case "globals":
  347. return value.size() ? value.map(symdef) : undefined;
  348. }
  349. if (skip_key(key)) return;
  350. if (value instanceof UglifyJS.AST_Token) return;
  351. if (value instanceof UglifyJS.Dictionary) return;
  352. if (value instanceof UglifyJS.AST_Node) {
  353. var result = {
  354. _class: "AST_" + value.TYPE
  355. };
  356. value.CTOR.PROPS.forEach(function(prop) {
  357. result[prop] = value[prop];
  358. });
  359. return result;
  360. }
  361. return value;
  362. }, 2));
  363. } else if (output == "spidermonkey") {
  364. print(JSON.stringify(UglifyJS.minify(result.code, {
  365. compress: false,
  366. mangle: false,
  367. output: {
  368. ast: true,
  369. code: false
  370. },
  371. }).ast.to_mozilla_ast(), null, 2));
  372. } else if (output) {
  373. fs.writeFileSync(output, result.code);
  374. if (result.map) fs.writeFileSync(output + ".map", result.map);
  375. } else {
  376. print(result.code);
  377. }
  378. if (nameCache) fs.writeFileSync(nameCache, JSON.stringify(options.nameCache));
  379. if (result.timings) for (var phase in result.timings) {
  380. print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s");
  381. }
  382. }
  383. function fatal(message) {
  384. if (message instanceof Error) {
  385. message = message.stack.replace(/^\S*?Error:/, "ERROR:")
  386. } else {
  387. message = "ERROR: " + message;
  388. }
  389. print_error(message);
  390. process.exit(1);
  391. }
  392. // A file glob function that only supports "*" and "?" wildcards in the basename.
  393. // Example: "foo/bar/*baz??.*.js"
  394. // Argument `glob` may be a string or an array of strings.
  395. // Returns an array of strings. Garbage in, garbage out.
  396. function simple_glob(glob) {
  397. if (Array.isArray(glob)) {
  398. return [].concat.apply([], glob.map(simple_glob));
  399. }
  400. if (glob.match(/\*|\?/)) {
  401. var dir = path.dirname(glob);
  402. try {
  403. var entries = fs.readdirSync(dir);
  404. } catch (ex) {}
  405. if (entries) {
  406. var pattern = "^" + path.basename(glob)
  407. .replace(/[.+^$[\]\\(){}]/g, "\\$&")
  408. .replace(/\*/g, "[^/\\\\]*")
  409. .replace(/\?/g, "[^/\\\\]") + "$";
  410. var mod = process.platform === "win32" ? "i" : "";
  411. var rx = new RegExp(pattern, mod);
  412. var results = entries.sort().filter(function(name) {
  413. return rx.test(name);
  414. }).map(function(name) {
  415. return path.join(dir, name);
  416. });
  417. if (results.length) return results;
  418. }
  419. }
  420. return [ glob ];
  421. }
  422. function read_file(path, default_value) {
  423. try {
  424. return fs.readFileSync(path, "utf8");
  425. } catch (ex) {
  426. if (ex.code == "ENOENT" && default_value != null) return default_value;
  427. fatal(ex);
  428. }
  429. }
  430. function parse_js(value, options, flag) {
  431. if (!options || typeof options != "object") options = {};
  432. if (typeof value == "string") try {
  433. UglifyJS.parse(value, {
  434. expression: true
  435. }).walk(new UglifyJS.TreeWalker(function(node) {
  436. if (node instanceof UglifyJS.AST_Assign) {
  437. var name = node.left.print_to_string();
  438. var value = node.right;
  439. if (flag) {
  440. options[name] = value;
  441. } else if (value instanceof UglifyJS.AST_Array) {
  442. options[name] = value.elements.map(to_string);
  443. } else {
  444. options[name] = to_string(value);
  445. }
  446. return true;
  447. }
  448. if (node instanceof UglifyJS.AST_Symbol || node instanceof UglifyJS.AST_PropAccess) {
  449. var name = node.print_to_string();
  450. options[name] = true;
  451. return true;
  452. }
  453. if (!(node instanceof UglifyJS.AST_Sequence)) throw node;
  454. function to_string(value) {
  455. return value instanceof UglifyJS.AST_Constant ? value.value : value.print_to_string({
  456. quote_keys: true
  457. });
  458. }
  459. }));
  460. } catch (ex) {
  461. if (flag) {
  462. fatal("cannot parse arguments for '" + flag + "': " + value);
  463. } else {
  464. options[value] = null;
  465. }
  466. }
  467. return options;
  468. }
  469. function skip_key(key) {
  470. return skip_keys.indexOf(key) >= 0;
  471. }
  472. function symdef(def) {
  473. var ret = (1e6 + def.id) + " " + def.name;
  474. if (def.mangled_name) ret += " " + def.mangled_name;
  475. return ret;
  476. }
  477. function format_object(obj) {
  478. var lines = [];
  479. var padding = "";
  480. Object.keys(obj).map(function(name) {
  481. if (padding.length < name.length) padding = Array(name.length + 1).join(" ");
  482. return [ name, JSON.stringify(obj[name]) ];
  483. }).forEach(function(tokens) {
  484. lines.push(" " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]);
  485. });
  486. return lines.join("\n");
  487. }
  488. function print_error(msg) {
  489. process.stderr.write(msg);
  490. process.stderr.write("\n");
  491. }
  492. function print(txt) {
  493. process.stdout.write(txt);
  494. process.stdout.write("\n");
  495. }