241 lines
6.0 KiB
JavaScript
Executable File
241 lines
6.0 KiB
JavaScript
Executable File
#! /usr/bin/env node
|
|
// -*- js -*-
|
|
|
|
"use strict";
|
|
|
|
var UglifyJS = require("../tools/node");
|
|
var sys = require("util");
|
|
var optimist = require("optimist");
|
|
var fs = require("fs");
|
|
var ARGS = optimist
|
|
.usage("$0 [options] input1.js [input2.js ...]\n\
|
|
Use a single dash to read input from the standard input.\
|
|
")
|
|
.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("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
|
|
.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.")
|
|
.describe("o", "Output file (default STDOUT).")
|
|
.describe("b", "Beautify output/specify output options.")
|
|
.describe("m", "Mangle names/pass mangler options.")
|
|
.describe("c", "Enable compressor/pass compressor options. \
|
|
Pass options like -c hoist_vars=false,if_return=false. \
|
|
Use -c with no argument if you want to disable the squeezer entirely.")
|
|
.describe("d", "Global definitions")
|
|
|
|
.describe("stats", "Display operations run time on STDERR.")
|
|
.describe("v", "Verbose")
|
|
|
|
.alias("p", "prefix")
|
|
.alias("o", "output")
|
|
.alias("v", "verbose")
|
|
.alias("b", "beautify")
|
|
.alias("m", "mangle")
|
|
.alias("c", "compress")
|
|
.alias("d", "define")
|
|
|
|
.string("b")
|
|
.string("m")
|
|
.string("c")
|
|
.string("d")
|
|
.boolean("v")
|
|
.boolean("stats")
|
|
|
|
.wrap(80)
|
|
|
|
.argv
|
|
;
|
|
|
|
function normalize(o) {
|
|
for (var i in o) if (o.hasOwnProperty(i) && /-/.test(i)) {
|
|
o[i.replace(/-/g, "_")] = o[i];
|
|
delete o[i];
|
|
}
|
|
}
|
|
|
|
normalize(ARGS);
|
|
|
|
if (ARGS.h || ARGS.help) {
|
|
sys.puts(optimist.help());
|
|
process.exit(0);
|
|
}
|
|
|
|
function getOptions(x) {
|
|
x = ARGS[x];
|
|
if (!x) return null;
|
|
var ret = {};
|
|
if (x !== true) {
|
|
x.replace(/^\s+|\s+$/g).split(/\s*,+\s*/).forEach(function(opt){
|
|
var a = opt.split(/\s*[=:]\s*/);
|
|
ret[a[0]] = a.length > 1 ? new Function("return(" + a[1] + ")")() : true;
|
|
});
|
|
normalize(ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
var COMPRESS = getOptions("c");
|
|
var MANGLE = getOptions("m");
|
|
var BEAUTIFY = getOptions("b");
|
|
|
|
if (COMPRESS && ARGS.d) {
|
|
COMPRESS.global_defs = getOptions("d");
|
|
}
|
|
|
|
var OUTPUT_OPTIONS = {
|
|
beautify: BEAUTIFY ? true : false
|
|
};
|
|
if (BEAUTIFY)
|
|
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
|
|
|
|
var files = ARGS._.slice();
|
|
|
|
var ORIG_MAP = ARGS.in_source_map;
|
|
|
|
if (ORIG_MAP) {
|
|
ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
|
|
if (files.length == 0) {
|
|
sys.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) {
|
|
sys.error("ERROR: Source map doesn't work with input from STDIN");
|
|
process.exit(1);
|
|
}
|
|
|
|
if (files.filter(function(el){ return el == "-" }).length > 1) {
|
|
sys.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 SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
|
|
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) {
|
|
sys.error(ex.msg);
|
|
sys.error("Supported options:");
|
|
sys.error(sys.inspect(ex.defs));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
files.forEach(function(file) {
|
|
var code = read_whole_file(file);
|
|
if (ARGS.p != null) {
|
|
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
|
|
}
|
|
time_it("parse", function(){
|
|
TOPLEVEL = UglifyJS.parse(code, {
|
|
filename: file,
|
|
toplevel: TOPLEVEL
|
|
});
|
|
});
|
|
});
|
|
|
|
var SCOPE_IS_NEEDED = COMPRESS || MANGLE;
|
|
|
|
if (SCOPE_IS_NEEDED) {
|
|
time_it("scope", function(){
|
|
TOPLEVEL.figure_out_scope();
|
|
});
|
|
}
|
|
|
|
if (COMPRESS) {
|
|
time_it("squeeze", function(){
|
|
TOPLEVEL = TOPLEVEL.transform(compressor);
|
|
});
|
|
}
|
|
|
|
if (SCOPE_IS_NEEDED) {
|
|
time_it("scope", function(){
|
|
TOPLEVEL.figure_out_scope();
|
|
if (MANGLE) {
|
|
TOPLEVEL.compute_char_frequency();
|
|
UglifyJS.base54.sort();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (MANGLE) time_it("mangle", function(){
|
|
TOPLEVEL.mangle_names(MANGLE);
|
|
});
|
|
time_it("generate", function(){
|
|
TOPLEVEL.print(output);
|
|
});
|
|
|
|
output = output.get();
|
|
|
|
if (SOURCE_MAP) {
|
|
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
|
|
output += "\n//@ sourceMappingURL=" + ARGS.source_map;
|
|
}
|
|
|
|
if (OUTPUT_FILE) {
|
|
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
|
|
} else {
|
|
sys.print(output);
|
|
sys.error("\n");
|
|
}
|
|
|
|
if (ARGS.stats) {
|
|
sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
|
|
count: files.length
|
|
}));
|
|
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
|
|
sys.error(UglifyJS.string_template("- {name}: {time}s", {
|
|
name: i,
|
|
time: (STATS[i] / 1000).toFixed(3)
|
|
}));
|
|
}
|
|
}
|
|
|
|
/* -----[ functions ]----- */
|
|
|
|
function read_whole_file(filename) {
|
|
if (filename == "-") {
|
|
// XXX: this sucks. How does one read the whole STDIN
|
|
// synchronously?
|
|
filename = "/dev/stdin";
|
|
}
|
|
try {
|
|
return fs.readFileSync(filename, "utf8");
|
|
} catch(ex) {
|
|
sys.error("ERROR: can't read file: " + filename);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
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;
|
|
};
|