A List Apart

Menu
Issue № 310

JavaScript Minification Part II

by Published in JavaScript, Workflow & Tools · 11 Comments

In my last article, I discussed the basics of better JavaScript minification with YUI Compressor. As many readers astutely pointed out, this boiled down to, “avoid using eval() and the with statement.” The article’s premise was to ensure you understood how not to shoot yourself in the foot when using the YUI Compressor. Previously, we learned what not to do. This article emphasizes what you should do to take advantage of YUI Compressor’s best feature for minification: local variable name replacement.

Variable name replacement

Replacing local variable names with short (one or two character) alternatives is the biggest byte savings that YUI Compressor offers. Frequently, variable naming is a source of angst in coding, but fundamentally, variable names only have meaning to humans attempting to understand the code. Once you’re sure that a human doesn’t need to interpret the code, variables simply become generic placeholders for values.

When deploying JavaScript out into production, your only consumer is the browser, and the browser couldn’t care less about variable names. For that reason, the YUI Compressor replaces these variable names with shorter versions. Since most debugging occurs on development boxes with unminified code, this typically doesn’t affect the engineer’s job. However, if you get stuck needing to debug something deeply in production, there are ways to make it easier to debug minified code.

What can’t be replaced?

Before talking about techniques for optimal variable replacement, it’s important to understand the parts of code that cannot be replaced. The following are frequently thought to be replaceable:

  • property names (object.property),
  • keywords (if, var, return, etc.), and
  • global variables.

Many developers don’t stop to think about the number of times a property is accessed or how many JavaScript keywords they use when writing code. Considering property names, keywords, and global variables within your code’s structure can result in a much greater minification ratio.

Property names

While the YUI Compressor can perform variable replacement on local variables, there is nothing it can do about properties. Since property names are defined on the object itself, there is no way to safely detect or replace them. That means code that accesses many properties will end up larger than code that doesn’t. For example:

(Line wraps marked » —Ed.)

function toggleImage(id){
  if (document.getElementById(id).src.indexOf("1.png") > -1){
    document.getElementById(id).src = document.getElementById » 
(id).src.replace("1.png", "2.png"); } }

This function toggles the display of an image, changing it from one image to another by manipulating the src property. Unfortunately, the resulting minified function is nearly as large (168 bytes vs. 205 bytes):

function toggleImage(a){ if(document.getElementById(a).src.indexOf » 
("1.png")>-1){document.getElementById(a).src=document.getElementBy »
Id(a).src.replace("1.png","2.png")}};

Note that document.getElementById(a).src is repeated three times, which is almost exactly the same as the original (with the exception of the variable name).

When the same object property is used frequently, the best strategy is to store the property’s value in a local variable and then use the local variable. The previous code can be rewritten as:

function toggleImage(id){
  var image = document.getElementById(id);
    if (image.src.indexOf("1.png") > -1){
      image.src = image.src.replace("1.png", "2.png");
    }
}

Not only is this version of the code smaller than the previous (185 bytes), the minified version is even smaller:

function toggleImage(b){var a=document.getElementById(b);if(a.src. » 
indexOf("1.png")>-1){a.src=a.src.replace("1.png","2.png")}};

This code is only 126 bytes—much smaller than the original code and smaller still than the original minified code. These savings come without any change in functionality, just a bit of refactoring. It’s worth noting that this change also improves the runtime performance because document.getElementById() doesn’t have to be called three times.

Always keep in mind that anything that appears after a dot in JavaScript (object.property) cannot be minified any further. When you minimize the number of dots in your code, you also minimize the overall size once the code is minified.

Keywords

When you look at any large JavaScript file, you’ll notice many keywords being used. Keywords such as var, if, for, and return are all regularly used to create the desired functionality. The problem is that these keywords can be overused, and as such, affect the minified file size. The two most overused keywords are var and return.

The var statement defines one or more variables. Sometimes you’ll see code that looks like this:

var image = document.getElementById("myImage");
var div = document.getElementById("myDiv");

This code defines two variables, one right after the other. I often see this pattern for 10 or more variables in a row. Doing so artificially inflates the size of your code because multiple var statements can be combined into one using a comma operator:

var image = document.getElementById("myImage"),
  div = document.getElementById("myDiv");

This code also defines two variables and provides the same initialization. However, you’ve saved the three bytes that another var would have cost. Three bytes might not seem like a big deal, but if you’re able to find dozens of places where there’s currently an extra var statement, the savings can really add up. The best advice is to use one var statement at the beginning of each function to define all the variables used in that function. The YUI Compressor will point out functions with more than one var statement when the ”“v flag is used (JSLint is also capable of detecting this).

The second statement, return, typically gets overused in this manner:

function getValueFor(data){  if (firstCondition(data)){
      return 1;
  } else if (secondCondition(data)){
      return 2;
  } else if (thirdCondition(data)){
      return 3;
  } else {
      return 4;
  }
    
}

Ultimately, this function ends up returning a value based on the evaluation of some conditions. The minified version is 146 bytes. You can save some bytes by using a single return statement:

function getValueFor(data){  var value;  if (firstCondition(data)){
      value = 1;
  } else if (secondCondition(data)){
      value = 2;
  } else if (thirdCondition(data)){
      value = 3;
  } else {
      value = 4;
  }  return value;
    
}

The rewritten code replaces most instances of return with a local variable that can be minified. The minified version of this code is 140 bytes. The code can actually be refactored even further:

function getValueFor(data){  var value = 4;  if (firstCondition(data)){
      value = 1;
  } else if (secondCondition(data)){
      value = 2;
  } else if (thirdCondition(data)){
      value = 3;
  }  return value;
    
}

This code minifies to 133 bytes and produces the same result. Note that this version also removes an additional keyword, else, which helps to make it even smaller.

Without intending to reopen the age-old debate about whether single exit points are good and/or necessary, using a single return statement for each function decreases the size of your functions when minified.

Global variables

As mentioned previously, variable name replacement occurs only on local variables. Attempting to replace global variable names, including global function names, can result in broken code because the YUI Compressor has no way of knowing where else these variables and functions might be used. This applies both to predefined globals such as window and document as well as global variables that you create.

Store global variables locally

If you need to use a global variable more than once in a function, it’s best to store that global variable in a local variable. This has two important advantages: It’s faster to access local variables at runtime and once stored in a local variable, the YUI Compressor can replace the variable name. For example:

function createMessageElement(message){
  var div = document.createElement("div");
  div[removed] = message;
  document.body.appendChild(div);
}

This function has two local variables, message and div, as well as one global variable, document. Look at what happens when the code is minified:

function createMessageElement(a){var b=document.createElement("div"); » 
b[removed]=a;document.body.appendChild(b)};

You can clearly see that document appears twice in the minified code. You’ve saved 43 bytes (158 for the original, 115 minified), which isn’t bad, but it could be better. By storing document in a local variable, you can save even more. Here’s the rewritten code:

function createMessageElement(message){
  var doc = document,
        div = doc.createElement("div");
  div[removed] = message;
  doc.body.appendChild(div);
}

The minified version of this code is as follows:

function createMessageElement(a){var b=document,c=b.createElement » 
("div");c[removed]=a;b.body.appendChild(c)};

Once again, even though the original code is slightly larger (175 bytes), the minified code is actually smaller (110 bytes). The savings in this case are an extra five bytes which may not seem like a lot, but this function only uses document twice; if it were used more frequently, the savings would be greater.

Avoid creating globals

The YUI Compressor can’t replace any globals, including the ones that you define yourself. For this reason, it’s best to minimize the number of global variables and functions you introduce (this is also considered a best practice for maintainability, as well). Consider the following example:

var helloMessage = "Hello world!";
function displayMessage(){
  alert(helloMessage);
}
displayMessage();

In this code, both message and displayMessage() are global and therefore can’t have their names replaced. The resulting minified code is as follows:

var helloMessage="Hello world!";function displayMessage(){alert » 
(helloMessage)}displayMessage();

Whereas the original code is 113 bytes, the compressed code is 95 bytes; not a huge savings.

In most cases, you don’t actually need global variables to accomplish the task. This code, for example, works equally well when both helloMessage and displayMessage() are local variables. You can create all of the variables and functions in a given code block by building a self-executing anonymous function around it.

A self-executing function looks a little strange:

(function(){
  //code here
})();

This code creates a function and executes it immediately. It’s similar to this:

function doSomething(){
  //code here
}
doSomething();

Since the function is only going to be called once, you can save bytes by eliminating the name. A self-executing function is the fastest way to create a function and execute it exactly once. It’s also very easy to include already-existing code inside of a self-executing function. Here’s how:

(function(){  var helloMessage = "Hello world!";
  function displayMessage(){
      alert(helloMessage);
  }
  displayMessage();})();

Inside of the self-executing function, helloMessage and displayMessage() are local. The minified version of this code is as follows:

(function(){var b="Hello world!";function a(){alert(b)}a()})();

Note that even though the original code is somewhat larger, weighing in at 154 bytes, the minified version is only 63 bytes (32 bytes smaller than the original minified code).

Combining techniques

You can further improve the efficacy of a self-executing function by passing globals in as arguments. Once inside the function, the arguments are local variables, so you have automatically created an opportunity for better minification. Suppose you have the following code:

var helloMessage = "Hello world!";function createMessageElement(message){
  var div = document.createElement("div");
  div[removed] = message;
  document.body.appendChild(div);
}createMessageElement(helloMessage);

This code creates a global function, createMessageElement(), and that function uses the global document object. There is also a global variable helloMessage. When minified on its own, you end up with a total of 179 bytes. Wrapping it in a self-executing function turns createMessageElement() and helloMessage into locals:

(function(){
  var helloMessage = "Hello world!";  function createMessageElement(message){
      var div = document.createElement("div");
      div[removed] = message;
      document.body.appendChild(div);
  }  createMessageElement(helloMessage);
})()

The only remaining global in this code is document, and so the minified code isn’t as optimal as it can be:

(function(){var a="Hello world!";function b(c){var d=document. » 
createElement("div");d[removed]=c;document.body.appendChild(d)}b(a)})();

The compression ratio is pretty good for this example (283 bytes to 135 bytes, or 52%), but you can still get a bit more by making document into a local variable. You can do this easily by passing in document to the self-executing function as an argument:

(function(doc){
    var helloMessage = "Hello world!";    function createMessageElement(message){
        var div = doc.createElement("div");
        div[removed] = message;
        doc.body.appendChild(div);
    }    createMessageElement(helloMessage);
})(document)

In this code, the variable doc is local to the self-executing function, and so can be replaced by YUI Compressor. The result is:

(function(c){var a="Hello world!";function b(d){var e=c.createElement » 
("div");e[removed]=d;c.body.appendChild(e)}b(a)})(document);

This code is 130 bytes, smaller than both the original and the previous examples. Once again, you see greater savings with this technique when global variables are used more than twice.

The gzip caveat

After minifying JavaScript, the next step is to compress it, and gzip is the most popular form of compression. Gzip works by optimizing redundancies in text, and the techniques described in this article effectively remove some of the redundancies that could normally be compressed using gzip.

When refactoring your code using these techniques, you should expect to see the gzip compression ratio decrease—which is to say that the percentage difference between the size of the minified file and the file when both minified and gzipped will be smaller than before. Ultimately, what you should be aiming for is the smallest possible gzipped version and then the smallest possible minified version. The gzipped version is important for network transfer times while the minified version is important for parse times and caching.

While writing this article, I came across Meebo’s XAuth open source library. The JavaScript looked like it could be smaller just by applying some of these techniques. I spent a little time refactoring the code and came away with decent results (you can see my code on GitHub).

                                                                             
RawMinifiedMinified + Gzipped% Gzip Diff
Original5720 b1307 b668 b49% smaller
Refactored6143 b1210 b663 b45% smaller

Even though the raw source code size grew by a few hundred bytes, the minified size decreased by almost 100 bytes, while the minified and gzipped version shrunk by five bytes. Note that gzip was about four percent less efficient with the refactored code even though the overall code size is smaller.

Conclusion

The most powerful byte-savings technique in YUI Compressor is replacing variables with single- or double-letter variable names. Making sure that your code is setup so that this replacement can happen as frequently as possible is important for getting the smallest code. When coding, keep in mind that property names, keywords, and global variables remain as-is in the minified code. The techniques presented in this article are designed to minimize the impact of these issues in the final minified code. You should take care to test your refactored code with gzip to ensure that you’re getting the smallest code possible, as removing redundancies decreases the efficiency of gzip.

11 Reader Comments

Load Comments