uglify.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. "use strict";
  2. var sys = require("util");
  3. var fs = require("fs");
  4. var path = require("path");
  5. var UglifyJS = require("uglify-js");
  6. var async = require("async");
  7. var acorn;
  8. exports.beautify = function (file) {
  9. var isJSON = /\.json$/i.test(file);
  10. var command = {
  11. _: [file],
  12. expr: false,
  13. beautify: "width=80",
  14. b: "width=80",
  15. self: false,
  16. v: false,
  17. verbose: false,
  18. stats: false,
  19. acorn: false,
  20. spidermonkey: false,
  21. lint: false,
  22. V: false,
  23. version: false,
  24. output: file,
  25. o: file,
  26. comments: '/<TMODJS>/',
  27. screw_ie8: false,
  28. export_all: false
  29. };
  30. if (isJSON) {
  31. command.expr = true;
  32. command.beautify = command.b = 'quote-keys=true,width=80';
  33. }
  34. init(command);
  35. };
  36. exports.minify = function (file) {
  37. var command = {
  38. _: [file],
  39. expr: false,
  40. self: false,
  41. v: false,
  42. verbose: false,
  43. stats: false,
  44. acorn: false,
  45. spidermonkey: false,
  46. lint: false,
  47. V: false,
  48. version: false,
  49. output: file,
  50. o: file,
  51. mangle: true,
  52. m: true,
  53. reserved: 'include,require',
  54. r: 'include,require',
  55. comments: '/<TMODJS>|^$/',
  56. compress: 'warnings=fasle',
  57. c: 'warnings=false',
  58. beautify: 'beautify=false,ascii-only=true',
  59. b: 'beautify=false,ascii-only=true',
  60. screw_ie8: false,
  61. export_all: false
  62. };
  63. init(command);
  64. };
  65. function init (ARGS) {
  66. var COMPRESS = getOptions("c", true);
  67. var MANGLE = getOptions("m", true);
  68. var BEAUTIFY = getOptions("b", true);
  69. if (ARGS.d) {
  70. if (COMPRESS) COMPRESS.global_defs = getOptions("d");
  71. }
  72. if (ARGS.r) {
  73. if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
  74. }
  75. var OUTPUT_OPTIONS = {
  76. beautify: BEAUTIFY ? true : false
  77. };
  78. if (ARGS.screw_ie8) {
  79. if (COMPRESS) COMPRESS.screw_ie8 = true;
  80. if (MANGLE) MANGLE.screw_ie8 = true;
  81. OUTPUT_OPTIONS.screw_ie8 = true;
  82. }
  83. if (BEAUTIFY)
  84. UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
  85. if (ARGS.comments) {
  86. if (/^\//.test(ARGS.comments)) {
  87. OUTPUT_OPTIONS.comments = new Function("return(" + ARGS.comments + ")")();
  88. } else if (ARGS.comments == "all") {
  89. OUTPUT_OPTIONS.comments = true;
  90. } else {
  91. OUTPUT_OPTIONS.comments = function(node, comment) {
  92. var text = comment.value;
  93. var type = comment.type;
  94. if (type == "comment2") {
  95. // multiline comment
  96. return /@preserve|@license|@cc_on/i.test(text);
  97. }
  98. }
  99. }
  100. }
  101. var files = ARGS._.slice();
  102. if (ARGS.self) {
  103. if (files.length > 0) {
  104. sys.error("WARN: Ignoring input files since --self was passed");
  105. }
  106. files = UglifyJS.FILES;
  107. if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
  108. ARGS.export_all = true;
  109. }
  110. var ORIG_MAP = ARGS.in_source_map;
  111. if (ORIG_MAP) {
  112. ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
  113. if (files.length == 0) {
  114. sys.error("INFO: Using file from the input source map: " + ORIG_MAP.file);
  115. files = [ ORIG_MAP.file ];
  116. }
  117. if (ARGS.source_map_root == null) {
  118. ARGS.source_map_root = ORIG_MAP.sourceRoot;
  119. }
  120. }
  121. if (files.length == 0) {
  122. files = [ "-" ];
  123. }
  124. if (files.indexOf("-") >= 0 && ARGS.source_map) {
  125. sys.error("ERROR: Source map doesn't work with input from STDIN");
  126. process.exit(1);
  127. }
  128. if (files.filter(function(el){ return el == "-" }).length > 1) {
  129. sys.error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
  130. process.exit(1);
  131. }
  132. var STATS = {};
  133. var OUTPUT_FILE = ARGS.o;
  134. var TOPLEVEL = null;
  135. var P_RELATIVE = ARGS.p && ARGS.p == "relative";
  136. var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
  137. file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE,
  138. root: ARGS.source_map_root,
  139. orig: ORIG_MAP
  140. }) : null;
  141. OUTPUT_OPTIONS.source_map = SOURCE_MAP;
  142. try {
  143. var output = UglifyJS.OutputStream(OUTPUT_OPTIONS);
  144. var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS);
  145. } catch(ex) {
  146. if (ex instanceof UglifyJS.DefaultsError) {
  147. sys.error(ex.msg);
  148. sys.error("Supported options:");
  149. sys.error(sys.inspect(ex.defs));
  150. process.exit(1);
  151. }
  152. }
  153. async.eachLimit(files, 1, function (file, cb) {
  154. read_whole_file(file, function (err, code) {
  155. if (err) {
  156. sys.error("ERROR: can't read file: " + file);
  157. process.exit(1);
  158. }
  159. if (ARGS.p != null) {
  160. if (P_RELATIVE) {
  161. file = path.relative(path.dirname(ARGS.source_map), file);
  162. } else {
  163. var p = parseInt(ARGS.p, 10);
  164. if (!isNaN(p)) {
  165. file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
  166. }
  167. }
  168. }
  169. time_it("parse", function(){
  170. if (ARGS.spidermonkey) {
  171. var program = JSON.parse(code);
  172. if (!TOPLEVEL) TOPLEVEL = program;
  173. else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
  174. }
  175. else if (ARGS.acorn) {
  176. TOPLEVEL = acorn.parse(code, {
  177. locations : true,
  178. trackComments : true,
  179. sourceFile : file,
  180. program : TOPLEVEL
  181. });
  182. }
  183. else {
  184. TOPLEVEL = UglifyJS.parse(code, {
  185. filename : file,
  186. toplevel : TOPLEVEL,
  187. expression : ARGS.expr
  188. });
  189. };
  190. });
  191. cb();
  192. });
  193. }, function () {
  194. if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
  195. TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
  196. });
  197. if (ARGS.wrap) {
  198. TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
  199. }
  200. if (ARGS.enclose) {
  201. var arg_parameter_list = ARGS.enclose;
  202. if (arg_parameter_list === true) {
  203. arg_parameter_list = [];
  204. }
  205. else if (!(arg_parameter_list instanceof Array)) {
  206. arg_parameter_list = [arg_parameter_list];
  207. }
  208. TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
  209. }
  210. var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
  211. if (SCOPE_IS_NEEDED) {
  212. time_it("scope", function(){
  213. TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
  214. if (ARGS.lint) {
  215. TOPLEVEL.scope_warnings();
  216. }
  217. });
  218. }
  219. if (COMPRESS) {
  220. time_it("squeeze", function(){
  221. TOPLEVEL = TOPLEVEL.transform(compressor);
  222. });
  223. }
  224. if (SCOPE_IS_NEEDED) {
  225. time_it("scope", function(){
  226. TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
  227. if (MANGLE) {
  228. TOPLEVEL.compute_char_frequency(MANGLE);
  229. }
  230. });
  231. }
  232. if (MANGLE) time_it("mangle", function(){
  233. TOPLEVEL.mangle_names(MANGLE);
  234. });
  235. time_it("generate", function(){
  236. TOPLEVEL.print(output);
  237. });
  238. output = output.get();
  239. if (SOURCE_MAP) {
  240. fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
  241. var source_map_url = ARGS.source_map_url || (
  242. P_RELATIVE
  243. ? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map)
  244. : ARGS.source_map
  245. );
  246. output += "\n//# sourceMappingURL=" + source_map_url;
  247. }
  248. if (OUTPUT_FILE) {
  249. fs.writeFileSync(OUTPUT_FILE, output, "utf8");
  250. } else {
  251. sys.print(output);
  252. sys.error("\n");
  253. }
  254. if (ARGS.stats) {
  255. sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
  256. count: files.length
  257. }));
  258. for (var i in STATS) if (STATS.hasOwnProperty(i)) {
  259. sys.error(UglifyJS.string_template("- {name}: {time}s", {
  260. name: i,
  261. time: (STATS[i] / 1000).toFixed(3)
  262. }));
  263. }
  264. }
  265. });
  266. /* -----[ functions ]----- */
  267. function getOptions(x, constants) {
  268. x = ARGS[x];
  269. if (!x) return null;
  270. var ret = {};
  271. if (x !== true) {
  272. var ast;
  273. try {
  274. ast = UglifyJS.parse(x);
  275. } catch(ex) {
  276. if (ex instanceof UglifyJS.JS_Parse_Error) {
  277. sys.error("Error parsing arguments in: " + x);
  278. process.exit(1);
  279. }
  280. }
  281. ast.walk(new UglifyJS.TreeWalker(function(node){
  282. if (node instanceof UglifyJS.AST_Toplevel) return; // descend
  283. if (node instanceof UglifyJS.AST_SimpleStatement) return; // descend
  284. if (node instanceof UglifyJS.AST_Seq) return; // descend
  285. if (node instanceof UglifyJS.AST_Assign) {
  286. var name = node.left.print_to_string({ beautify: false }).replace(/-/g, "_");
  287. var value = node.right;
  288. if (constants)
  289. value = new Function("return (" + value.print_to_string() + ")")();
  290. ret[name] = value;
  291. return true; // no descend
  292. }
  293. sys.error(node.TYPE)
  294. sys.error("Error parsing arguments in: " + x);
  295. process.exit(1);
  296. }));
  297. }
  298. return ret;
  299. }
  300. function read_whole_file(filename, cb) {
  301. if (filename == "-") {
  302. var chunks = [];
  303. process.stdin.setEncoding('utf-8');
  304. process.stdin.on('data', function (chunk) {
  305. chunks.push(chunk);
  306. }).on('end', function () {
  307. cb(null, chunks.join(""));
  308. });
  309. process.openStdin();
  310. } else {
  311. fs.readFile(filename, "utf-8", cb);
  312. }
  313. }
  314. function time_it(name, cont) {
  315. var t1 = new Date().getTime();
  316. var ret = cont();
  317. if (ARGS.stats) {
  318. var spent = new Date().getTime() - t1;
  319. if (STATS[name]) STATS[name] += spent;
  320. else STATS[name] = spent;
  321. }
  322. return ret;
  323. }
  324. /*init end*/
  325. };