Compare commits

...

90 Commits

Author SHA1 Message Date
Mihai Bazon
d13aa3954d v2.3.3 2013-05-14 11:33:28 +03:00
Mihai Bazon
f64539fb76 Compress code passed to new Function if it's a constant.
Only for `--unsafe`.

Close #203
2013-05-14 10:47:06 +03:00
Mihai Bazon
d56ebd7d7b Fix a["1_1"]
Close #204
2013-05-14 10:42:34 +03:00
Mihai Bazon
3edfe7d0ee Merge pull request #202 from nschonni/add-travis-ci
Add CI build for supported Node versions
2013-05-10 02:56:24 -07:00
Mihai Bazon
7f77edadb3 v2.3.2 2013-05-09 08:58:55 +03:00
Mihai Bazon
a9511dfbe5 Use the negation hack rather than parens for a toplevel function expression call
(only in !beautify mode)
2013-05-09 08:58:47 +03:00
Mihai Bazon
064e7aa1bb Fix is_assignable
(not likely to be noticed, it's only used in `strict` parse mode)
2013-05-09 08:44:24 +03:00
Nick Schonning
46814f88d9 Add Travis build badge to README 2013-05-08 23:48:12 -04:00
Nick Schonning
4a19802d0c Add CI build for supported Node versions 2013-05-08 23:42:06 -04:00
Trey Griffith
1e9f98aa51 add a test for zero-length string in is_identifier_string, which is used in property compression. Also added a test exercising the change. 2013-05-08 22:43:20 +03:00
Mihai Bazon
11e24d53a1 Fix property names
Close #199
2013-05-08 22:37:48 +03:00
Mihai Bazon
0f509f8336 v2.3.1 2013-05-08 16:45:36 +03:00
Mihai Bazon
a6ed2c84ac Better fix for equality of typeof ... against "undefined" 2013-05-08 16:22:48 +03:00
Justin Lau
a1958aad56 Fixed typeof undefined optimization and updated related test case to
accomodates the sort behaviour changes made in commit
mishoo/UglifyJS2@aebafad41e.
Signed-off-by: Justin Lau <justin@tclau.com>
2013-05-08 16:22:48 +03:00
Justin Lau
672699613e Added test cases for #104.
Signed-off-by: Justin Lau <justin@tclau.com>
2013-05-08 16:22:48 +03:00
Mihai Bazon
645d5bdbc5 Merge pull request #195 from kjbekkelund/typo
Fix typo in bin and readme
2013-05-08 05:51:52 -07:00
Justin Lau
9af2bbffde Fixed dot properties not optimizing unicode identifiers. Signed-off-by: Justin Lau <justin@tclau.com> 2013-05-07 14:20:19 +03:00
Justin Lau
fcd544cc10 Added test scenario with unicode in properties name.
Signed-off-by: Justin Lau <justin@tclau.com>
2013-05-06 01:26:33 +08:00
Justin Lau
1e3bc0caa0 Fixed dot property issue with invlid identifier names.
Signed-off-by: Justin Lau <justin@tclau.com>
2013-05-05 22:27:43 +08:00
Justin Lau
8227e8795b Added scenario in test case where properties shouldn't be accessed with
dotted syntax even with screw_ie8 option.
Signed-off-by: Justin Lau <justin@tclau.com>
2013-05-05 22:08:13 +08:00
Kim Joar Bekkelund
790b3bcdc6 Fix typo in bin and readme 2013-05-02 11:15:33 +02:00
Mihai Bazon
d6e6458f68 Merge pull request #194 from ulikoehler/master
Add README syntax highlighting
2013-05-01 07:04:01 -07:00
Uli Köhler
a54b6703c0 Add README syntax highlighting 2013-05-01 15:56:20 +02:00
Mihai Bazon
8e6266136d Take two. v2.3.0 2013-05-01 13:15:34 +03:00
Mihai Bazon
5c22a1bdf5 v2.3 2013-05-01 13:14:07 +03:00
Mihai Bazon
9794ebf88c Workaround for missing prefix in UnaryExpression generated by Esprima
See #193
2013-04-29 15:03:52 +03:00
Mihai Bazon
68394eed93 Make compress/mangle disabled by default, as before 5af144522a 2013-04-21 11:35:50 +03:00
Mihai Bazon
753b4b6cc8 Merge pull request #191 from michaelficarra/use-es5-member-access-with-screw-ie
use dotted member access when --screw-ie8 option given
2013-04-21 01:30:02 -07:00
Mihai Bazon
a9c1b9f138 Merge pull request #190 from michaelficarra/patch-1
unbalanced parentheses in readme
2013-04-21 01:28:45 -07:00
Michael Ficarra
5af144522a fixes #189: use dotted member access when --screw-ie8 option given 2013-04-20 15:11:05 -05:00
Michael Ficarra
483e0cadfb unbalanced parentheses in readme 2013-04-20 14:05:52 -05:00
Roman Bataev
4b818056cf Fix typeof evaluation for regex and function 2013-04-03 22:34:38 -04:00
Roman Bataev
b956e5f1d9 Add tests for typeof evaluation 2013-04-03 22:34:19 -04:00
Vladimir Zhuravlev
37d7cb8565 Quote objects with numeric keys 2013-03-31 19:52:28 +03:00
Mihai Bazon
2b8e206fec fix package.json 2013-03-31 13:38:02 +03:00
Mihai Bazon
a869b854fa Don't use \xYY for identifiers
Fix #173
2013-03-31 13:36:22 +03:00
Andreas Lind Petersen
81f5efe39a Output, to_ascii: Escape non-ascii chars with \xnn instead of \unnnn whenever possible. 2013-03-31 13:36:22 +03:00
Andreas Lind Petersen
69dde0462b uglifyjs binary: Make read_whole_file async and don't attempt to read STDIN synchronously. 2013-03-31 13:36:22 +03:00
Mihai Bazon
7628bcac01 Merge pull request #163 from mzgol/screw-oldie
renamed --screw-ie to --screw-oldie, documented it in README.md, indicat...
2013-03-25 09:05:44 -07:00
Michał Gołębiowski
75f0bbe6e8 renamed --screw-ie to --screw-ie8, documented it in README.md, indicated it doesn't break IE9+ 2013-03-25 17:03:21 +01:00
Jake Harding
478bf4dbdd Add support for enclose option. Closes #139. 2013-03-24 11:11:23 +02:00
Mihai Bazon
e0f67baf2d Don't print the warning on parse error, just throw a JS_Parse_Error.
Fix #159
2013-03-24 00:57:35 +02:00
Mihai Bazon
b14d3df3d2 Keep legit code working even when --screw-ie is not passed.
Previously:

    Without `--screw-ie`, UglifyJS would always leak names of function
    expressions into the containing scope, as if they were function
    declarations.  That was to emulate IE<9 behavior.  Code relying on this
    IE bug would continue to work properly after mangling, although it would
    only work in IE (since other engines don't share the bug).  Sometimes
    this broke legitimage code (see #153 and #155).

    With `--screw-ie` the names would not be leaked into the current scope,
    working properly in legit cases; but still it broke legit code when
    running in IE<9 (see #24).

Currently:

    Regardless of the `--screw-ie` setting, the names will not be leaked.
    Code relying on the IE bug will not work properly after mangling.
    <evil laughter here>

    Without `--screw-ie`: a hack has been added to the mangler to avoid
    using the same name for a function expression and some other variable in
    the same scope.  This keeps legit code working, at the (negligible,
    indeed) cost of one more identifier.

    With `--screw-ie` you allow the mangler to name function expressions
    with the same identifier as another variable in scope.  After mangling
    code might break in IE<9.

Oh man, the commit message is longer than the patch.

Fix #153, #155
2013-03-22 18:04:46 +02:00
Mihai Bazon
24e58ee70c Merge pull request #125 from devongovett/master
Allow inSourceMap option to be a generated JSON source map
2013-03-13 01:36:55 -07:00
Mihai Bazon
9b1a40dfc3 Support mangling toplevel names
Close #127
2013-03-13 09:44:06 +02:00
Mihai Bazon
e4b078cff7 Disable unsafe by default
Close #147
2013-03-11 00:04:31 +02:00
Mihai Bazon
3bd7ca9961 Merge pull request #146 from mbostock/read-all-stdin
Read the entire STDIN.
2013-03-05 22:17:09 -08:00
Mike Bostock
f83aca65b7 Read the entire STDIN.
The problem with reading synchronously from /dev/stdin is that you can get a
spurious EOF when the input buffer is empty, even if more content is coming. Now
STDIN is read from a loop, and only stops polling when all input has been read.
This fixes #70 #85 and other errors related to parsing large files on STDIN.
2013-03-05 20:35:49 -08:00
Mihai Bazon
aebafad41e Fix reordering comparisons
Close #143
2013-03-04 10:06:01 +02:00
Mihai Bazon
26746ce316 Add --screw-ie option
For now the implication is that UglifyJS will not leak a function
expression's name in the surrounding scope (IE < 9 does that).

(ref. mishoo/UglifyJS#485)
2013-03-02 14:28:34 +02:00
Mihai Bazon
dac6efb43d Drop last default: if it's the last branch and empty
Close #141
2013-03-01 13:12:03 +02:00
Mihai Bazon
8880f4824c Compress boolean constants after evaluation
Close #137
2013-03-01 10:26:06 +02:00
Mihai Bazon
cb0c576bdd Add license
Close #131
2013-02-22 13:58:16 +02:00
Mihai Bazon
3a591c43fe Fix compressing do {...} while (false)
It's not safe to transform it to {...} because the body might contain
`break`.  The solution could be more elaborate (detect if body contains
`break`) but I don't think it's worth the trouble.

Close #129
2013-02-19 18:12:19 +02:00
Mihai Bazon
db66eca958 v2.2.5 2013-02-14 12:51:13 +02:00
Devon Govett
f2767452e6 Allow inSourceMap to be a generated JSON source map instead of just a file name 2013-02-10 10:06:13 -08:00
Mihai Bazon
916faf0a48 Force space after literal regexp when used in "instanceof" or "in"
Close #118
2013-02-06 11:57:59 +02:00
Mihai Bazon
f36e4e9a78 Give up evaluating (unary-prefix '-' 0)
Close #117

------

    JS, WHY YOU SUCK SO BADLY? ;-(
2013-02-06 11:51:09 +02:00
Mihai Bazon
fdf8b5eb71 Fix parens for NaN
Close #116
2013-02-06 11:38:29 +02:00
Mihai Bazon
de7ec7f1b7 Fix parens for negative numbers
Close #115
2013-02-06 11:36:04 +02:00
Mihai Bazon
3c8a0bdff4 Fix parens for AST_New
Close #114
2013-02-06 11:28:49 +02:00
Mihai Bazon
9e8ba27dcd Fix handling of constants
Close #113
2013-02-06 11:15:31 +02:00
Mihai Bazon
719a8fd102 Ugly hack to print comments before return/throw statements
Close #112
2013-02-05 19:11:39 +02:00
Mihai Bazon
3a22e917de Merge pull request #111 from mattrobenolt/safer-sourcemap
Wraps sourceMappingURL in a multiline comment. Fixes #108
2013-02-03 23:44:31 -08:00
Matt Robenolt
a9af2c9e62 Wraps sourceMappingURL in a multiline comment. Fixes #108 2013-02-03 16:01:01 -08:00
Mihai Bazon
31e99cebe7 v2.2.4 2013-02-01 13:32:15 +02:00
Mihai Bazon
a5b209470c Fix end token for Assign nodes 2013-02-01 13:32:15 +02:00
Mihai Bazon
e9a571b2a1 Merge pull request #94 from paulmillr/patch-1
Add better fromstring docs.
2013-01-31 23:50:59 -08:00
Mihai Bazon
8bf83f42ea Merge pull request #106 from gibson042/105
Fix #105: property comparison to undefined is not always safe
2013-01-24 05:51:33 -08:00
Richard Gibson
522566ea80 Fix #105: property comparison to undefined is not always safe 2013-01-23 23:52:04 -05:00
Mihai Bazon
297af47c89 Add --source-map-url option
Fix #100
Fix #47
2013-01-20 12:32:07 +02:00
Mihai Bazon
faa354f5ca [AST_Hole] the print function can be a no-op. 2013-01-17 11:36:10 +02:00
David Glasser
1529ab965a Fix output for arrays containing undefined values.
1b6bcca7 was a first attempt at this. That commit made Uglify stop replacing
holes with undefined, but instead it started replacing undefined with
holes. This is slightly problematic, because there is a difference between a
hole and an undefined value. More problematically, it changed [1,undefined] to
[1,] which generally doesn't even parse as a hole (just as a trailing comma), so
it didn't even preserve the length of the array!

Instead, parse holes as their own special AST node which prints invisibly.
2013-01-17 11:36:10 +02:00
Mihai Bazon
605f330e69 Merge pull request #98 from ForbesLindesay/patch-1
Update installation instructions
2013-01-17 01:08:59 -08:00
Mihai Bazon
f0909bdc8f Handle String() with no arguments.
Fix #91
2013-01-17 11:01:38 +02:00
Forbes Lindesay
c13e7e621d Update installation instructions re #4 2013-01-17 00:13:42 +00:00
Paul Miller
ad071f8017 Add better fromstring docs. 2013-01-13 18:45:43 +02:00
Mihai Bazon
c058d8b9cd Merge pull request #90 from jakearchibald/patch-1
Compressor options use underscores rather than hyphens
2013-01-08 14:21:25 -08:00
Jake Archibald
1d8871a092 Compressor options use underscores rather than hyphens 2013-01-08 12:33:58 -08:00
Mihai Bazon
16953c2064 v2.2.3 2013-01-04 22:50:53 +02:00
Mihai Bazon
6b14f7c224 Fix handling of labels in nested scopes 2013-01-04 14:17:33 +02:00
Mihai Bazon
130c623be7 Support output, mangle and compress options to UglifyJS.minify.
Close #57
Close #86
Close #33
2013-01-04 11:25:13 +02:00
Mihai Bazon
47c9895d59 Merge pull request #87 from BenoitZugmeyer/master
Add a --version option
2013-01-03 02:28:35 -08:00
Benoît Zugmeyer
ba403331c5 Set --version as a boolean #87 2013-01-03 11:22:37 +01:00
Benoît Zugmeyer
e82e89d1b0 --version option 2013-01-03 11:07:53 +01:00
Mihai Bazon
83a4ebfedc Implement -m sort=true
close #83
2013-01-02 12:39:00 +02:00
Mihai Bazon
9916d0e547 Accept string or number as name of an accessor.
[not sure I'm happy about this fix]

Reference mishoo/UglifyJS#478
2012-12-22 01:24:04 +02:00
Mihai Bazon
31c4a37e37 Optimize new Array(1, 2, 3) → [1, 2, 3]
Close #74
2012-12-21 21:04:35 +02:00
Mihai Bazon
08219f0cee Fix output when semicolons is off.
(need to force a semicolon for the empty body of an `if`)

Close #72
2012-12-21 11:57:08 +02:00
Mihai Bazon
c4993e1e5c Small cleanup 2012-12-12 11:51:55 +02:00
19 changed files with 785 additions and 309 deletions

7
.travis.yml Normal file
View File

@@ -0,0 +1,7 @@
language: node_js
node_js:
- "0.4"
- "0.6"
- "0.8"
- "0.10"
- "0.11"

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
UglifyJS is released under the BSD license:
Copyright 2012-2013 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

260
README.md
View File

@@ -1,5 +1,6 @@
UglifyJS 2
==========
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.png)](https://travis-ci.org/mishoo/UglifyJS2)
UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit.
@@ -12,7 +13,14 @@ Chrome and probably Safari).
Install
-------
From NPM:
First make sure you have installed the latest version of [node.js](http://nodejs.org/)
(You may need to restart your computer after this step).
From NPM for use as a command line app:
npm install uglify-js -g
From NPM for programmatic use:
npm install uglify-js
@@ -42,8 +50,14 @@ The available options are:
[string]
--source-map-root The path to the original source to be included in the
source map. [string]
--source-map-url The path to the source map to be added in //@
sourceMappingURL. Defaults to the value passed with
--source-map. [string]
--in-source-map Input source map, useful if you're compressing JS that was
generated from some other original code.
--screw-ie8 Pass this flag if you don't care about full compliance with
Internet Explorer 6-8 quirks (by default UglifyJS will try
to be IE-proof).
-p, --prefix 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.
@@ -67,7 +81,7 @@ The available options are:
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).
--spidermonkey Assume input files are SpiderMonkey AST format (as JSON).
[boolean]
--self Build itself (UglifyJS2) as a library (implies
--wrap=UglifyJS --export-all) [boolean]
@@ -78,7 +92,9 @@ The available options are:
[string]
--export-all Only used when --wrap, this tells UglifyJS to add code to
automatically export all globals. [boolean]
--lint Display some scope warnings [boolean]
-v, --verbose Verbose [boolean]
-V, --version Print version number and exit. [boolean]
Specify `--output` (`-o`) to declare the output file. Otherwise the output
goes to STDOUT.
@@ -129,12 +145,19 @@ input files from the command line.
## 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
to assign shorter names to most frequently used variables. This saves a few
hundred bytes on jQuery before gzip, but the output is _bigger_ after gzip
(and seems to happen for other libraries I tried it on) therefore it's not
enabled by default.
To enable the mangler you need to pass `--mangle` (`-m`). The following
(comma-separated) options are supported:
- `sort` — to assign shorter names to most frequently used variables. This
saves a few hundred bytes on jQuery before gzip, but the output is
_bigger_ after gzip (and seems to happen for other libraries I tried it
on) therefore it's not enabled by default.
- `toplevel` — mangle names declared in the toplevel scope (disabled by
default).
- `eval` — mangle names visible in scopes where `eval` or `when` are used
(disabled by default).
When mangling is enabled but you want to prevent certain names from being
mangled, you can declare those names with `--reserved` (`-r`) — pass a
@@ -151,15 +174,12 @@ you can pass a comma-separated list of options. Options are in the form
`foo=bar`, or just `foo` (the latter implies a boolean option that you want
to set `true`; it's effectively a shortcut for `foo=true`).
The defaults should be tuned for maximum compression on most code. Here are
the available options (all are `true` by default, except `hoist_vars`):
- `sequences` -- join consecutive simple statements using the comma operator
- `properties` -- rewrite property access using the dot notation, for
example `foo["bar"] → foo.bar`
- `dead-code` -- remove unreachable code
- `drop-debugger` -- remove `debugger;` statements
- `unsafe` -- apply "unsafe" transformations (discussion below)
- `dead_code` -- remove unreachable code
- `drop_debugger` -- remove `debugger;` statements
- `unsafe` (default: false) -- apply "unsafe" transformations (discussion below)
- `conditionals` -- apply optimizations for `if`-s and conditional
expressions
- `comparisons` -- apply certain optimizations to binary nodes, for example:
@@ -171,26 +191,43 @@ the available options (all are `true` by default, except `hoist_vars`):
- `loops` -- optimizations for `do`, `while` and `for` loops when we can
statically determine the condition
- `unused` -- drop unreferenced functions and variables
- `hoist-funs` -- hoist function declarations
- `hoist-vars` -- hoist `var` declarations (this is `false` by default
because it seems to increase the size of the output in general)
- `if-return` -- optimizations for if/return and if/continue
- `join-vars` -- join consecutive `var` statements
- `hoist_funs` -- hoist function declarations
- `hoist_vars` (default: false) -- hoist `var` declarations (this is `false`
by default because it seems to increase the size of the output in general)
- `if_return` -- optimizations for if/return and if/continue
- `join_vars` -- join consecutive `var` statements
- `cascade` -- small optimization for sequences, transform `x, x` into `x`
and `x = something(), x` into `x = something()`
- `warnings` -- display warnings when dropping unreachable code or unused
declarations etc.
### The `unsafe` option
It enables some transformations that *might* break code logic in certain
contrived cases, but should be fine for most code. You might want to try it
on your own code, it should reduce the minified size. Here's what happens
when this flag is on:
- `new Array(1, 2, 3)` or `Array(1, 2, 3)` → `[1, 2, 3 ]`
- `new Object()` → `{}`
- `String(exp)` or `exp.toString()` → `"" + exp`
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new`
- `typeof foo == "undefined"` → `foo === void 0`
- `void 0` → `"undefined"` (if there is a variable named "undefined" in
scope; we do it because the variable name will be mangled, typically
reduced to a single character).
### Conditional compilation
You can use the `--define` (`-d`) switch in order to declare global
variables that UglifyJS will assume to be constants (unless defined in
scope). For example if you pass `--define DEBUG=false` then, coupled with
dead code removal UglifyJS will discard the following from the output:
if (DEBUG) {
console.log("debug stuff");
}
```javascript
if (DEBUG) {
console.log("debug stuff");
}
```
UglifyJS will warn about the condition being always false and about dropping
unreachable code; for now there is no option to turn off only this specific
@@ -199,10 +236,11 @@ warning, you can pass `warnings=false` to turn off *all* warnings.
Another way of doing that is to declare your globals as constants in a
separate file and include it into the build. For example you can have a
`build/defines.js` file with the following:
const DEBUG = false;
const PRODUCTION = true;
// etc.
```javascript
const DEBUG = false;
const PRODUCTION = true;
// etc.
```
and build your code like this:
@@ -213,6 +251,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.
<a name="codegen-options"></a>
## Beautifier options
The code generator tries to output shortest code possible by default. In
@@ -260,14 +299,15 @@ keep only comments that match this regexp. For example `--comments
Note, however, that there might be situations where comments are lost. For
example:
function f() {
/** @preserve Foo Bar */
function g() {
// this function is never called
}
return something();
}
```javascript
function f() {
/** @preserve Foo Bar */
function g() {
// this function is never called
}
return something();
}
```
Even though it has "@preserve", the comment will be lost because the inner
function `g` (which is the AST node to which the comment is attached to) is
@@ -309,8 +349,9 @@ API Reference
Assuming installation via NPM, you can load UglifyJS in your application
like this:
var UglifyJS = require("uglify-js");
```javascript
var UglifyJS = require("uglify-js");
```
It exports a lot of names, but I'll discuss here the basics that are needed
for parsing, mangling and compressing a piece of code. The sequence is (1)
@@ -321,44 +362,49 @@ parse, (2) compress, (3) mangle, (4) generate output code.
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
```javascript
var result = UglifyJS.minify("/path/to/file.js");
console.log(result.code); // minified output
// if you need to pass code instead of file name
var result = UglifyJS.minify("var b = function () {};", {fromString: true});
```
You can also compress multiple files:
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ]);
console.log(result.code);
```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ]);
console.log(result.code);
```
To generate a source map:
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], {
outSourceMap: "out.js.map"
});
console.log(result.code); // minified output
console.log(result.map);
```javascript
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], {
outSourceMap: "out.js.map"
});
console.log(result.code); // minified output
console.log(result.map);
```
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"
});
```javascript
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:
var result = UglifyJS.minify("compiled.js", {
inSourceMap: "compiled.js.map",
outSourceMap: "minified.js.map"
});
// same as before, it returns `code` and `map`
```javascript
var result = UglifyJS.minify("compiled.js", {
inSourceMap: "compiled.js.map",
outSourceMap: "minified.js.map"
});
// same as before, it returns `code` and `map`
```
The `inSourceMap` is only used if you also request `outSourceMap` (it makes
no sense otherwise).
@@ -366,9 +412,19 @@ 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.
- `mangle` — pass `false` to skip mangling names.
- `output` (default `null`) — pass an object if you wish to specify
additional [output options][codegen]. The defaults are optimized
for best compression.
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
Pass an object to specify custom [compressor options][compressor].
We could add more options to `UglifyJS.minify` — if you need additional
functionality please suggest!
@@ -378,8 +434,9 @@ Following there's more detailed API info, in case the `minify` function is
too simple for your needs.
#### The parser
var toplevel_ast = UglifyJS.parse(code, options);
```javascript
var toplevel_ast = UglifyJS.parse(code, options);
```
`options` is optional and if present it must be an object. The following
properties are available:
@@ -393,15 +450,16 @@ properties are available:
The last two options are useful when you'd like to minify multiple files and
get a single file as the output and a proper source map. Our CLI tool does
something like this:
var toplevel = null;
files.forEach(function(file){
var code = fs.readFileSync(file);
toplevel = UglifyJS.parse(code, {
filename: file,
toplevel: toplevel
});
});
```javascript
var toplevel = null;
files.forEach(function(file){
var code = fs.readFileSync(file);
toplevel = UglifyJS.parse(code, {
filename: file,
toplevel: toplevel
});
});
```
After this, we have in `toplevel` a big AST containing all our files, with
each token having proper information about where it came from.
@@ -415,15 +473,17 @@ referenced, if it is a global or not, if a function is using `eval` or the
`with` statement etc. I will discuss this some place else, for now what's
important to know is that you need to call the following before doing
anything with the tree:
toplevel.figure_out_scope()
```javascript
toplevel.figure_out_scope()
```
#### Compression
Like this:
var compressor = UglifyJS.Compressor(options);
var compressed_ast = toplevel.transform(compressor);
```javascript
var compressor = UglifyJS.Compressor(options);
var compressed_ast = toplevel.transform(compressor);
```
The `options` can be missing. Available options are discussed above in
“Compressor options”. Defaults should lead to best compression in most
@@ -439,23 +499,26 @@ the compressor might drop unused variables / unreachable code and this might
change the number of identifiers or their position). Optionally, you can
call a trick that helps after Gzip (counting character frequency in
non-mangleable words). Example:
compressed_ast.figure_out_scope();
compressed_ast.compute_char_frequency();
compressed_ast.mangle_names();
```javascript
compressed_ast.figure_out_scope();
compressed_ast.compute_char_frequency();
compressed_ast.mangle_names();
```
#### Generating output
AST nodes have a `print` method that takes an output stream. Essentially,
to generate code you do this:
var stream = UglifyJS.OutputStream(options);
compressed_ast.print(stream);
var code = stream.toString(); // this is your minified code
```javascript
var stream = UglifyJS.OutputStream(options);
compressed_ast.print(stream);
var code = stream.toString(); // this is your minified code
```
or, for a shortcut you can do:
var code = compressed_ast.print_to_string(options);
```javascript
var code = compressed_ast.print_to_string(options);
```
As usual, `options` is optional. The output stream accepts a lot of otions,
most of them documented above in section “Beautifier options”. The two
@@ -493,16 +556,17 @@ to be a `SourceMap` object (which is a thin wrapper on top of the
[source-map][source-map] library).
Example:
```javascript
var source_map = UglifyJS.SourceMap(source_map_options);
var stream = UglifyJS.OutputStream({
...
source_map: source_map
});
compressed_ast.print(stream);
var source_map = UglifyJS.SourceMap(source_map_options);
var stream = UglifyJS.OutputStream({
...
source_map: source_map
});
compressed_ast.print(stream);
var code = stream.toString();
var map = source_map.toString(); // json output for your source map
var code = stream.toString();
var map = source_map.toString(); // json output for your source map
```
The `source_map_options` (optional) can contain the following properties:
@@ -516,3 +580,5 @@ The `source_map_options` (optional) can contain the following properties:
[acorn]: https://github.com/marijnh/acorn
[source-map]: https://github.com/mozilla/source-map
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[codegen]: http://lisperator.net/uglifyjs/codegen
[compressor]: http://lisperator.net/uglifyjs/compress

View File

@@ -7,6 +7,7 @@ var UglifyJS = require("../tools/node");
var sys = require("util");
var optimist = require("optimist");
var fs = require("fs");
var async = require("async");
var acorn;
var ARGS = optimist
.usage("$0 input1.js [input2.js ...] [options]\n\
@@ -19,7 +20,9 @@ mangling you need to use `-c` and `-m`.\
")
.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("source-map-url", "The path to the source map to be added in //@ sourceMappingURL. Defaults to the value passed with --source-map.")
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
.describe("screw-ie8", "Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks (by default UglifyJS will try to be IE-proof).")
.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).")
@@ -30,6 +33,7 @@ For example -p 3 will drop 3 directories from file names and ensure they are rel
Pass options like -c hoist_vars=false,if_return=false. \
Use -c with no argument to use the default compression options.")
.describe("d", "Global definitions")
.describe("e", "Embed everything in a big function, with a configurable parameter/argument list.")
.describe("comments", "Preserve copyright comments in the output. \
By default this works like Google Closure, keeping JSDoc-style comments that contain \"@license\" or \"@preserve\". \
@@ -42,13 +46,14 @@ 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("spidermonkey", "Assume input files are SpiderMonkey AST format (as JSON).")
.describe("self", "Build itself (UglifyJS2) as a library (implies --wrap=UglifyJS --export-all)")
.describe("wrap", "Embed everything in a big function, making the “exports” and “global” variables available. \
You need to pass an argument to this option to specify the name that your module will take when included in, say, a browser.")
.describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.")
.describe("lint", "Display some scope warnings")
.describe("v", "Verbose")
.describe("V", "Print version number and exit.")
.alias("p", "prefix")
.alias("o", "output")
@@ -58,15 +63,20 @@ You need to pass an argument to this option to specify the name that your module
.alias("c", "compress")
.alias("d", "define")
.alias("r", "reserved")
.alias("V", "version")
.alias("e", "enclose")
.string("source-map")
.string("source-map-root")
.string("source-map-url")
.string("b")
.string("m")
.string("c")
.string("d")
.string("e")
.string("comments")
.string("wrap")
.boolean("screw-ie8")
.boolean("export-all")
.boolean("self")
.boolean("v")
@@ -74,6 +84,7 @@ You need to pass an argument to this option to specify the name that your module
.boolean("acorn")
.boolean("spidermonkey")
.boolean("lint")
.boolean("V")
.wrap(80)
@@ -82,6 +93,12 @@ You need to pass an argument to this option to specify the name that your module
normalize(ARGS);
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
sys.puts(json.name + ' ' + json.version);
process.exit(0);
}
if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast();
sys.puts(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
@@ -101,12 +118,17 @@ var COMPRESS = getOptions("c", true);
var MANGLE = getOptions("m", true);
var BEAUTIFY = getOptions("b", true);
if (COMPRESS && ARGS.d) {
COMPRESS.global_defs = getOptions("d");
if (ARGS.d) {
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
}
if (MANGLE && ARGS.r) {
MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
if (ARGS.screw_ie8) {
if (COMPRESS) COMPRESS.screw_ie8 = true;
if (MANGLE) MANGLE.screw_ie8 = true;
}
if (ARGS.r) {
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
}
var OUTPUT_OPTIONS = {
@@ -195,100 +217,116 @@ try {
}
}
files.forEach(function(file) {
var code = read_whole_file(file);
if (ARGS.p != null) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
async.eachLimit(files, 1, function (file, cb) {
read_whole_file(file, function (err, code) {
if (err) {
sys.error("ERROR: can't read file: " + filename);
process.exit(1);
}
if (ARGS.p != null) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
}
time_it("parse", function(){
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
});
};
});
cb();
});
}, function () {
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
});
if (ARGS.wrap) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
time_it("parse", function(){
if (ARGS.spidermonkey) {
var program = JSON.parse(code);
if (!TOPLEVEL) TOPLEVEL = program;
else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
if (ARGS.enclose) {
var arg_parameter_list = ARGS.enclose;
if (!(arg_parameter_list instanceof Array)) {
arg_parameter_list = [arg_parameter_list];
}
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
});
};
TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
}
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
});
}
if (COMPRESS) {
time_it("squeeze", function(){
TOPLEVEL = TOPLEVEL.transform(compressor);
});
}
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
if (MANGLE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
if (MANGLE) time_it("mangle", function(){
TOPLEVEL.mangle_names(MANGLE);
});
});
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
});
if (ARGS.wrap) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope();
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
time_it("generate", function(){
TOPLEVEL.print(output);
});
}
if (COMPRESS) {
time_it("squeeze", function(){
TOPLEVEL = TOPLEVEL.transform(compressor);
});
}
output = output.get();
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope();
if (MANGLE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
output += "\n/*\n//@ sourceMappingURL=" + (ARGS.source_map_url || ARGS.source_map) + "\n*/";
}
if (MANGLE) time_it("mangle", function(){
TOPLEVEL.mangle_names(MANGLE);
});
time_it("generate", function(){
TOPLEVEL.print(output);
});
if (OUTPUT_FILE) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
sys.print(output);
sys.error("\n");
}
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)
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 ]----- */
@@ -333,17 +371,18 @@ function getOptions(x, constants) {
return ret;
}
function read_whole_file(filename) {
function read_whole_file(filename, cb) {
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);
var chunks = [];
process.stdin.setEncoding('utf-8');
process.stdin.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function () {
cb(null, chunks.join(""));
});
process.openStdin();
} else {
fs.readFile(filename, "utf-8", cb);
}
}

View File

@@ -285,6 +285,27 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
$propdoc: {
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
},
wrap_enclose: function(arg_parameter_pairs) {
var self = this;
var args = [];
var parameters = [];
arg_parameter_pairs.forEach(function(pair) {
var split = pair.split(":");
args.push(split[0]);
parameters.push(split[1]);
});
var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")";
wrapped_tl = parse(wrapped_tl);
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){
if (node instanceof AST_Directive && node.value == "$ORIG") {
return MAP.splice(self.body);
}
}));
return wrapped_tl;
},
wrap_commonjs: function(name, export_all) {
var self = this;
var to_export = [];
@@ -863,6 +884,11 @@ var AST_Undefined = DEFNODE("Undefined", null, {
value: (function(){}())
}, AST_Atom);
var AST_Hole = DEFNODE("Hole", null, {
$documentation: "A hole in an array",
value: (function(){}())
}, AST_Atom);
var AST_Infinity = DEFNODE("Infinity", null, {
$documentation: "The `Infinity` value",
value: 1/0

View File

@@ -52,7 +52,7 @@ function Compressor(options, false_by_default) {
properties : !false_by_default,
dead_code : !false_by_default,
drop_debugger : !false_by_default,
unsafe : !false_by_default,
unsafe : false,
unsafe_comps : false,
conditionals : !false_by_default,
comparisons : !false_by_default,
@@ -66,6 +66,7 @@ function Compressor(options, false_by_default) {
join_vars : !false_by_default,
cascade : !false_by_default,
side_effects : !false_by_default,
screw_ie8 : false,
warnings : true,
global_defs : {}
@@ -156,7 +157,7 @@ merge(Compressor.prototype, {
value: val
}).optimize(compressor);
case "boolean":
return make_node(val ? AST_True : AST_False, orig);
return make_node(val ? AST_True : AST_False, orig).optimize(compressor);
case "undefined":
return make_node(AST_Undefined, orig).optimize(compressor);
default:
@@ -625,10 +626,24 @@ merge(Compressor.prototype, {
var e = this.expression;
switch (this.operator) {
case "!": return !ev(e);
case "typeof": return typeof ev(e);
case "typeof":
// Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
if (e instanceof AST_Function) return typeof function(){};
e = ev(e);
// typeof <RegExp> returns "object" or "function" on different platforms
// so cannot evaluate reliably
if (e instanceof RegExp) throw def;
return typeof e;
case "void": return void ev(e);
case "~": return ~ev(e);
case "-": return -ev(e);
case "-":
e = ev(e);
if (e === 0) throw def;
return -e;
case "+": return +ev(e);
}
throw def;
@@ -669,12 +684,7 @@ merge(Compressor.prototype, {
});
def(AST_SymbolRef, function(){
var d = this.definition();
if (d && d.constant) {
var orig = d.orig[0];
if (orig) orig = orig.init[0];
orig = orig && orig.value;
if (orig) return ev(orig);
}
if (d && d.constant && d.init) return ev(d.init);
throw def;
});
})(function(node, func){
@@ -1199,8 +1209,6 @@ merge(Compressor.prototype, {
extract_declarations_from_unreachable_code(compressor, self.body, a);
return make_node(AST_BlockStatement, self, { body: a });
}
} else {
return self.body;
}
}
return self;
@@ -1418,11 +1426,18 @@ merge(Compressor.prototype, {
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
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
last_branch.body.pop();
for(;;) {
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 && loop_body(compressor.loopcontrol_target(stat.label)) === self)
last_branch.body.pop();
if (last_branch instanceof AST_Default && last_branch.body.length == 0) {
self.body.pop();
continue;
}
}
break;
}
var exp = self.expression.evaluate(compressor);
out: if (exp.length == 2) try {
@@ -1566,11 +1581,53 @@ merge(Compressor.prototype, {
}
break;
case "String":
if (self.args.length == 0) return make_node(AST_String, self, {
value: ""
});
return make_node(AST_Binary, self, {
left: self.args[0],
operator: "+",
right: make_node(AST_String, self, { value: "" })
});
case "Function":
if (self.args[self.args.length - 1] instanceof AST_String) {
// quite a corner-case, but we can handle it:
// https://github.com/mishoo/UglifyJS2/issues/203
// if the code argument is a constant, then we can minify it.
try {
var code = "(function(" + self.args.slice(0, -1).map(function(arg){
return arg.value;
}).join(",") + "){" + self.args[self.args.length - 1].value + "})()";
var ast = parse(code);
ast.figure_out_scope();
var comp = new Compressor(compressor.options);
ast = ast.transform(comp);
ast.figure_out_scope();
ast.mangle_names();
var fun = ast.body[0].body.expression;
var args = fun.argnames.map(function(arg, i){
return make_node(AST_String, self.args[i], {
value: arg.print_to_string()
});
});
var code = OutputStream();
AST_BlockStatement.prototype._codegen.call(fun, fun, code);
code = code.toString().replace(/^\{|\}$/g, "");
args.push(make_node(AST_String, self.args[self.args.length - 1], {
value: code
}));
self.args = args;
return self;
} catch(ex) {
if (ex instanceof JS_Parse_Error) {
compressor.warn("Error parsing code passed to new Function [{file}:{line},{col}]", self.args[self.args.length - 1].start);
compressor.warn(ex.toString());
} else {
console.log(ex);
}
}
}
break;
}
}
else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) {
@@ -1601,7 +1658,7 @@ merge(Compressor.prototype, {
case "Function":
case "Error":
case "Array":
return make_node(AST_Call, self, self);
return make_node(AST_Call, self, self).transform(compressor);
}
}
}
@@ -1708,8 +1765,8 @@ merge(Compressor.prototype, {
var commutativeOperators = makePredicate("== === != !== * & | ^");
OPT(AST_Binary, function(self, compressor){
function reverse(op) {
if (!(self.left.has_side_effects() && self.right.has_side_effects())) {
function reverse(op, force) {
if (force || !(self.left.has_side_effects() || self.right.has_side_effects())) {
if (op) self.operator = op;
var tmp = self.left;
self.left = self.right;
@@ -1719,7 +1776,10 @@ merge(Compressor.prototype, {
if (commutativeOperators(self.operator)) {
if (self.right instanceof AST_Constant
&& !(self.left instanceof AST_Constant)) {
reverse();
// if right is a constant, whatever side effects the
// left side might have could not influence the
// result. hence, force switch.
reverse(null, true);
}
}
self = self.lift_sequences(compressor);
@@ -1736,11 +1796,12 @@ merge(Compressor.prototype, {
if (self.left instanceof AST_String
&& self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") {
&& self.right.operator == "typeof"
&& compressor.option("unsafe")) {
if (!(self.right.expression instanceof AST_SymbolRef)
|| !self.right.expression.undeclared()) {
self.left = self.right.expression;
self.right = make_node(AST_Undefined, self.left).optimize(compressor);
self.right = self.right.expression;
self.left = make_node(AST_Undefined, self.left).optimize(compressor);
if (self.operator.length == 2) self.operator += "=";
}
}
@@ -1943,7 +2004,8 @@ merge(Compressor.prototype, {
var prop = self.property;
if (prop instanceof AST_String && compressor.option("properties")) {
prop = prop.getValue();
if (is_identifier(prop)) {
if ((compressor.option("screw_ie8") && RESERVED_WORDS(prop))
|| (!(RESERVED_WORDS(prop)) && is_identifier_string(prop))) {
return make_node(AST_Dot, self, {
expression : self.expression,
property : prop

View File

@@ -148,12 +148,14 @@
};
function From_Moz_Unary(M) {
return new (M.prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({
var prefix = "prefix" in M ? M.prefix
: M.type == "UnaryExpression" ? true : false;
return new (prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({
start : my_start_token(M),
end : my_end_token(M),
operator : M.operator,
expression : from_moz(M.argument)
})
});
};
var ME_TO_MOZ = {};

View File

@@ -69,11 +69,16 @@ function OutputStream(options) {
var current_pos = 0;
var OUTPUT = "";
function to_ascii(str) {
function to_ascii(str, identifier) {
return str.replace(/[\u0080-\uffff]/g, function(ch) {
var code = ch.charCodeAt(0).toString(16);
while (code.length < 4) code = "0" + code;
return "\\u" + code;
if (code.length <= 2 && !identifier) {
while (code.length < 2) code = "0" + code;
return "\\x" + code;
} else {
while (code.length < 4) code = "0" + code;
return "\\u" + code;
}
});
};
@@ -109,7 +114,7 @@ function OutputStream(options) {
function make_name(name) {
name = name.toString();
if (options.ascii_only)
name = to_ascii(name);
name = to_ascii(name, true);
return name;
};
@@ -340,23 +345,28 @@ function OutputStream(options) {
/* -----[ utils ]----- */
function DEFPRINT(nodetype, generator) {
nodetype.DEFMETHOD("print", function(stream, force_parens){
var self = this;
stream.push_node(self);
if (force_parens || self.needs_parens(stream)) {
stream.with_parens(function(){
self.add_comments(stream);
self.add_source_map(stream);
generator(self, stream);
});
} else {
nodetype.DEFMETHOD("_codegen", generator);
};
AST_Node.DEFMETHOD("print", function(stream, force_parens){
var self = this, generator = self._codegen;
stream.push_node(self);
var needs_parens = self.needs_parens(stream);
var fc = self instanceof AST_Function && !stream.option("beautify");
if (force_parens || (needs_parens && !fc)) {
stream.with_parens(function(){
self.add_comments(stream);
self.add_source_map(stream);
generator(self, stream);
}
stream.pop_node();
});
};
});
} else {
self.add_comments(stream);
if (needs_parens && fc) stream.print("!");
self.add_source_map(stream);
generator(self, stream);
}
stream.pop_node();
});
AST_Node.DEFMETHOD("print_to_string", function(options){
var s = OutputStream(options);
@@ -373,6 +383,16 @@ function OutputStream(options) {
if (start && !start._comments_dumped) {
start._comments_dumped = true;
var comments = start.comments_before;
// XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112
// if this node is `return` or `throw`, we cannot allow comments before
// the returned or thrown value.
if (self instanceof AST_Exit &&
self.value && self.value.start.comments_before.length > 0) {
comments = (comments || []).concat(self.value.start.comments_before);
self.value.start.comments_before = [];
}
if (c.test) {
comments = comments.filter(function(comment){
return c.test(comment.value);
@@ -497,11 +517,23 @@ function OutputStream(options) {
PARENS(AST_New, function(output){
var p = output.parent();
if (no_constructor_parens(this, output)
&& (p instanceof AST_Dot // (new Date).getTime()
&& (p instanceof AST_PropAccess // (new Date).getTime(), (new Date)["getTime"]()
|| p instanceof AST_Call && p.expression === this)) // (new foo)(bar)
return true;
});
PARENS(AST_Number, function(output){
var p = output.parent();
if (this.getValue() < 0 && p instanceof AST_PropAccess && p.expression === this)
return true;
});
PARENS(AST_NaN, function(output){
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this)
return true;
});
function assign_and_conditional_paren_rules(output) {
var p = output.parent();
// !(a = false) → true
@@ -726,7 +758,7 @@ function OutputStream(options) {
// to the inner IF). This function checks for this case and
// adds the block brackets if needed.
if (!self.body)
return output.semicolon();
return output.force_semicolon();
if (self.body instanceof AST_Do
&& output.option("ie_proof")) {
// https://github.com/mishoo/UglifyJS/issues/#issue/57 IE
@@ -750,7 +782,7 @@ function OutputStream(options) {
}
else break;
}
self.body.print(output);
force_statement(self.body, output);
};
DEFPRINT(AST_If, function(self, output){
output.print("if");
@@ -902,7 +934,7 @@ function OutputStream(options) {
DEFPRINT(AST_New, function(self, output){
output.print("new");
output.space();
AST_Call.prototype.print.call(self, output);
AST_Call.prototype._codegen(self, output);
});
AST_Seq.DEFMETHOD("_do_print", function(output){
@@ -930,7 +962,7 @@ function OutputStream(options) {
DEFPRINT(AST_Dot, function(self, output){
var expr = self.expression;
expr.print(output);
if (expr instanceof AST_Number) {
if (expr instanceof AST_Number && expr.getValue() >= 0) {
if (!/[xa-f.]/i.test(output.last())) {
output.print(".");
}
@@ -982,8 +1014,7 @@ function OutputStream(options) {
if (len > 0) output.space();
a.forEach(function(exp, i){
if (i) output.comma();
if (!(exp instanceof AST_Undefined))
exp.print(output);
exp.print(output);
});
if (len > 0) output.space();
});
@@ -1005,7 +1036,7 @@ function OutputStream(options) {
DEFPRINT(AST_ObjectKeyVal, function(self, output){
var key = self.key;
if (output.option("quote_keys")) {
output.print_string(key);
output.print_string(key + "");
} else if ((typeof key == "number"
|| !output.option("beautify")
&& +key + "" == key)
@@ -1034,6 +1065,7 @@ function OutputStream(options) {
DEFPRINT(AST_Undefined, function(self, output){
output.print("void 0");
});
DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_Infinity, function(self, output){
output.print("1/0");
});
@@ -1057,6 +1089,9 @@ function OutputStream(options) {
if (output.option("ascii_only"))
str = output.to_ascii(str);
output.print(str);
var p = output.parent();
if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self)
output.print(" ");
});
function force_statement(stat, output) {

View File

@@ -149,7 +149,7 @@ function is_unicode_connector_punctuation(ch) {
};
function is_identifier(name) {
return /^[a-z_$][a-z0-9_$]*$/i.test(name) && !RESERVED_WORDS(name);
return !RESERVED_WORDS(name) && /^[a-z_$][a-z0-9_$]*$/i.test(name);
};
function is_identifier_start(code) {
@@ -167,6 +167,17 @@ function is_identifier_char(ch) {
;
};
function is_identifier_string(str){
var i = str.length;
if (i == 0) return false;
if (is_digit(str.charCodeAt(0))) return false;
while (--i >= 0) {
if (!is_identifier_char(str.charAt(i)))
return false;
}
return true;
};
function parse_js_number(num) {
if (RE_HEX_NUMBER.test(num)) {
return parseInt(num.substr(2), 16);
@@ -190,12 +201,6 @@ JS_Parse_Error.prototype.toString = function() {
};
function js_error(message, filename, line, col, pos) {
AST_Node.warn("ERROR: {message} [{file}:{line},{col}]", {
message: message,
file: filename,
line: line,
col: col
});
throw new JS_Parse_Error(message, line, col, pos);
};
@@ -881,11 +886,14 @@ function parse($TEXT, options) {
};
var function_ = function(in_statement, ctor) {
var name = is("name") ? as_symbol(in_statement
? AST_SymbolDefun
: ctor === AST_Accessor
? AST_SymbolAccessor
: AST_SymbolLambda) : null;
var is_accessor = ctor === AST_Accessor;
var name = (is("name") ? as_symbol(in_statement
? AST_SymbolDefun
: is_accessor
? AST_SymbolAccessor
: AST_SymbolLambda)
: is_accessor && (is("string") || is("num")) ? as_atom_node()
: null);
if (in_statement && !name)
unexpected();
expect("(");
@@ -1128,7 +1136,7 @@ function parse($TEXT, options) {
if (first) first = false; else expect(",");
if (allow_trailing_comma && is("punc", closing)) break;
if (is("punc", ",") && allow_empty) {
a.push(new AST_Undefined({ start: S.token, end: S.token }));
a.push(new AST_Hole({ start: S.token, end: S.token }));
} else {
a.push(expression(false));
}
@@ -1333,15 +1341,8 @@ function parse($TEXT, options) {
function is_assignable(expr) {
if (!options.strict) return true;
switch (expr[0]+"") {
case "dot":
case "sub":
case "new":
case "call":
return true;
case "name":
return expr[1] != "this";
}
if (expr instanceof AST_This) return false;
return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol);
};
var maybe_assign = function(no_in) {
@@ -1355,7 +1356,7 @@ function parse($TEXT, options) {
left : left,
operator : val,
right : maybe_assign(no_in),
end : peek()
end : prev()
});
}
croak("Invalid assignment");

View File

@@ -57,13 +57,17 @@ function SymbolDef(scope, index, orig) {
SymbolDef.prototype = {
unmangleable: function(options) {
return this.global
return (this.global && !(options && options.toplevel))
|| this.undeclared
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with));
},
mangle: function(options) {
if (!this.mangled_name && !this.unmangleable(options))
this.mangled_name = this.scope.next_mangled(options);
if (!this.mangled_name && !this.unmangleable(options)) {
var s = this.scope;
if (this.orig[0] instanceof AST_SymbolLambda && !options.screw_ie8)
s = s.parent_scope;
this.mangled_name = s.next_mangled(options);
}
}
};
@@ -84,9 +88,12 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
if (node instanceof AST_Scope) {
node.init_scope_vars(nesting);
var save_scope = node.parent_scope = scope;
var save_labels = labels;
++nesting;
scope = node;
labels = new Dictionary();
descend();
labels = save_labels;
scope = save_scope;
--nesting;
return true; // don't descend again in TreeWalker
@@ -118,13 +125,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
node.init_scope_vars();
}
if (node instanceof AST_SymbolLambda) {
//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);
scope.def_function(node);
}
else if (node instanceof AST_SymbolDefun) {
// Careful here, the scope where this should be defined is
@@ -138,7 +139,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(){
|| node instanceof AST_SymbolConst) {
var def = scope.def_variable(node);
def.constant = node instanceof AST_SymbolConst;
def = tw.parent();
def.init = tw.parent().value;
}
else if (node instanceof AST_SymbolCatch) {
// XXX: this is wrong according to ECMA-262 (12.4). the
@@ -276,14 +277,14 @@ AST_Scope.DEFMETHOD("def_variable", function(symbol){
});
AST_Scope.DEFMETHOD("next_mangled", function(options){
var ext = this.enclosed, n = ext.length;
var ext = this.enclosed;
out: while (true) {
var m = base54(++this.cname);
if (!is_identifier(m)) continue; // skip over "do"
// we must ensure that the mangled name does not shadow a name
// from some parent scope that is referenced in this or in
// inner scopes.
for (var i = n; --i >= 0;) {
for (var i = ext.length; --i >= 0;) {
var sym = ext[i];
var name = sym.mangled_name || (sym.unmangleable(options) && sym.name);
if (m == name) continue out;
@@ -338,8 +339,11 @@ AST_Symbol.DEFMETHOD("global", function(){
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){
return defaults(options, {
except : [],
eval : false,
except : [],
eval : false,
sort : false,
toplevel : false,
screw_ie8 : false
});
});
@@ -360,12 +364,16 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){
return true; // don't descend again in TreeWalker
}
if (node instanceof AST_Scope) {
var p = tw.parent();
var p = tw.parent(), a = [];
node.variables.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) {
to_mangle.push(symbol);
a.push(symbol);
}
});
if (options.sort) a.sort(function(a, b){
return b.references.length - a.references.length;
});
to_mangle.push.apply(to_mangle, a);
return;
}
if (node instanceof AST_Label) {

View File

@@ -44,7 +44,6 @@
"use strict";
// Tree transformer helpers.
// XXX: eventually I should refactor the compressor to use this infrastructure.
function TreeTransformer(before, after) {
TreeWalker.call(this);

View File

@@ -3,7 +3,7 @@
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
"homepage": "http://lisperator.net/uglifyjs",
"main": "tools/node.js",
"version": "2.2.2",
"version": "2.3.3",
"engines": { "node" : ">=0.4.0" },
"maintainers": [{
"name": "Mihai Bazon",
@@ -15,6 +15,7 @@
"url": "https://github.com/mishoo/UglifyJS2.git"
}],
"dependencies": {
"async" : "~0.2.6",
"source-map" : "~0.1.7",
"optimist" : "~0.3.5"
},

12
test/compress/arrays.js Normal file
View File

@@ -0,0 +1,12 @@
holes_and_undefined: {
input: {
x = [1, 2, undefined];
y = [1, , 2, ];
z = [1, undefined, 3];
}
expect: {
x=[1,2,void 0];
y=[1,,2];
z=[1,void 0,3];
}
}

View File

@@ -0,0 +1,25 @@
typeof_eq_undefined: {
options = {
comparisons: true
};
input: { a = typeof b.c != "undefined" }
expect: { a = "undefined" != typeof b.c }
}
typeof_eq_undefined_unsafe: {
options = {
comparisons: true,
unsafe: true
};
input: { a = typeof b.c != "undefined" }
expect: { a = void 0 !== b.c }
}
typeof_eq_undefined_unsafe2: {
options = {
comparisons: true,
unsafe: true
};
input: { a = "undefined" != typeof b.c }
expect: { a = void 0 !== b.c }
}

View File

@@ -0,0 +1,48 @@
/**
* There was an incorrect sort behaviour documented in issue #143:
* (x = f(…)) <= x → x >= (x = f(…))
*
* For example, let the equation be:
* (a = parseInt('100')) <= a
*
* If a was an integer and has the value of 99,
* (a = parseInt('100')) <= a → 100 <= 100 → true
*
* When transformed incorrectly:
* a >= (a = parseInt('100')) → 99 >= 100 → false
*/
tranformation_sort_order_equal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) == a }
expect: { (a = parseInt('100')) == a }
}
tranformation_sort_order_unequal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) != a }
expect: { (a = parseInt('100')) != a }
}
tranformation_sort_order_lesser_or_equal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) <= a }
expect: { (a = parseInt('100')) <= a }
}
tranformation_sort_order_greater_or_equal: {
options = {
comparisons: true,
};
input: { (a = parseInt('100')) >= a }
expect: { (a = parseInt('100')) >= a }
}

View File

@@ -17,9 +17,38 @@ dot_properties: {
input: {
a["foo"] = "bar";
a["if"] = "if";
a["*"] = "asterisk";
a["\u0EB3"] = "unicode";
a[""] = "whitespace";
a["1_1"] = "foo";
}
expect: {
a.foo = "bar";
a["if"] = "if";
a["*"] = "asterisk";
a.\u0EB3 = "unicode";
a[""] = "whitespace";
a["1_1"] = "foo";
}
}
dot_properties_es5: {
options = {
properties: true,
screw_ie8: true
};
input: {
a["foo"] = "bar";
a["if"] = "if";
a["*"] = "asterisk";
a["\u0EB3"] = "unicode";
a[""] = "whitespace";
}
expect: {
a.foo = "bar";
a.if = "if";
a["*"] = "asterisk";
a.\u0EB3 = "unicode";
a[""] = "whitespace";
}
}

View File

@@ -208,3 +208,53 @@ constant_switch_9: {
}
}
}
drop_default_1: {
options = { dead_code: true };
input: {
switch (foo) {
case 'bar': baz();
default:
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
}
}
drop_default_2: {
options = { dead_code: true };
input: {
switch (foo) {
case 'bar': baz(); break;
default:
break;
}
}
expect: {
switch (foo) {
case 'bar': baz();
}
}
}
keep_default: {
options = { dead_code: true };
input: {
switch (foo) {
case 'bar': baz();
default:
something();
break;
}
}
expect: {
switch (foo) {
case 'bar': baz();
default:
something();
}
}
}

25
test/compress/typeof.js Normal file
View File

@@ -0,0 +1,25 @@
typeof_evaluation: {
options = {
evaluate: true
};
input: {
a = typeof 1;
b = typeof 'test';
c = typeof [];
d = typeof {};
e = typeof /./;
f = typeof false;
g = typeof function(){};
h = typeof undefined;
}
expect: {
a='number';
b='string';
c=typeof[];
d=typeof{};
e=typeof/./;
f='boolean';
g='function';
h='undefined';
}
}

View File

@@ -56,6 +56,9 @@ exports.minify = function(files, options) {
inSourceMap : null,
fromString : false,
warnings : false,
mangle : {},
output : null,
compress : {}
});
if (typeof files == "string")
files = [ files ];
@@ -73,33 +76,42 @@ exports.minify = function(files, options) {
});
// 2. compress
toplevel.figure_out_scope();
var sq = UglifyJS.Compressor({
warnings: options.warnings,
});
toplevel = toplevel.transform(sq);
if (options.compress) {
var compress = { warnings: options.warnings };
UglifyJS.merge(compress, options.compress);
toplevel.figure_out_scope();
var sq = UglifyJS.Compressor(compress);
toplevel = toplevel.transform(sq);
}
// 3. mangle
toplevel.figure_out_scope();
toplevel.compute_char_frequency();
toplevel.mangle_names();
if (options.mangle) {
toplevel.figure_out_scope();
toplevel.compute_char_frequency();
toplevel.mangle_names(options.mangle);
}
// 4. output
var map = null;
var inMap = null;
if (options.inSourceMap) {
var inMap = options.inSourceMap;
var output = {};
if (typeof options.inSourceMap == "string") {
inMap = fs.readFileSync(options.inSourceMap, "utf8");
}
if (options.outSourceMap) map = UglifyJS.SourceMap({
file: options.outSourceMap,
orig: inMap,
root: options.sourceRoot
});
var stream = UglifyJS.OutputStream({ source_map: map });
if (options.outSourceMap) {
output.source_map = UglifyJS.SourceMap({
file: options.outSourceMap,
orig: inMap,
root: options.sourceRoot
});
}
if (options.output) {
UglifyJS.merge(output, options.output);
}
var stream = UglifyJS.OutputStream(output);
toplevel.print(stream);
return {
code : stream + "",
map : map + ""
map : output.source_map + ""
};
};