JavaScript Minification Part II
Issue № 310

JavaScript Minification Part II

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.

Article Continues Below

Variable name replacement#section1

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?#section2

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#section3

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#section4

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#section5

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#section6

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.innerHTML = 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.innerHTML=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.innerHTML = 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.innerHTML=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#section7

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#section8

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.innerHTML = 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.innerHTML = 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.innerHTML=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.innerHTML = 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.innerHTML=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#section9

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).

 

                       

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Raw Minified Minified + Gzipped % Gzip Diff
Original 5720 b 1307 b 668 b 49% smaller
Refactored 6143 b 1210 b 663 b 45% 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#section10

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

  1. For the most part these suggestions are all quite good. I would personally be hesitant about aliasing some of the builtin global variables (such as document) though.

    Whilst it may have a benefit in the size of the minified script, it could also defeat developers who are searching the source code, expecting to find “that document.getElementById” call they were looking to work on. Unless they know that document is regularly aliased and what it is aliased to, this practice could make their job harder (the job of their search tools at least).

    Also, it feels counter-intuitive to me. You would be purposely introducing non-standard names for the globals. I would expect this to add to the (time) cost of adding new developers to the project as they are forced to cope with using new names for well-known/commonly-used variables.

    From my own (server-side) experience – I once changed a common local variable that was “standard to me” in projects I work on. For months I caught myself instinctively mis-typing “db.CreateCommand()” where I meant to type “Connection.CreateCommand()”.

    Perhaps I’m too shielded from the world of .js these days, I spend the vast majority of my time in serverside logic where the size of the source files doesn’t matter anywhere near as much but I certainly wouldn’t ever want to be futzing with the names of my builtins there.

    Also – on the “single return point” issue, I always use a single return point regardless (and if I am returning anything, the variable I will be returning is always named “output”). It makes code so much more readable and easy to follow if you can be 100% certain that – as long as no exception is raised – execution begins at the start and will continue to the end of every method.

  2. Not creating lots of global variables, and structured code without eval (evil) in general is a good idea. But I don’t like the idea of having obfuscated code in production.

    First of all, I don’t like having to think about code that might not be compatible with the minifier you’re using. I don’t like extra restrictions, and I don’t want to think about them while coding, and I don’t want to stress about differences between production and a live environment.

    Secondly, even though at the company I work at, we test our code quite a lot, there are always minor bugs that show up in production because of unforeseen combinations of factors. They should’ve been killed in production, but in practice they aren’t. It’s really useful if the code is actually readable so debugging is faster and easier.

    With gzip and relatively fast internet, the amount of code saved is that minor that I conclude there’s not enough reason to minify. Unless you’re using a library that has proven to work as a minified version, like jQuery or Prototype.

  3. @craigfowler – I’ve been aliasing globals for a few years now, and have never run into developer confusion over this (provided that the variables names are easy to figure out, i.e. var doc = document, win = window;). The cognitive overhead of developers adapting to this is trivial, so I wouldn’t worry too much about that.

    @Blaise – This is technically not obfuscated code, it’s minified code. Obfuscated code is purposely hard for someone to understand and typically has some level of indirection. In this case, it’s just variable replacement and stripping of white space. The reason I recommend YUI Compressor over Closure Compiler is that the YUI Compressor goes through great pains to make sure that it’s *not* changing your code, it’s only making small optimizations that ultimately add up. I’ve never run into a production problem that was caused by the minification process.

    @atk If you’re more comfortable with line breaks remaining, or without variable replacement, there are “options”:http://developer.yahoo.com/yui/compressor/ for those. Also, if you’re really in a pickle and need to debug minified code in production, you can use the technique I described “here”:http://www.yuiblog.com/blog/2008/06/27/fiddler/ .

  4. @blaise and @atk:

    Remember, you should never have to look at/debug your minified code. If you need to fix a bug then you fix it in your development source code (laid out by your organisation’s standards, full of useful documentation and comments and the like) and then re-minify it. If a new developer comes into the project then you give them access to that source code, not just the minified script.

    The best possible solution of course is to make the minifcation step part of your automated build procedure (you’re using an automated build procedure right?). That way if you need to do anything, you don’t just start hacking at the production code. Instead you work with the development copy and build it into a new production copy. This also works well if your are using a version control system ‘properly’:

    Branch » fix » build » test » merge.

    I like to think of minified JavaScript as I think of compiled binary executables and libraries. It’s not meant to be human-readable. If I want to change a binary then I edit the source code and rebuild/recompile to create a new binary. It’s the same with minified js.

  5. The if..elseif example could be reduced further from 133 to 95 bytes by replacing the elseifs with a ternary structure:

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

  6. I like the content of this article and believe that squeezing every available byte out your page has many benefits across the board. With the feature-rich online applications these days, some pages can get bloated with necessary JavaScript libraries and associated files. Any reduction in your production environment filesize is a help to your end users.

    We are currently investigating the effect of overall file size on SEO ranking and may use this technique to further optimise file sizes for (hopefully) increased effect.

    Thanks

  7. Maybe I completely misunderstood your point at the end, but are you saying that your refactoring amounted to a 5 byte saving when minified and gzipped? Is that really worth it?

  8. I’m all for best practices, but some of the code changes suggested in the article make the code harder for others to understand and maintain. Not everyone is a javascript ninja like Nicholas. After factoring in the effects of gzip, the savings don’t seem worth the extra maintenance cost.

  9. How much time did it take to refactor the code, in order to save a whopping 5 bytes in the gzipped file?

  10. A couple of people asked if all of this work is worth the 5 bytes saved after gzip. I fear my point in doing that exercise was lost slightly. The real point is that you need to be careful when applying such optimizations that you don’t take an unnecessary hit when gzipped. You can go overboard with doing optimizations and actually end up with a larger file on the wire.

    The example I gave was to show that you could still apply the majority of optimizations and not adversely affect the gzipped size of the response. Note, though, that the disk size (the minified column) is about 1kb smaller. This is especially important because resources are cached as uncompressed files. Mobile devices have very small caches, so minimizing disk size is incredibly important for overall web application performance.

  11. @nicholas
    I have to take issue with your statement:
    “Always keep in mind that anything that appears after a dot in JavaScript (object.property) cannot be minified any further.”

    Maybe the minifier can’t spot it, but as a general statement, it ain’t so.
    One common example is where what’s after the dot is actually an object to which a var can be assigned:

    Real world example this…

    shadow.style.overflow=’hidden’;
    shadow.style.position=’absolute’;
    shadow.style.top=’-5000px’;
    shadow.style.height=’1px’;

    Can be this…

    var x=shadow.style;
    x.overflow=’hidden’;
    x.position=’absolute’;
    x.top=’-5000px’;
    x.height=’1px’;

    Further, if what’s after the dot is a property these too can be minified if they appear more than once – using bracketed notation. Even methods can be minified this way.
    (Whether it’s worth the effort is another matter – but it can be done.)

    Let’s say you had two or three elements to which you are applying a particular property:

    objx.style.minWidth=’200px’;
    objy.style.minWidth=’400px’;
    objz.style.minWidth=’250px’;

    *Could* become this:

    var a=’minWidth’;
    objx.style[a]=’200px’;
    objy.style[a]=’400px’;
    objz.style[a]=’250px’;

    Or, to keep in step with the previous example:

    var a=’minWidth’, b=’style’;
    objx[b][a]=’200px’;
    objy[b][a]=’400px’;
    objz[b][a]=’250px’;

    With methods you can do things like this:

    var t = ‘toLowerCase’;
    if stringx[t]()==stringy[t]() return true;

    or

    var d = document, r=d.getElementById, obj_a=r(‘element-a’),obj_b=r(‘element-b’), obj_c=r(‘element-c’);

    [ In other words, getElementById() is aliased as r() ]

    Lastly: it can’t be stressed enough that the greatest amount of optimization can be had by simply combining scripts into a single file for a single HTTP request. I do a lot of view sourcing and I constantly see multiple script elements that can slow download – especially on the first page view before caching kicks in – that anybody, no matter what their level of knowledge, can easily avoid.
    Once combined, if minified, even better.

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA