added --acorn and --spidermonkey options

This commit is contained in:
Mihai Bazon
2012-10-05 15:22:12 +03:00
parent ecd9f21467
commit e88dcc3819
2 changed files with 95 additions and 32 deletions

View File

@@ -23,8 +23,9 @@ files.
The available options are: The available options are:
--source-map Specify an output file where to generate source map. --source-map Specify an output file where to generate source map.
[string]
--source-map-root The path to the original source to be included in the --source-map-root The path to the original source to be included in the
source map. source map. [string]
--in-source-map Input source map, useful if you're compressing JS that was --in-source-map Input source map, useful if you're compressing JS that was
generated from some other original code. generated from some other original code.
-p, --prefix Skip prefix for original filenames that appear in source -p, --prefix Skip prefix for original filenames that appear in source
@@ -49,12 +50,15 @@ The available options are:
compression is on, because of dead code removal or compression is on, because of dead code removal or
cascading statements into sequences. [string] cascading statements into sequences. [string]
--stats Display operations run time on STDERR. [boolean] --stats Display operations run time on STDERR. [boolean]
--acorn Use Acorn for parsing. [boolean]
--spidermonkey Assume input fles are SpiderMonkey AST format (as JSON).
[boolean]
-v, --verbose Verbose [boolean] -v, --verbose Verbose [boolean]
Specify `--output` (`-o`) to declare the output file. Otherwise the output Specify `--output` (`-o`) to declare the output file. Otherwise the output
goes to STDOUT. goes to STDOUT.
### Source map options ## Source map options
UglifyJS2 can generate a source map file, which is highly useful for UglifyJS2 can generate a source map file, which is highly useful for
debugging your compressed JavaScript. To get a source map, pass debugging your compressed JavaScript. To get a source map, pass
@@ -83,7 +87,7 @@ mapping will refer to `http://foo.com/src/js/file1.js` and
as the source map root, and the original files as `js/file1.js` and as the source map root, and the original files as `js/file1.js` and
`js/file2.js`). `js/file2.js`).
#### Composed source map ### Composed source map
When you're compressing JS code that was output by a compiler such as When you're compressing JS code that was output by a compiler such as
CoffeeScript, mapping to the JS code won't be too helpful. Instead, you'd CoffeeScript, mapping to the JS code won't be too helpful. Instead, you'd
@@ -98,7 +102,7 @@ To use this feature you need to pass `--in-source-map
to the file containing the generated JS, so if that's correct you can omit to the file containing the generated JS, so if that's correct you can omit
input files from the command line. input files from the command line.
### Mangler options ## Mangler options
To enable the mangler you need to pass `--mangle` (`-m`). Optionally you To enable the mangler you need to pass `--mangle` (`-m`). Optionally you
can pass `-m sort` (we'll possibly have other flags in the future) in order can pass `-m sort` (we'll possibly have other flags in the future) in order
@@ -115,7 +119,7 @@ comma-separated list of names. For example:
to prevent the `require`, `exports` and `$` names from being changed. to prevent the `require`, `exports` and `$` names from being changed.
### Compressor options ## Compressor options
You need to pass `--compress` (`-c`) to enable the compressor. Optionally You need to pass `--compress` (`-c`) to enable the compressor. Optionally
you can pass a comma-separated list of options. Options are in the form you can pass a comma-separated list of options. Options are in the form
@@ -184,7 +188,7 @@ will evaluate references to them to the value itself and drop unreachable
code as usual. The possible downside of this approach is that the build code as usual. The possible downside of this approach is that the build
will contain the `const` declarations. will contain the `const` declarations.
### Beautifier options ## Beautifier options
The code generator tries to output shortest code possible by default. In The code generator tries to output shortest code possible by default. In
case you want beautified output, pass `--beautify` (`-b`). Optionally you case you want beautified output, pass `--beautify` (`-b`). Optionally you
@@ -242,3 +246,34 @@ discarded by the compressor as not referenced.
The safest comments where to place copyright information (or other info that The safest comments where to place copyright information (or other info that
needs to me kept in the output) are comments attached to toplevel nodes. needs to me kept in the output) are comments attached to toplevel nodes.
## Support for the SpiderMonkey AST
UglifyJS2 has its own abstract syntax tree format; for
[practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/)
we can't easily change to using the SpiderMonkey AST internally. However,
UglifyJS now has a converter which can import a SpiderMonkey AST.
For example [Acorn](https://github.com/marijnh/acorn) is a super-fast parser
that produces a SpiderMonkey AST. It has a small CLI utility that parses
one file and dumps the AST in JSON on the standard output. To use UglifyJS
to mangle and compress that:
acorn file.js | uglifyjs2 --spidermonkey -m -c
The `--spidermonkey` option tells UglifyJS that all input files are not
JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we
don't use our own parser in this case, but just transform that AST into our
internal AST.
### Use Acorn for parsing
More for fun, I added the `--acorn` option which will use Acorn to do all
the parsing. If you pass this option, UglifyJS will `require("acorn")`. At
the time I'm writing this it needs
[this commit](https://github.com/mishoo/acorn/commit/17c0d189c7f9ce5447293569036949b5d0a05fef)
in Acorn to support multiple input files and properly generate source maps.
Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but
converting the SpiderMonkey tree that Acorn produces takes another 150ms so
in total it's a bit more than just using UglifyJS's own parser.

View File

@@ -7,6 +7,7 @@ var UglifyJS = require("../tools/node");
var sys = require("util"); var sys = require("util");
var optimist = require("optimist"); var optimist = require("optimist");
var fs = require("fs"); var fs = require("fs");
var acorn;
var ARGS = optimist var ARGS = optimist
.usage("$0 input1.js [input2.js ...] [options]\n\ .usage("$0 input1.js [input2.js ...] [options]\n\
Use a single dash to read input from the standard input.\ Use a single dash to read input from the standard input.\
@@ -40,6 +41,8 @@ Note that currently not *all* comments can be kept when compression is on, \
because of dead code removal or cascading statements into sequences.") because of dead code removal or cascading statements into sequences.")
.describe("stats", "Display operations run time on STDERR.") .describe("stats", "Display operations run time on STDERR.")
.describe("acorn", "Use Acorn for parsing.")
.describe("spidermonkey", "Assume input fles are SpiderMonkey AST format (as JSON).")
.describe("v", "Verbose") .describe("v", "Verbose")
.alias("p", "prefix") .alias("p", "prefix")
@@ -60,40 +63,25 @@ because of dead code removal or cascading statements into sequences.")
.string("comments") .string("comments")
.boolean("v") .boolean("v")
.boolean("stats") .boolean("stats")
.boolean("acorn")
.boolean("spidermonkey")
.wrap(80) .wrap(80)
.argv .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) { if (ARGS.h || ARGS.help) {
sys.puts(optimist.help()); sys.puts(optimist.help());
process.exit(0); process.exit(0);
} }
function getOptions(x) { if (ARGS.acorn) {
x = ARGS[x]; acorn = require("acorn");
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;
} }
normalize(ARGS);
var COMPRESS = getOptions("c"); var COMPRESS = getOptions("c");
var MANGLE = getOptions("m"); var MANGLE = getOptions("m");
var BEAUTIFY = getOptions("b"); var BEAUTIFY = getOptions("b");
@@ -190,13 +178,32 @@ files.forEach(function(file) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/"); file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
} }
time_it("parse", function(){ time_it("parse", function(){
TOPLEVEL = UglifyJS.parse(code, { if (ARGS.spidermonkey) {
filename: file, var program = JSON.parse(code);
toplevel: TOPLEVEL if (!TOPLEVEL) TOPLEVEL = program;
}); else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
}
else if (ARGS.acorn) {
TOPLEVEL = acorn.parse(code, {
locations : true,
trackComments : true,
sourceFile : file,
program : TOPLEVEL
});
}
else {
TOPLEVEL = UglifyJS.parse(code, {
filename: file,
toplevel: TOPLEVEL
});
};
}); });
}); });
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
});
var SCOPE_IS_NEEDED = COMPRESS || MANGLE; var SCOPE_IS_NEEDED = COMPRESS || MANGLE;
if (SCOPE_IS_NEEDED) { if (SCOPE_IS_NEEDED) {
@@ -256,6 +263,27 @@ if (ARGS.stats) {
/* -----[ functions ]----- */ /* -----[ functions ]----- */
function normalize(o) {
for (var i in o) if (o.hasOwnProperty(i) && /-/.test(i)) {
o[i.replace(/-/g, "_")] = o[i];
delete o[i];
}
}
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;
}
function read_whole_file(filename) { function read_whole_file(filename) {
if (filename == "-") { if (filename == "-") {
// XXX: this sucks. How does one read the whole STDIN // XXX: this sucks. How does one read the whole STDIN
@@ -279,4 +307,4 @@ function time_it(name, cont) {
else STATS[name] = spent; else STATS[name] = spent;
} }
return ret; return ret;
}; }