From 6a099fba66eba896755f5a723a9d08034faee615 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 17 Oct 2012 16:17:14 +0300 Subject: [PATCH 01/18] define aborts on AST_If: true if both branches abort --- lib/compress.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 4a51a49f..d2e3ffc7 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -349,9 +349,7 @@ merge(Compressor.prototype, { extract_declarations_from_unreachable_code(compressor, stat, a); } else { a.push(stat); - if (stat instanceof AST_Jump) { - has_quit = true; - } + if (aborts(stat)) has_quit = true; } return a; }, []); @@ -771,6 +769,9 @@ merge(Compressor.prototype, { var n = this.body.length; return n > 0 && aborts(this.body[n - 1]); }); + def(AST_If, function(){ + return this.alternative && aborts(this.body) && aborts(this.alternative); + }); })(function(node, func){ node.DEFMETHOD("aborts", func); }); From 253bd8559bdc3b681fb665704718bc024f5d316e Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 17 Oct 2012 21:57:08 +0300 Subject: [PATCH 02/18] more small optimizations (unlikely to help for hand-written code) --- lib/compress.js | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index d2e3ffc7..1af4a2d6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -162,6 +162,9 @@ merge(Compressor.prototype, { if (val === null) { return make_node(AST_Null, orig).optimize(compressor); } + if (val instanceof RegExp) { + return make_node(AST_RegExp, orig).optimize(compressor); + } throw new Error(string_template("Can't handle constant of type: {type}", { type: typeof val })); @@ -1109,7 +1112,7 @@ merge(Compressor.prototype, { extract_declarations_from_unreachable_code(compressor, self.alternative, a); } a.push(self.body); - return make_node(AST_BlockStatement, self, { body: a }); + return make_node(AST_BlockStatement, self, { body: a }).transform(compressor); } } else { compressor.warn("Condition always false [{file}:{line},{col}]", self.condition.start); @@ -1117,7 +1120,7 @@ merge(Compressor.prototype, { var a = []; extract_declarations_from_unreachable_code(compressor, self.body, a); if (self.alternative) a.push(self.alternative); - return make_node(AST_BlockStatement, self, { body: a }); + return make_node(AST_BlockStatement, self, { body: a }).transform(compressor); } } } @@ -1134,7 +1137,7 @@ merge(Compressor.prototype, { if (is_empty(self.body) && is_empty(self.alternative)) { return make_node(AST_SimpleStatement, self.condition, { body: self.condition - }); + }).transform(compressor); } if (self.body instanceof AST_SimpleStatement && self.alternative instanceof AST_SimpleStatement) { @@ -1144,7 +1147,7 @@ merge(Compressor.prototype, { consequent : self.body.body, alternative : self.alternative.body }) - }); + }).transform(compressor); } if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) { if (negated_is_best) return make_node(AST_SimpleStatement, self, { @@ -1153,14 +1156,14 @@ merge(Compressor.prototype, { left : negated, right : self.body.body }) - }); + }).transform(compressor); return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "&&", left : self.condition, right : self.body.body }) - }); + }).transform(compressor); } if (self.body instanceof AST_EmptyStatement && self.alternative @@ -1171,7 +1174,7 @@ merge(Compressor.prototype, { left : self.condition, right : self.alternative.body }) - }); + }).transform(compressor); } if (self.body instanceof AST_Exit && self.alternative instanceof AST_Exit @@ -1182,7 +1185,7 @@ merge(Compressor.prototype, { consequent : self.body.value, alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor) }) - }); + }).transform(compressor); } if (self.body instanceof AST_If && !self.body.alternative @@ -1200,7 +1203,7 @@ merge(Compressor.prototype, { self.alternative = null; return make_node(AST_BlockStatement, self, { body: [ self, alt ] - }); + }).transform(compressor); } } if (aborts(self.alternative)) { @@ -1210,12 +1213,17 @@ merge(Compressor.prototype, { self.alternative = null; return make_node(AST_BlockStatement, self, { body: [ self, body ] - }); + }).transform(compressor); } return self; }); OPT(AST_Switch, function(self, compressor){ + if (self.body.length == 0 && compressor.option("conditionals")) { + return make_node(AST_SimpleStatement, self, { + body: self.expression + }).transform(compressor); + } var last_branch = self.body[self.body.length - 1]; if (last_branch) { var stat = last_branch.body[last_branch.body.length - 1]; // last statement @@ -1303,14 +1311,14 @@ merge(Compressor.prototype, { left: exp.expression, operator: "+", right: make_node(AST_String, self, { value: "" }) - }); + }).transform(compressor); } } if (compressor.option("side_effects")) { if (self.expression instanceof AST_Function && self.args.length == 0 && !self.expression.has_side_effects()) { - return make_node(AST_Undefined, self); + return make_node(AST_Undefined, self).transform(compressor); } } return self; @@ -1614,4 +1622,14 @@ merge(Compressor.prototype, { return self; }); + function literals_in_boolean_context(self, compressor) { + if (compressor.option("booleans") && compressor.in_boolean_context()) { + return make_node(AST_True, self); + } + return self; + }; + OPT(AST_Array, literals_in_boolean_context); + OPT(AST_Object, literals_in_boolean_context); + OPT(AST_RegExp, literals_in_boolean_context); + })(); From 4482fdd63f0a9fcd8e6f92f2af8076cdb3240af6 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 17 Oct 2012 21:59:36 +0300 Subject: [PATCH 03/18] added note about API docs and online demo --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 793306d8..8cf0ba42 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,11 @@ UglifyJS 2 UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit. -For now this page documents the command line utility. More advanced -API documentation will be made available later. +This page documents the command line utility. For +[API and internals documentation see my website](http://lisperator.net/uglifyjs/). +There's also an +[in-browser online demo](http://lisperator.net/uglifyjs/#demo) (for Firefox, +Chrome and probably Safari). Install ------- From a5e75c5a2125662b1ed1a93e8b2204bf33bf44f8 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 17 Oct 2012 22:00:11 +0300 Subject: [PATCH 04/18] v2.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db49c772..943eb5b8 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "JavaScript parser, mangler/compressor and beautifier toolkit", "homepage": "http://lisperator.net/uglifyjs", "main": "tools/node.js", - "version": "2.0.0", + "version": "2.1.0", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From 4fe4257c69734448fb242dfaab3804f883165df3 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 18 Oct 2012 10:54:10 +0300 Subject: [PATCH 05/18] fix `--comments` (close #16) --- bin/uglifyjs2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/uglifyjs2 b/bin/uglifyjs2 index acca760d..aadda7cf 100755 --- a/bin/uglifyjs2 +++ b/bin/uglifyjs2 @@ -127,7 +127,7 @@ if (ARGS.comments) { var type = comment.type; if (type == "comment2") { // multiline comment - return /@preserve|@license|@cc_on/i.test(test); + return /@preserve|@license|@cc_on/i.test(text); } } } From 6aa56f92fe07edfc677d390a2e26b37c98da0968 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 18 Oct 2012 10:54:30 +0300 Subject: [PATCH 06/18] v2.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 943eb5b8..7b5bf89c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "JavaScript parser, mangler/compressor and beautifier toolkit", "homepage": "http://lisperator.net/uglifyjs", "main": "tools/node.js", - "version": "2.1.0", + "version": "2.1.1", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From afb7faa6fadee46a6ab46232eddba2121c77549b Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 18 Oct 2012 15:14:57 +0300 Subject: [PATCH 07/18] more optimizations for some break/continue cases --- lib/ast.js | 8 +- lib/compress.js | 45 ++++++++++- lib/utils.js | 6 ++ test/compress/labels.js | 163 ++++++++++++++++++++++++++++++++++++++++ test/run-tests.js | 3 +- 5 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 test/compress/labels.js diff --git a/lib/ast.js b/lib/ast.js index 177bd1d7..44cbed12 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -929,10 +929,10 @@ TreeWalker.prototype = { } else { for (var i = stack.length; --i >= 0;) { var x = stack[i]; - if (x instanceof AST_Switch) return x; - if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { - return (x.body instanceof AST_BlockStatement ? x.body : x); - } + if (x instanceof AST_Switch + || x instanceof AST_For + || x instanceof AST_ForIn + || x instanceof AST_DWLoop) return x; } } } diff --git a/lib/compress.js b/lib/compress.js index 1af4a2d6..cde4b6f7 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -186,6 +186,14 @@ merge(Compressor.prototype, { return false; }; + function loop_body(x) { + if (x instanceof AST_Switch) return x; + if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { + return (x.body instanceof AST_BlockStatement ? x.body : x); + } + return x; + }; + function tighten_body(statements, compressor) { var CHANGED; do { @@ -303,8 +311,13 @@ merge(Compressor.prototype, { } var ab = aborts(stat.body); + var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null; if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) - || (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) { + || (ab instanceof AST_Continue && self === loop_body(lct)) + || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { + if (ab.label) { + remove(ab.label.thedef.references, ab.label); + } CHANGED = true; var body = as_statement_array(stat.body).slice(0, -1); stat = stat.clone(); @@ -320,8 +333,13 @@ merge(Compressor.prototype, { } var ab = aborts(stat.alternative); + var lct = ab instanceof AST_LoopControl ? compressor.loopcontrol_target(ab.label) : null; if (ab && ((ab instanceof AST_Return && !ab.value && in_lambda) - || (ab instanceof AST_Continue && self === compressor.loopcontrol_target(ab.label)))) { + || (ab instanceof AST_Continue && self === loop_body(lct)) + || (ab instanceof AST_Break && lct instanceof AST_BlockStatement && self === lct))) { + if (ab.label) { + remove(ab.label.thedef.references, ab.label); + } CHANGED = true; stat = stat.clone(); stat.body = make_node(AST_BlockStatement, stat.body, { @@ -347,11 +365,26 @@ merge(Compressor.prototype, { function eliminate_dead_code(statements, compressor) { var has_quit = false; var orig = statements.length; + var self = compressor.self(); statements = statements.reduce(function(a, stat){ if (has_quit) { extract_declarations_from_unreachable_code(compressor, stat, a); } else { - a.push(stat); + if (stat instanceof AST_LoopControl) { + var lct = compressor.loopcontrol_target(stat.label); + if ((stat instanceof AST_Break + && lct instanceof AST_BlockStatement + && loop_body(lct) === self) || (stat instanceof AST_Continue + && loop_body(lct) === self)) { + if (stat.label) { + remove(stat.label.thedef.references, stat.label); + } + } else { + a.push(stat); + } + } else { + a.push(stat); + } if (aborts(stat)) has_quit = true; } return a; @@ -795,6 +828,10 @@ merge(Compressor.prototype, { }); OPT(AST_LabeledStatement, function(self, compressor){ + if (self.body instanceof AST_Break + && compressor.loopcontrol_target(self.body.label) === self.body) { + return make_node(AST_EmptyStatement, self); + } return self.label.references.length == 0 ? self.body : self; }); @@ -1227,7 +1264,7 @@ merge(Compressor.prototype, { var last_branch = self.body[self.body.length - 1]; if (last_branch) { var stat = last_branch.body[last_branch.body.length - 1]; // last statement - if (stat instanceof AST_Break && compressor.loopcontrol_target(stat.label) === self) + if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self) last_branch.body.pop(); } return self; diff --git a/lib/utils.js b/lib/utils.js index 79039665..4d3d60f6 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -166,6 +166,12 @@ function string_template(text, props) { }); }; +function remove(array, el) { + for (var i = array.length; --i >= 0;) { + if (array[i] === el) array.splice(i, 1); + } +}; + function mergeSort(array, cmp) { if (array.length < 2) return array.slice(); function merge(a, b) { diff --git a/test/compress/labels.js b/test/compress/labels.js new file mode 100644 index 00000000..044b7a7e --- /dev/null +++ b/test/compress/labels.js @@ -0,0 +1,163 @@ +labels_1: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + out: { + if (foo) break out; + console.log("bar"); + } + }; + expect: { + foo || console.log("bar"); + } +} + +labels_2: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + out: { + if (foo) print("stuff"); + else break out; + console.log("here"); + } + }; + expect: { + if (foo) { + print("stuff"); + console.log("here"); + } + } +} + +labels_3: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + for (var i = 0; i < 5; ++i) { + if (i < 3) continue; + console.log(i); + } + }; + expect: { + for (var i = 0; i < 5; ++i) + i < 3 || console.log(i); + } +} + +labels_4: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + out: for (var i = 0; i < 5; ++i) { + if (i < 3) continue out; + console.log(i); + } + }; + expect: { + for (var i = 0; i < 5; ++i) + i < 3 || console.log(i); + } +} + +labels_5: { + options = { if_return: true, conditionals: true, dead_code: true }; + // should keep the break-s in the following + input: { + while (foo) { + if (bar) break; + console.log("foo"); + } + out: while (foo) { + if (bar) break out; + console.log("foo"); + } + }; + expect: { + while (foo) { + if (bar) break; + console.log("foo"); + } + out: while (foo) { + if (bar) break out; + console.log("foo"); + } + } +} + +labels_6: { + input: { + out: break out; + }; + expect: {} +} + +labels_7: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + while (foo) { + x(); + y(); + continue; + } + }; + expect: { + while (foo) { + x(); + y(); + } + } +} + +labels_8: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + while (foo) { + x(); + y(); + break; + } + }; + expect: { + while (foo) { + x(); + y(); + break; + } + } +} + +labels_9: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + out: while (foo) { + x(); + y(); + continue out; + z(); + k(); + } + }; + expect: { + while (foo) { + x(); + y(); + } + } +} + +labels_10: { + options = { if_return: true, conditionals: true, dead_code: true }; + input: { + out: while (foo) { + x(); + y(); + break out; + z(); + k(); + } + }; + expect: { + out: while (foo) { + x(); + y(); + break out; + } + } +} diff --git a/test/run-tests.js b/test/run-tests.js index 001140f3..0568c6a7 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -73,12 +73,13 @@ function run_compress_tests() { var cmp = new U.Compressor(options, true); var expect = make_code(as_toplevel(test.expect), false); var input = as_toplevel(test.input); + var input_code = make_code(test.input); var output = input.transform(cmp); output.figure_out_scope(); output = make_code(output, false); if (expect != output) { log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", { - input: make_code(test.input), + input: input_code, output: output, expected: expect }); From 6f45928a73009b6b1aee8c1886f08ff5b83e6cb1 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 18 Oct 2012 15:49:15 +0300 Subject: [PATCH 08/18] add fromString argument to `UglifyJS.minify` (allows to pass the source code, instead of file names, as first argument). close #17 --- README.md | 7 +++++++ tools/node.js | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8cf0ba42..f519c119 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,7 @@ There's a single toplevel function which combines all the steps. If you don't need additional customization, you might want to go with `minify`. Example: + // see "fromString" below if you need to pass code instead of file name var result = UglifyJS.minify("/path/to/file.js"); console.log(result.code); // minified output @@ -354,6 +355,12 @@ can use the `inSourceMap` argument: The `inSourceMap` is only used if you also request `outSourceMap` (it makes no sense otherwise). +Other options: + +- `warnings` (default `false`) — pass `true` to display compressor warnings. +- `fromString` (default `false`) — if you pass `true` then you can pass + JavaScript source code, rather than file names. + We could add more options to `UglifyJS.minify` — if you need additional functionality please suggest! diff --git a/tools/node.js b/tools/node.js index 07c3bfae..6a21710a 100644 --- a/tools/node.js +++ b/tools/node.js @@ -66,6 +66,7 @@ exports.minify = function(files, options) { options = UglifyJS.defaults(options, { outSourceMap : null, inSourceMap : null, + fromString : false, warnings : false, }); if (typeof files == "string") @@ -74,9 +75,11 @@ exports.minify = function(files, options) { // 1. parse var toplevel = null; files.forEach(function(file){ - var code = fs.readFileSync(file, "utf8"); + var code = options.fromString + ? file + : fs.readFileSync(file, "utf8"); toplevel = UglifyJS.parse(code, { - filename: file, + filename: options.fromString ? "?" : file, toplevel: toplevel }); }); From 11dffe950ed5beb1bbf224a594cf0fae7dc11f9a Mon Sep 17 00:00:00 2001 From: Sergej Tatarincev Date: Fri, 19 Oct 2012 12:35:19 +0300 Subject: [PATCH 09/18] Add sourceRoot option to minify --- README.md | 8 ++++++++ tools/node.js | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f519c119..4bf29b20 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,14 @@ Note that the source map is not saved in a file, it's just returned in `result.map`. The value passed for `outSourceMap` is only used to set the `file` attribute in the source map (see [the spec][sm-spec]). +You can also specify sourceRoot property to be included in source map: + + var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { + outSourceMap: "out.js.map", + sourceRoot: "http://example.com/src" + }); + + If you're compressing compiled JavaScript and have a source map for it, you can use the `inSourceMap` argument: diff --git a/tools/node.js b/tools/node.js index 6a21710a..80cc1858 100644 --- a/tools/node.js +++ b/tools/node.js @@ -65,6 +65,7 @@ for (var i in UglifyJS) { exports.minify = function(files, options) { options = UglifyJS.defaults(options, { outSourceMap : null, + sourceRoot : null, inSourceMap : null, fromString : false, warnings : false, @@ -104,7 +105,8 @@ exports.minify = function(files, options) { } if (options.outSourceMap) map = UglifyJS.SourceMap({ file: options.outSourceMap, - orig: inMap + orig: inMap, + root: options.sourceRoot }); var stream = UglifyJS.OutputStream({ source_map: map }); toplevel.print(stream); From fc8314e810c91431cd18fa9a784adb7867726013 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Fri, 19 Oct 2012 12:57:29 +0300 Subject: [PATCH 10/18] minor fix for dropping unused definitions. function f(x, y) { var g = function() { return h() }; var h = function() { return g() }; return x + y; } now compresses to `function f(x, y) { return x + y }` --- lib/compress.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index cde4b6f7..3e5b524d 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -741,9 +741,10 @@ merge(Compressor.prototype, { }); def(AST_SimpleStatement, function(){ - if (this.body instanceof AST_Function) return false; return this.body.has_side_effects(); }); + def(AST_Defun, function(){ return true }); + def(AST_Function, function(){ return false }); def(AST_Binary, function(){ return this.left.has_side_effects() || this.right.has_side_effects(); @@ -1354,7 +1355,7 @@ merge(Compressor.prototype, { if (compressor.option("side_effects")) { if (self.expression instanceof AST_Function && self.args.length == 0 - && !self.expression.has_side_effects()) { + && !AST_Block.prototype.has_side_effects.call(self.expression)) { return make_node(AST_Undefined, self).transform(compressor); } } From 12f71e01d02a2f4cc76cb8471036992e0cba9cf1 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Sat, 20 Oct 2012 11:12:21 +0300 Subject: [PATCH 11/18] alternate hack to disable deprecation warning ref #9, close #20 --- tools/node.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tools/node.js b/tools/node.js index 6a21710a..86df2a9f 100644 --- a/tools/node.js +++ b/tools/node.js @@ -1,27 +1,25 @@ -var save_stderr = process.stderr; +var path = require("path"); var fs = require("fs"); -// discard annoying NodeJS warning ("path.existsSync is now called `fs.existsSync`.") -var devnull = fs.createWriteStream("/dev/null"); -process.__defineGetter__("stderr", function(){ - return devnull; -}); +// Avoid NodeJS warning. +// +// There's a --no-deprecation command line argument supported by +// NodeJS, but that's tricky to use, so I'd like to set it from the +// program itself. Turns out you need to set `process.noDeprecation`, +// but by the time you can set that the `path` module is already +// loaded and `path.existsSync` is already changed to display that +// warning, therefore here's the poor solution: +path.existsSync = fs.existsSync; var vm = require("vm"); var sys = require("util"); -var path = require("path"); var UglifyJS = vm.createContext({ sys : sys, console : console, - MOZ_SourceMap : require("source-map") }); -process.__defineGetter__("stderr", function(){ - return save_stderr; -}); - function load_global(file) { file = path.resolve(path.dirname(module.filename), file); try { From 41be8632d3ec2ac0bae566aab97f40d7d7146142 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 22 Oct 2012 07:57:28 +0300 Subject: [PATCH 12/18] v2.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b5bf89c..c7f29906 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "JavaScript parser, mangler/compressor and beautifier toolkit", "homepage": "http://lisperator.net/uglifyjs", "main": "tools/node.js", - "version": "2.1.1", + "version": "2.1.2", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From 30faaf13edca3a4db6867e8faf0f0b68c6ac11e0 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Mon, 22 Oct 2012 11:49:58 +0300 Subject: [PATCH 13/18] more sequence optimizations (lift some sequences above binary/unary expressions so that we can avoid parens) --- lib/ast.js | 12 ++++++++++ lib/compress.js | 49 ++++++++++++++++++++++++++++++++++++++ lib/output.js | 2 +- test/compress/sequences.js | 40 +++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/lib/ast.js b/lib/ast.js index 44cbed12..32ec5380 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -581,6 +581,18 @@ var AST_Seq = DEFNODE("Seq", "car cdr", { } return list; }, + to_array: function() { + var p = this, a = []; + while (p) { + a.push(p.car); + if (p.cdr && !(p.cdr instanceof AST_Seq)) { + a.push(p.cdr); + break; + } + p = p.cdr; + } + return a; + }, add: function(node) { var p = this; while (p) { diff --git a/lib/compress.js b/lib/compress.js index 3e5b524d..22fb330e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1380,6 +1380,10 @@ merge(Compressor.prototype, { }); OPT(AST_Seq, function(self, compressor){ + if (!compressor.option("side_effects")) + return self; + if (!self.car.has_side_effects()) + return self.cdr; if (compressor.option("cascade")) { if (self.car instanceof AST_Assign && !self.car.left.has_side_effects() @@ -1395,7 +1399,26 @@ merge(Compressor.prototype, { return self; }); + AST_Unary.DEFMETHOD("lift_sequences", function(compressor){ + if (compressor.option("sequences")) { + if (this.expression instanceof AST_Seq) { + var seq = this.expression; + var x = seq.to_array(); + this.expression = x.pop(); + x.push(this); + seq = AST_Seq.from_array(x).transform(compressor); + return seq; + } + } + return this; + }); + + OPT(AST_UnaryPostfix, function(self, compressor){ + return self.lift_sequences(compressor); + }); + OPT(AST_UnaryPrefix, function(self, compressor){ + self = self.lift_sequences(compressor); var e = self.expression; if (compressor.option("booleans") && compressor.in_boolean_context()) { switch (self.operator) { @@ -1418,7 +1441,32 @@ merge(Compressor.prototype, { return self.evaluate(compressor)[0]; }); + AST_Binary.DEFMETHOD("lift_sequences", function(compressor){ + if (compressor.option("sequences")) { + if (this.left instanceof AST_Seq) { + var seq = this.left; + var x = seq.to_array(); + this.left = x.pop(); + x.push(this); + seq = AST_Seq.from_array(x).transform(compressor); + return seq; + } + if (this.right instanceof AST_Seq + && !(this.operator == "||" || this.operator == "&&") + && !this.left.has_side_effects()) { + var seq = this.right; + var x = seq.to_array(); + this.right = x.pop(); + x.push(this); + seq = AST_Seq.from_array(x).transform(compressor); + return seq; + } + } + return this; + }); + OPT(AST_Binary, function(self, compressor){ + self = self.lift_sequences(compressor); if (compressor.option("comparisons")) switch (self.operator) { case "===": case "!==": @@ -1557,6 +1605,7 @@ merge(Compressor.prototype, { var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; OPT(AST_Assign, function(self, compressor){ + self = self.lift_sequences(compressor); if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary diff --git a/lib/output.js b/lib/output.js index 8d2c4d39..4b515ecd 100644 --- a/lib/output.js +++ b/lib/output.js @@ -414,7 +414,7 @@ function OutputStream(options) { var p = output.parent(); return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4) || p instanceof AST_Unary // !(foo, bar, baz) - || p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 7 + || p instanceof AST_Binary // 1 + (2, 3) + 4 ==> 8 || p instanceof AST_VarDef // var a = (1, 2), b = a + a; ==> b == 4 || p instanceof AST_Dot // (1, {foo:2}).foo ==> 2 || p instanceof AST_Array // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ] diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 513bc84b..d48eced2 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -87,3 +87,43 @@ make_sequences_4: { with (x = 5, obj); } } + +lift_sequences_1: { + options = { sequences: true }; + input: { + foo = !(x(), y(), bar()); + } + expect: { + x(), y(), foo = !bar(); + } +} + +lift_sequences_2: { + options = { sequences: true, evaluate: true }; + input: { + q = 1 + (foo(), bar(), 5) + 7 * (5 / (3 - (a(), (QW=ER), c(), 2))) - (x(), y(), 5); + } + expect: { + foo(), bar(), a(), QW = ER, c(), x(), y(), q = 36 + } +} + +lift_sequences_3: { + options = { sequences: true, conditionals: true }; + input: { + x = (foo(), bar(), baz()) ? 10 : 20; + } + expect: { + foo(), bar(), x = baz() ? 10 : 20; + } +} + +lift_sequences_4: { + options = { side_effects: true }; + input: { + x = (foo, bar, baz); + } + expect: { + x = baz; + } +} From 70fd2b1f336535188696d9e7b198b8acd31aa558 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 24 Oct 2012 09:33:32 +0300 Subject: [PATCH 14/18] fix for `if (...) return; else return ...;` (it was assumed that the first `return` always contains a value) close #22 --- lib/compress.js | 4 ++-- test/compress/issue-22.js | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 test/compress/issue-22.js diff --git a/lib/compress.js b/lib/compress.js index 22fb330e..2ea91d0c 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1220,8 +1220,8 @@ merge(Compressor.prototype, { return make_node(self.body.CTOR, self, { value: make_node(AST_Conditional, self, { condition : self.condition, - consequent : self.body.value, - alternative : self.alternative.value || make_node(AST_Undefined, self).optimize(compressor) + consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor), + alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor) }) }).transform(compressor); } diff --git a/test/compress/issue-22.js b/test/compress/issue-22.js new file mode 100644 index 00000000..a8b7bc60 --- /dev/null +++ b/test/compress/issue-22.js @@ -0,0 +1,17 @@ +return_with_no_value_in_if_body: { + options = { conditionals: true }; + input: { + function foo(bar) { + if (bar) { + return; + } else { + return 1; + } + } + } + expect: { + function foo (bar) { + return bar ? void 0 : 1; + } + } +} From 7b87d2ef837efa6e8b03f7f778e87e5d264589c0 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 24 Oct 2012 09:41:40 +0300 Subject: [PATCH 15/18] v2.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7f29906..8b1c4eab 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "JavaScript parser, mangler/compressor and beautifier toolkit", "homepage": "http://lisperator.net/uglifyjs", "main": "tools/node.js", - "version": "2.1.2", + "version": "2.1.3", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon", From 202fb937995086561421719b2b26449757e80913 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 25 Oct 2012 10:58:48 +0300 Subject: [PATCH 16/18] test for fs.existsSync --- tools/node.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/node.js b/tools/node.js index de749bb3..4a891380 100644 --- a/tools/node.js +++ b/tools/node.js @@ -9,7 +9,9 @@ var fs = require("fs"); // but by the time you can set that the `path` module is already // loaded and `path.existsSync` is already changed to display that // warning, therefore here's the poor solution: -path.existsSync = fs.existsSync; +if (fs.existsSync) { + path.existsSync = fs.existsSync; +} var vm = require("vm"); var sys = require("util"); From cb3cafa14d2b65e8100b777d47a0cbade08ad7ca Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 25 Oct 2012 18:52:35 +0300 Subject: [PATCH 17/18] cripple scope to make IE happy :-( close #24 --- lib/scope.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/scope.js b/lib/scope.js index d87a54d8..910ce5ad 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -115,7 +115,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){ node.init_scope_vars(); } if (node instanceof AST_SymbolLambda) { - scope.def_function(node); + //scope.def_function(node); + // + // https://github.com/mishoo/UglifyJS2/issues/24 — MSIE + // leaks function expression names into the containing + // scope. Don't like this fix but seems we can't do any + // better. IE: please die. Please! + (node.scope = scope.parent_scope).def_function(node); + node.init.push(tw.parent()); } else if (node instanceof AST_SymbolDefun) { From 0852f5595edbceb1b8626c7214fcc6c17bf271e9 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Thu, 25 Oct 2012 18:52:49 +0300 Subject: [PATCH 18/18] v2.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b1c4eab..7418dd33 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "JavaScript parser, mangler/compressor and beautifier toolkit", "homepage": "http://lisperator.net/uglifyjs", "main": "tools/node.js", - "version": "2.1.3", + "version": "2.1.4", "engines": { "node" : ">=0.4.0" }, "maintainers": [{ "name": "Mihai Bazon",