UNPKG

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