Helping the YUI Compressor

By YUI TeamFebruary 11th, 2008

Nicholas Zakas joined Yahoo! in 2006. He is the author of Professional Ajax and Professional JavaScript for Web Developers. He’s a contributor to our Yahoo! Juku. His Maintainable JavaScript presentation is available on YUI Theater.

Julien’s YUI Compressor is an incredibly useful tool for decreasing the size of your JavaScript files. Since it uses Rhino to parse your JavaScript code, it can perform all kinds of smart operations to save bytes in a completely safe way:

  • Replacement of local variable names with shorter (one, two, or three character) variable names.
  • Replacement of bracket notation with dot notation where possible (i.e. foo["bar"] becomes foo.bar).
  • Replacement of quoted literal property names where possible (i.e. { "foo":"bar" } becomes { foo:"bar" } ).
  • Replacement of escaped quotes in strings (i.e. 'aaa\'bbb' becomes "aaa’bbb").

Running your JavaScript code through YUI Compressor results in tremendous savings by default, but there are things you can do to increase the byte savings even further.

Use Constants for Repeated Values

In my talk, Maintainable JavaScript, I talk about using constants (really, just variables that you have no intention of changing) to store repeating values. The idea is that your code is more maintainable because you have a single place to change a value instead of multiple places. As it turns out, this technique also helps YUI Compressor to remove more bytes. Consider the following function:

function toggle(element){
    if (YAHOO.util.Dom.hasClass(element, "selected")){
        YAHOO.util.Dom.removeClass(element, "selected");
    } else {
        YAHOO.util.Dom.addClass(element, "selected");
    }
}

This simple function is designed to toggle the “selected” class on a given element. If the element has the class, then it’s removed; if the element doesn’t have the class, it’s added. As a result, the string “selected” appears three times in the function. The function takes 212 bytes (including white space). When compressed, the resulting code is as follows:

function toggle(A){if(YAHOO.util.Dom.hasClass(A,"selected")){YAHOO.util.Dom.removeClass(A,"selected")}else{YAHOO.util.Dom.addClass(A,"selected")}}

This code weighs in at 146 bytes (a savings of 30%), but you can see that the string “selected” still appears three times. Moving the repeated value into a variable makes the code more maintainable and allows YUI Compressor to remove extra space. Here’s the rewritten function:

function toggle(element){
    var className = "selected";
    if (YAHOO.util.Dom.hasClass(element, className)){
        YAHOO.util.Dom.removeClass(element, className);
    } else {
        YAHOO.util.Dom.addClass(element, className);
    }
}

This code is slightly larger than the original (241 bytes versus 212 bytes), but compresses down to the following:

function toggle(A){var B="selected";if(YAHOO.util.Dom.hasClass(A,B)){YAHOO.util.Dom.removeClass(A,B)}else{YAHOO.util.Dom.addClass(A,B)}}

Note that this compressed code only has one instance of “selected”, resulting in a final byte size of 136 bytes, 10 bytes fewer than the previous version. The savings grow as the instances of the string increase, so if you have 20 places where “selected” was being used, you’d see even greater savings.

Replacing repeated values in your code can lead to greater incremental savings as the number of repeated values increases, as well. It is worthwhile to consider this approach not just for strings, but also for numbers (even Boolean values, if you so desire).

Store Local References to Objects/Values

The YUI Compressor can’t perform variable replacement for either global variables or multi-level object references, so it’s better to store these in local variables. The previous example has three instances of YAHOO.util.Dom in the source code, and so the compressed version also has three instances. By storing YAHOO.util.Dom in a local variable, you can reduce the number of times that it appears in the compressed code. For example:

function toggle(element){
    var className = "selected";
    var YUD = YAHOO.util.Dom;
    if (YUD.hasClass(element, className)){
        YUD.removeClass(element, className);
    } else {
        YUD.addClass(element, className);
    }
}

This version of the function is 238 bytes, and when compressed, shows even greater savings than the previous versions of the function:


function toggle(A){var B="selected";var C=YAHOO.util.Dom;if(C.hasClass(A,B)){C.removeClass(A,B)}else{C.addClass(A,B)}}

The final weight for this version is 118 bytes, a savings of 28 bytes over the original compressed function and 120 bytes smaller from the uncompressed version. And this is just one function, imagine if you got the same savings for all functions in your script.

Keep in mind that this technique also applies to object properties, so if className were a member of an object, its value should be stored locally as well. For instance:

function toggle(element){
    var YUD = YAHOO.util.Dom;
    if (YUD.hasClass(element, Constants.className)){
        YUD.removeClass(element, Constants.className);
    } else {
        YUD.addClass(element, Constants.className);
    }
}

In this function, Constants.className contains the class to use. The variable Constants is global, so its name cannot be replaced. You could set up a reference to Constants, but that is inefficient because you’re only using one property of that object in the function, so set up a reference to Constants.className to save even more bytes:

function toggle(element){
    var className = Constants.className
    var YUD = YAHOO.util.Dom;
    if (YUD.hasClass(element, className)){
        YUD.removeClass(element, className);
    } else {
        YUD.addClass(element, className);
    }
}

Avoid eval()

By this point, you’ve been told that eval() is evil multiple times and by multiple people. YUI Compressor agrees. The nature of eval() is such that the code executed has access to the variables that are present in the scope in which eval() was called. Because of that, YUI Compressor can’t safely do variable name changing when eval() is present. For example:

function doSomething(code){
    var msg = "hi";
    eval(code);
}

doSomething("alert(msg)");   //”hi”

Even though the string that is being passed to eval() exists outside of the function in which eval() is called, it still has access to the local variables in that function. Since YUI Compressor can’t possibly know that the variable code contains a reference to a variable in the function, it doesn’t change the variable names in the doSomething() function, resulting in a less-than-optimal compression. Remember this: any time you use eval() in a function, that function’s variables cannot be renamed. The best approach is, as often said, to avoid eval() at all costs. If you absolutely must use eval() for some reason, try to isolate it away from other code so that the amount of variable renaming issues are minimal. For example:

function myEval(code){
    return eval(code);
}

function doSomething(code){
    var msg = “hi”;
    var count= 10;

    myEval(code);
}

In this code, the call to eval() is isolated away from the main body of the doSomething() function. Now, YUI Compressor is free to replace variables in doSomething().

Avoid with

The with statement is another that is often recommended to avoid in JavaScript. For YUI Compressor, the reason is the same for eval(): just the presence of with in a function causes variable renaming to be skipped for the entire function. There is just no way to keep track of variables versus object properties in the context of a with statement, so YUI Compressor rightly leaves the code as-is to avoid breaking the functionality. The best advice here is to avoid using with altogether. If you follow the advice of storing local copies of objects/properties, you should have no use for with.

Use the Verbose Option

YUI Compressor has a “verbose” option (activated by the –v command line switch) that can help in the identification of some of these issues as well as a few others. The verbose option prints out warnings to the console indicating things that are preventing the YUI Compressor from fully doing its job. It will, for instance, tell you that a function contains eval() or the with statement, and therefore cannot be properly compressed. It also does analysis of variables, telling you if a variable was never defined (in which case it becomes global and cannot have its name replaced), if a variable was defined and never used (which just wastes space), and if a variable has been declared multiple times (also a waste of space).

Conclusion

When used alone, the YUI Compressor achieves an excellent compression rate of your JavaScript code. The greatest byte savings are achieved by taking full advantage of variable replacement. The hints presented here have the primary goal of ensuring the YUI Compressor can do variable replacement whenever possible. Using constants to represent repeated values not only aids in compression, but also aids in the maintainability of your code by limited the number of areas that must be updated to accommodate a change in the value. Using local variables for multi-level object references allows for greater compression through variable replacement as well as providing faster runtime performance (local variable access is faster than global variable access and object property lookup). Perhaps most important is to ensure that you don’t use eval() or with when they’re not necessary, as each causes variable replacement to be turned off in the containing function. The YUI Compressor does a lot for you, but it can’t do everything. You can help it out greatly by following these tips.

25 Comments

  1. The YUI Compressor is awesome!
    I’ve been using it now to compress all the JavaScript and CSS for my website for months now.
    It’s never ONCE given me an issue with the code it produces, it just works.

    It’s 100% trustworthy in a production environment and I highly recommend it :)

  2. Your advice in “Use Constants for Repeated Values” will reduce the resultant compressed code, but it doesn’t seem optimal. Couldn’t YUI Compressor be enhanced to detect repeated string literals and replace them with a newly created local variable?

    I agree that repeated values are generally bad, but I would like to caution readers to avoid writing code where the variable name matches the value:

    function toggle(element){
    var selected = "selected";
    if (YAHOO.util.Dom.hasClass(element, selected)){
    YAHOO.util.Dom.removeClass(element, selected);
    } else {
    YAHOO.util.Dom.addClass(element, selected);
    }
    }

    Even though you’ll get the YUI Compressor benefit, you gain nothing in readability or maintainability.

  3. Using constants for repeating values does presume that you’re using logical variable names. I’ll agree that, in most cases, using a variable name that exactly matches the string value is not a good idea as it tightly couples the variable to its meaning. That’s why I used className in my example.

    As I mentioned, this approach is good for maintainability, and having YUI Compressor do this for you negates that gain. You’re much better off structuring code in a maintainable way that also helps YUI Compressor rather than expecting the tool to do such optimizations for you.

  4. [...] Zakas has some tips on optimizing your JavaScript code for compression over at the YUI blog today. It is written specifically for helping the YUI Compressor better [...]

  5. This could be automatically done by YUI compressor.

    Another optimization which can gain some bytes is optimizing variable declaration.
    As we know JavaScript has function scope, so one “var” per scope is enaught.
    I already send patch, which concatenates all local variable declarations to one, hoping Julien will include it in his next release.

  6. I ran it against a 65K library. It produced a 37K packed file. Dean Edwards JavaScript Packer produced 27K file and JSMIN gave 44K.

    I liked the -v flag though. Found some unused variables and a unused function.

  7. Using JSLint (http://jslint.com) is very very recommended.
    D.E. Packer is great, but it has fatal mistake, JavaScript needs to be uncompressed on every run=pageload (which adds additional >200ms) load time. There is no doubt: YUI compressor and gzip is the winner! ;)

  8. [...] to read about things that really interest me, and I have yet to see a newspaper do a piece on, say, Javascript Compression. So I created [...]

  9. Very helpful thanks!

  10. Great summary, Nicholas. You recommend some best-practices that ultimately become that much better when leveraging YUI Compressor! I’ve always been a big fan of “Storing Local References to Objects/Values” as it saves a lot of redundant typing and bandwidth. YUI Compressor compliments this technique nicely :)

  11. [...] Helping the YUI Compressor » Yahoo! User Interface Blog Julien’s YUI Compressor is an incredibly useful tool for decreasing the size of your JavaScript files. Since it uses Rhino to parse your JavaScript code, it can perform all kinds of smart operations to save bytes in a completely safe way: (tags: yuiblog.com 2008 mes1 dia20 at_home javascript compressor Yahoo! **** blog_post) [...]

  12. Should setTimeout() be also isolated like eval() should be? setTimeout()/setInterval() also takes the the expression to be evaluated, like eval()..

    Any plans to implement compacting of code inside html?

  13. The setTimeout() and setInterval() functions work slightly differently than eval() when taking a string. For eval(), the string is executed within the scope of the function in which it is called. For the others, the string is executed in the global scope and so doesn’t have access to local variables or functions. Therefore, the restrictions for eval() don’t apply to setTimeout() or setInterval().

  14. Hi,

    I wish to change YUI Compressor’s sources a little to try one idea.

    I failed to find anything anywhere (including google) on how to rebuild it. Are there any Ant files, Makefiles, bash scripts or anything to rebuild it?

    Thanks in advance for your answer!

  15. Hi,

    unless you also use gzip (with content negotiation or some rewrite voodoo) I don’t see the point of the Compressor. Take a look at the editor from yui 2.5.0

    340K editor-beta-debug.js
    64K editor-beta-debug.js.gz
    332K editor-beta.js
    60K editor-beta.js.gz
    124K editor-beta-min.js
    32K editor-beta-min.js.gz

    If you realy want to get everithing out of it -min.js.gz is the way to go, otherwize gzip is better.

  16. It seems guys from Stunnix have integrated YUI Compressor in their JavaScript Obfuscator and with their product you can minify JavaScript inside HTML files too (with use of YUI Compressor).

    They even claim compressing with YUI Compressor works with server-side javascript too (ASP scripts can be coded in VBScript and in JavaScript)/

  17. Fantastic work!

    Looked around for a lib I could could use to automate compressed JS/CSS in my netbeans builds and this lib has done the trick, see my blog for info on how I used it.

  18. [...] bytes by refactoring our code to follow the advice given in Nicholas Zakas’s article Helping the YUI Compressor, which describes several things you can do to help the YUI Compressor generate even more compact [...]

  19. Hello,

    Compressor is a really good tool, I was used to play with Jsmin and a personal regexp-based variable rename tool, and your app is a lo better.

    However, it doesn’t seem to support javascript 1.7/1.8 features.

    For example, it doesn’t understand theses peaces of code :

    [a, b] = toto(); // where toto returns an array

    for each(let {a:mya, b:myb, length:length} in toto){
    // xxx
    }

    Really good work, continue! :)

  20. [...] Zakas of YUI also recommends the following, Helping The YUI Compressor. posted by Matt Snider at 4:28 pm [...]

  21. [...] Compression, plus Nick Zakas’ document on how to improve YUI compression performance, Helping The YUI Compressor, and some of the comments left me thinking about other ways to improve the compression of YUI [...]

  22. [...] Extreme JavaScript Compression with YUI Compressor. This was a followup to my YUI Blog post, Helping the YUI Compressor, in which I talked about certain patterns that could help or hinder the YUI Compressor. I continued [...]

  23. Andreas Köberle said:
    August 27, 2009 at 1:19 pm

    Whats about:

    YUD[(YUD.hasClass(element, className) ? 'remove' : 'add') + 'Class'](element, className);

  24. whay is the best option for yui compression?

  25. Replacing all string literals with variable names, while usually a good idea, is not without cost. The JS interpreter must perform a scope-chain resolution on each access to a variable name. This run-time cost is likely usually worth the download savings, but you cannot fairly completely ignore this issue, which you in fact write about yourself here: http://www.nczonline.net/blog/2009/02/10/javascript-variable-performance/