From e88dcc3819bd5af933d124fc5bf2b021366d5f28 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 5 Oct 2012 15:22:12 +0300 Subject: [PATCH] added --acorn and --spidermonkey options --- README.md | 47 ++++++++++++++++++++++++++---- bin/uglifyjs2 | 80 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 95 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ce5e05ed..252a9c28 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,9 @@ files. The available options are: --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. + source map. [string] --in-source-map Input source map, useful if you're compressing JS that was generated from some other original code. -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 cascading statements into sequences. [string] --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] Specify `--output` (`-o`) to declare the output file. Otherwise the output goes to STDOUT. -### Source map options +## Source map options UglifyJS2 can generate a source map file, which is highly useful for 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 `js/file2.js`). -#### Composed source map +### Composed source map 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 @@ -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 input files from the command line. -### Mangler options +## Mangler options 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 @@ -115,7 +119,7 @@ comma-separated list of names. For example: 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 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 will contain the `const` declarations. -### Beautifier options +## Beautifier options The code generator tries to output shortest code possible by default. In 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 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. diff --git a/bin/uglifyjs2 b/bin/uglifyjs2 index cdecb848..17f82b58 100755 --- a/bin/uglifyjs2 +++ b/bin/uglifyjs2 @@ -7,6 +7,7 @@ var UglifyJS = require("../tools/node"); var sys = require("util"); var optimist = require("optimist"); var fs = require("fs"); +var acorn; var ARGS = optimist .usage("$0 input1.js [input2.js ...] [options]\n\ 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.") .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") .alias("p", "prefix") @@ -60,40 +63,25 @@ because of dead code removal or cascading statements into sequences.") .string("comments") .boolean("v") .boolean("stats") + .boolean("acorn") + .boolean("spidermonkey") .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; +if (ARGS.acorn) { + acorn = require("acorn"); } +normalize(ARGS); + var COMPRESS = getOptions("c"); var MANGLE = getOptions("m"); var BEAUTIFY = getOptions("b"); @@ -190,13 +178,32 @@ files.forEach(function(file) { file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/"); } time_it("parse", function(){ - TOPLEVEL = UglifyJS.parse(code, { - filename: file, - toplevel: TOPLEVEL - }); + if (ARGS.spidermonkey) { + var program = JSON.parse(code); + 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; if (SCOPE_IS_NEEDED) { @@ -256,6 +263,27 @@ if (ARGS.stats) { /* -----[ 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) { if (filename == "-") { // XXX: this sucks. How does one read the whole STDIN @@ -279,4 +307,4 @@ function time_it(name, cont) { else STATS[name] = spent; } return ret; -}; +} -- 2.34.1