So you created that nice website or web application and would like to go a step further in optimizing the front-end loading time? With some great free tools you can easily optimize your javascript to load 2 or 3 times faster if you are willing to trade the regular way of embedding javascript file.

Loading script asynchronously

One thing you need to know is that your script tag block the rendering of your page. In fact, it blocks literally anything from happening, when you are downloading and executing one script, not one css file or one image is downloaded. So imagine you got 10 script files loaded in your head with around 300k in size, well the browser need to load them one by one (well in fact 6 at a time in FF and WebKit, and as many as 18 (or more) in IE8. In older browsers it will be one at a time. ), and execute them one by one, by that time there is a good chance your page would have already render the HTML and CSS.

The first and very easy thing you can do is loading them just before the body end.

<script type="text/javascript" src="/js/jquery/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/js/jquery/jquery.json-2.2.min.js"></script>
<script type="text/javascript" src="/js/jquery/plugins/formvalidator/jquery.validationEngine.js"></script>
 
</body>
</html>

That way, at the very least, the CSS and the html will be loaded before the javascript. One thing really cool about doing this is that the jQuery DOM ready statement becomes obsolete. Your html is already ready because you parsed it before any script tag. That being said, watch out for the global scope, I would still wrap my script in an anonymous self-executing function, like this:

(function () {
 add script here ...
})()

Going one step further with labJS

With LABjs you can load your js files completely asynchronous. The difference with the previous example is that even if your scripts are at the bottom, they are still downloading one by one. With this tool, you can load your scripts simultaneously and specify an executing order. From my tests with dynatrace, the javascript load 3x more rapidly using LABjs, and my page is loading 15% faster (for about 15 js files) on IE8. An example:

<script src='/js/LAB.min.js'></script>
<script>
$LAB
.script("/modules/comment/front/js/comment.js")
.script("/modules/core/commun/js/common.js")
.script("/modules/core/front/js/custom.js")
</script>
 
</body>
</html>

Your loading speed gains comes with a cost of complexity. As I said earlier, it loads them async, so any of them could be executed before another. This is pretty bad if you use a library like jQuery, it needs to be executed first. You have 2 choices here, use .wait() at the end of each script that need to preserve the execution order, or use .setOptions({AlwaysPreserveOrder:true}) that will always preserve the order at a small cost of performance. So if you want to go all gun blazing use .wait(), if not, use AlwaysPreserveOrder (that’s what I do, the speed cost is really minimal). Some examples:

With .wait()

<script src='/js/LAB.min.js'></script>
<script>
$LAB
.script("js/jquery/jquery-1.4.2.min.js").wait()
.script("js/jquery/jquery-sortable-1.7.1.custom.min.js")
.script("js/jquery/jquery.json-2.2.min.js").wait()
.script("js/jquery/common.js")
</script>
</body>
</html>

With AlwaysPreserveOrder

<script src='/js/LAB.min.js'></script>
<script>
$LAB
.setOptions({AlwaysPreserveOrder:true})
.script("js/jquery/jquery-1.4.2.min.js")
.script("js/jquery/jquery-sortable-1.7.1.custom.min.js")
.script("js/jquery/jquery.json-2.2.min.js")
.script("js/jquery/common.js")
</script>
 
</body>
</html>

The full labJS solution

Okay we figured out our execution order, what about our inline scripts? Those scripts block will probably be executed before any js file has been downloaded. Well the solution to this is cool, but not simple. We will create a global variable that will contain all our inline coding. To do that the first thing to do is add a variable to the head.

<script> var _loadingQueue = []; // declare our array</script>
</head>

Now in any part of your page, you could add an inline script block this way:

<script src='/js/LAB.min.js'></script>
<script>
 _loadingQueue.push(function(){
	$("body").html("this is loaded from an inline script block")
 }); 
</script>

As you can see I use jquery in there, _loadingQueue.push will add this anonymous function to our loading queue, you can have as many inline scripts as you want.

In our labjs loading sequence, we need to do a bit of hacking to make this work.

<html>
<head>
<script> var _loadingQueue = []; // declare our array</script>
</head>
<body>
<randomHtmlTags..............>
<script>
 _loadingQueue.push(function(){
	$("body").html("this is loaded from an inline script block")
 }); 
</script>
<randomHtmlTags..............>
 
<script src='/js/LAB.min.js'></script>
<script>
var $LoadDefer = $LAB
.setOptions({AlwaysPreserveOrder:true})
.script("js/jquery/jquery-1.4.2.min.js")
.script("js/jquery/jquery-sortable-1.7.1.custom.min.js")
.script("js/jquery/jquery.json-2.2.min.js")
.script("js/jquery/common.js");
.wait(function(){
      framework.doSomething(); 
   });
 
 
if( typeof( window[ '_loadingQueue' ]) != "undefined"){ 
	for(var i=0,len=_loadingQueue.length; i<len; i++){	 
		$LoadDefer = $LoadDefer.wait(_loadingQueue[i]);	
	}
}
</script>
</body>
</html>

As you can see I have a nice loop now. If the variable _loadingQueue exists, I get the _loadingQueue length and when all my scripts are loaded and executed ( $LoadDefer.wait). I execute all my stored functions. Pretty nice!

This is the full solution to load asynchronously your scripts, I think it will cover 99% of your needs. I added a demo below.

View demo

Combining and minifying your scripts

Something you might not notice in your local development is that file requests are time consuming. Imagine you have 10 js files in your page, if your browser spend 100ms on the network looking for each script, you lose a complete second just looking around the web for those files!

Enter minify

Minify is a nice php library that let you combine your css and js files on the fly and cache them on your server. With this library, you could get back this 1 second.

Minify is easy to install, you download it, put it at your address root, from there you can simple access minified files like this http//www.example.com/min/?f=js/jquery/jquery-1.4.2.min.js,js/jquery/jquery-sortable-1.7.1.custom.min.js. At my workplace, we use minify on pretty much every generic plugins and components that we are using in a given site. This is a good compromise, this way we have about 10 javascript files combined and we can still see errors online from our custom scripts. But you could simply combine all the files for maximum performance, at the cost of losing errors location.

You might get a bit lost in the config, the 2 really important configuration are $min_cachePath and $min_documentRoot. I had to work a bit to get minify working with the Zend framework.

Integrating it with labJS

Once minify works, adding it is not really complicated.

<script src='/js/LAB.min.js'></script>
<script>
var $LoadDefer = $LAB
.setOptions({AlwaysPreserveOrder:true})
.script("/min/?f=js/jquery/jquery-1.4.2.min.js,js/jquery/jquery-sortable-1.7.1.custom.min.js").
.script("js/jquery/common.js")
 
if(_loadingQueue){ for(var i=0,len=_loadingQueue.length; i<len; i++){	 $LoadDefer = $LoadDefer.wait(_queue[i])	}}
</script>

Small warning

I do not use minify to combine my css files, simple because it brakes my images path. If you always use absolute path you will have no problem, but remember that most of your jQuery plugins use relative paths in their css.

Conclusion

Optimizing is not free, it comes with adding a layer of complexity to your application, personally I created a small helper for Zend framework that incorporate everything I mentioned above. From one boolean variable I can decided if I want to minify and embed using labJS. This is really useful for debugging. Also if you are interested in optimizing your js I would strongly recommend you have a look at the api of both LABjs and minify for more options and more detailed instructions.

Some other technologies worth mentioning, YUI compressor as a replacement for the minify library, and requireJS as a replacement of LABjs. requireJS got a different approach, instead of focusing on loading asynch our files, it focus at loading only the files you need to execute a given script in the page.

Small clarification from James Burke : RequireJS also loads files asynchronously. As compared to LABjs, RequireJS encourages writing well-scoped modules, and using its built in optimization tool to combine and minify scripts.

Dynatrace
dynaTrace Ajax
Dynatrace is really the tool for testing front-end performance on internet explorer, you get execution times from everything, cpu usage and much more. It is the most powerful tool I saw on any platform for front-end tracing. The best part? It’s completely free!

20 thoughts on “Optimizing javascript/jQuery loading time, a beginner’s guide

  1. Small clarification, RequireJS also loads files asynchronously. As compared to LABjs, RequireJS encourages writing well-scoped modules, and using its built in optimization tool to combine and minify scripts.

    Disclaimer: I am the author of RequireJS.

  2. As the author of LABjs, I’m quite pleased to see it mentioned here in this article! And the writeup is really well done.

    A few points of clarification:

    1. Adding a .wait() after every single script is pretty much identical in terms of performance to setting AlwaysPreserveOrder:true. Reason: internally, AlwaysPreserveOrder:true tells LABjs to add an implicit .wait() after each .script(). The only real difference then between the two is size of code… if you have a dozen scripts you’re loading, and you put .wait() after each one, that’s ~50 bytes larger than just setting the AlwaysPreserveOrder:true setting. Use either technique at your discretion.

    2. A complicated race condition exists still if you have a chain of $LAB.script(…).script(…) calls that does NOT end in a final .wait(…). The behavior can be intermittently that the final script in your chain downloads but never executes. The simplest solution is to always end a chain with a .wait() call. You can either do so with an empty .wait() or if you have some inline code that needs to run, calling .wait(…) and passing a function in works fine too. NOTE: the “AlwaysPreserveOrder” implicit .wait() is also fine to avoid this issue.

    3. No dynamic loaders, including RequireJS and LABjs, are capable of properly handling loading scripts that have document.write()s in them. So avoid dynamically loading scripts of that nature. jQuery 1.4+ is known to be ok to load in LABjs, but other frameworks (especially older ones) were known to use document.write() for various tasks and so caution should be used on those.

  3. The script tags won’t download one by one, they’ll download in parallel. 6 at a time in FF and WebKit, and as many as 18 (or more) in IE8. In older browsers it will be one at a time. But you are correct in that whether one or several scripts load, all other items (style sheets, images) on the page are blocked.

    Regardless, nice article, and I welcome the effort to encourage the JQuery community to combine their scripts and speed up their load times.

  4. @Kyle, Thx for the clarifications, didn’t knew the document.write limitation

    @Julian, never saw this from Paul before , seems sweet at the first look

    @Mike, thx, I was sure it was one by one, I will update the article

  5. I would like to suggest another tool to minify / compress javascript: http://jscrambler.com

    The team has been working at providing a smaller version of the jquery library : http://jscrambler.com/index/javascript_compressor_results/

    The gziped version is 3.7% smaller than the original gziped version. This can be usefull por people that have sites with loads of traffic and don’t use ore wan’t to use one of the common CDN such as google and microsoft ( http://docs.jquery.com/Downloading_jQuery#CDN_Hosted_jQuery ).

  6. for bassicly its a great tips, but for XHTML language like blogger, other writing in script and placement…
    thanks for tips…^^

  7. Really useful tutorial, this a first time I heard about labJS, maybe I will give it a try.

    Could you merger my comment to the last one? I’ve used it , it works well but maybe can’t work with Cufon 🙁

  8. @Jennifer

    you might want to load cufon in the head if you want your font to load before your text is rendered

    That said, you will still gain a boost by using labjs on your other js files

  9. Hey Cedric, small note about minify relative paths rewriting issue. This can be prevented by adding one line of code somewhere in config.php of minify. I have had problems with this issue too but since i found solution, i use minify for js as well as for css files. ;)))

    // Disallow css uris rewriting
    $min_serveOptions[‘rewriteCssUris’] = false;

  10. I’ve tested your demmo page ang google page speed says ”
    Combine external JavaScript”
    – I think there are better ways to minify and cache js though

  11. Although i dont usual like to comment about law gone bad, oftentimes i know i must. This is one of those times, I cannot stand sub par search engine optimization.

Comments are closed.