At YUIConf this year, I mentioned to someone that I was playing around in Node with statically analyzing YUI to aid in crafting use statements that included no more than was strictly necessary. That's an interesting idea, but is probably not very realistic in pure static analysis, and it's really not the subject of this post. In that conversation, I was told about UglifyJS, a JavaScript compressor written for NodeJS that works by creating an abstract syntax tree of the JavaScript before minifying it. It seemed that an AST could help in my static analysis, so I started looking at it.
In the README file, under the "Compression - How Good is it?" heading, some basic figures were provided using two libraries (DynaARCH and jQuery) and three minifiers (UglifyJS, YUICompressor, and Google's Closure Compiler). You can go read his results there, but I took a few issues with his data. First, we were not told what versions of the libraries he used, or the compressors. Recent versions of the YUICompressor have some significant performance increases that I wanted to test as well. Second, YUICompressor was designed to make a few decisions that were meant to improve gzip compressibility of a program, so you can't really examine the compression without considering gzip, in my opinion.
Let me first describe my methodology. For time performance tests, I ran through each compressor 1000 times using the UNIX time program to get runtimes for each run, I then took the arithmetic mean of the runtimes for each library and compressor combination to get my figures for the following charts. I also ran each compressor one additional time to save a minified version to disk, and gzipped a copy of the minified file to do the file-size comparison. This was done with the aid of a shell script. The tests were run on my workstation, an AMD Athlon 64 X2 Dual Core 6000+ running Ubuntu 10.10, but of course the more interesting part is the relationships, not the raw data.
I tested the following libraries:
- jQuery 1.4.3
- Mootools Core 1.3
- SimpleYUI 3.3.0pr1
Using the following compressors:
- YUICompressor 2.4.5pre in command-line mode (Distributed with YUI Builder)
- YUICompressor 2.4.5pre in server mod
- UglifyJS 0.0.1
- Google Closure Compiler from 2010.11.10, running with the SIMPLE_OPTIMIZATIONS flag
I want to first address speed. In the readme, the UglifyJS author says that Uglify is about twice as fast as YUICompressor and more than four times as fast as the closure compiler. One of the largest criticisms of closure compiler is it's speed, and YUICompressor definitely suffered a similar reputation. However, in recent builds, Reid Burke of Yahoo! modified YUICompressor to run as a server that you could POST files up to using cURL, meaning you don't need to pay the Java startup costs on every invocation. This has dramatically improved the performance.
Time to Compress Data
UglifyJS's speed is very impressive, given that it's a pure-javascript implementation. Anyone who thinks JavaScript is too slow now? However, when you take out the startup overhead by running YUICompressor in server mode, it destroys UglifyJS. And Closure is just slow, but everyone knows that. There is one fascinating anomaly in the above data, and that is jQuery. Now there are those who argue that jQuery's code standards are somewhat shoddy, and that makes minifying it properly difficult, which may account for why YUICompressor takes so long to compress it. And Reid tells me that YUI Compressor uses a slightly different set of defaults in server mode as command-line mode. It would be interesting to see what about jQuery's source hits this awful regression so hard.
Uglify's process of building an AST of the program seems to be fairly resilient against poorly formed inputs, which is why it's compression time seems to be more variable on file-size than anything else. While this means that Uglify is consistent, it does raise a few questions with me about it's resilience. But I'll do more on that later. For now, let's look at compression.
File Size Comparisons Data
This comparison is going to be a lot more relevant for most users, since minification is something that is done rarely, where serving the files is done often. Again, the raw data is linked, but what is more interesting is the comparisons visible in the bars above. Closure and Uglify are pretty similar, with Closure usually taking the uncompresed file size, and Uglify being better once gzip is applied. YUICompressor definitely falls short here, coming in roughly 20% less efficient before gzip, and ~15% less efficient after gzip.
I am not saying not to use YUICompressor. Hell, I am looking at integrating it into our build process at work. Unlike Closure and Uglify, YUICompressor can do CSS, so you have one fewer applications to keep around for minification. There is a far more important point: YUICompressor is more conservative, and it changes less frequently. Closure is known to break code under certain circumstances, Uglify is not covered by as many tests as it's creator would like.
Will Uglify break your code? I'm not sure. It's mechanism and process look fairly safe, but it remains untested as far as many are concerned. I am interested in working on a set of Compressor test inputs which will verify the code is still functional and behaving consistently after compression, but haven't begun that project yet. If these tests show anything it's that YUICompressor should be used in server-mode if you care about it's speed, and that it can definitely improve it's minification ratio, and Uglify may be a good place to look for inspiration.
In the future, I hope that a set of confirmation tests for JavaScript minifiers will be available that ensures that the code they output behaves as expected.