Speeding Up Javascript Downloads

Tuesday, July 22nd, 2008
Looking at different methods of including Javascript in web pages to determine the fastest method for page loading and rendering.
This post was inspired by Non-blocking JavaScript Downloads as well as Steve Sounders Google I/O presentation.

The Test Method

To set these tests up I created 5 php files that each sleep for 2 seconds before returning one Javascript variable to the page. The 2 second sleep time and the tiny file size makes any network or bandwidth variability have a negligible affect on the overall time for the downloading. This isn’t meant to simulate real world, it’s meant to easily illustrate the differences in download time while eliminating network/bandwidth issues as much as possible. Here is an example of one of the php files:
<?
Header("content-type: application/x-javascript");
sleep(2);
// Setting Var for Safari 2 to check when script is loaded
??>
var d1 = true;
On to the tests

Including Javascript in the Header

This is the most common way of including Javascript files in a page:

<head>
<title>Loading scripts in header normally</title>
<script type="text/javascript">
      var start= new Date();
   </script>
<script type="text/javascript" src="delay1.php"></script>
<script type="text/javascript" src="delay2.php"></script>
<script type="text/javascript" src="delay3.php"></script>
<script type="text/javascript" src="delay4.php"></script>
<script type="text/javascript" src="delay5.php"></script>
<script type="text/javascript">
     var end= new Date();
     alert(((end - start) / 1000) + 'seconds');
   </script>
</head>

The file can be viewed and test here. As Stoyan points out loading scripts in the header blocks everything else on the page, including other scripts. So until all the scripts have loaded one by one nothing shows up on the page. In this case the page takes over 10 seconds to load and is blank that entire time.

The advantages of this method is you know the Javascripts will be loaded and ready so they can be referenced in the page, which if inline scripting is used (which it shouldn’t be without a specific reason) it can be required. The disadvantage is that a user is at best just staring at a blank screen while the Javascript is loading, and at worse has already left the site.

Including Javascript at the Bottom of the Page

This is the next most common method of including Javascript, putting it at the bottom of the page:

<script type="text/javascript" src="delay1.php"></script>
<script type="text/javascript" src="delay2.php"></script>
<script type="text/javascript" src="delay3.php"></script>
<script type="text/javascript" src="delay4.php"></script>
<script type="text/javascript" src="delay5.php"></script>
<script type="text/javascript">
	var end= new Date();
	alert(((end - start) / 1000) + 'seconds');
</script>

The file can be viewed and tested here. The Javascript here still blocks everything and loads the scripts one by one, but in this case at least the user can at least see the content of the page while the scripts are loading. This method still takes over 10 seconds to load but at least the page isn’t blank.

The advantages of this method is that the the page doesn’t appear blank while the Javascript is loading, and as the Javascript is loaded before the end of the page events such as the window load can still be used to initiate the scripts. The disadvantage is that no time was shaved off the download time as the scripts are still download one by one while blocking all the other elements.

Dynamically including the Javascripts

This method uses the YUI Get utility to dynamically download the scripts:

<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo/yahoo-min.js&amp;2.5.2/build/get/get-min.js"></script>
<script type="text/javascript">
var js_urls = [
    "delay1.php",
    "delay2.php", 
    "delay3.php", 
    "delay4.php", 
    "delay5.php"
];

YAHOO.util.Get.script(js_urls, { onSuccess: function() {
        var end= new Date();
        alert(((end - start) / 1000) + 'seconds');    
    }
});
</script>

The file can be viewed and tested here. This Javascript no longer blocks everything but the YUI Get utility still loads the scripts one by one. As we now need to download the YUI YAHOO and Get scripts as well this page actually takes the longest of the test pages to load. On a real page where there are images and other items to be loaded this should speed things up as it removes the blocking issue. This method takes over 11 seconds to load due to the additional script files required, but on an actual page it would be more likely to speed the page loading up.

The advantages of this method is that the scripts are no longer blocking so other elements such as images can load at the same time (which wasn’t reflected in this test). The disadvantages are the scripts are still downloaded one by one so there is no time savings there. The other disadvantage is that the scripts may not be loaded when events such as the window load or the Dom Ready events fire, so the scripts can’t be set to start on those events.

Dynamically including Multiple Javascripts at Once

So far this post has matched pretty closely with Stoyan’s, but from the Google I/O conference I remembered that scripts loaded dynamically could be downloaded simultaneously. It turns out that the YUI Get method is the item limiting the scripts downloading to be one after the other. This method uses multiple YUI Gets to download multiple files at once:

<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo/yahoo-min.js&amp;2.5.2/build/get/get-min.js"></script>
<script type="text/javascript">
if (typeof JP == "undefined" || !JP) { var JP = {};}
JP.Loader = function (scripts, config) {
    if ( this instanceof JP.Loader ) {
        this.config = config || {};
        this.scriptsLoaded = '';
        
        var loaded = function(o) {
            this.scripts_loaded++;
            if (this.scripts_loaded == this.len) {
                if (this.config.onSuccess != 'undefined' && this.config.onSuccess) {
                    var sc=this.config.scope || window;
                    this.config.onSuccess.call(sc);
                }
            }
        };
        
        this.scripts_loaded = 0;
        this.len=scripts.length;
        var include_varName = false;
        var varName = this.config.varName || [];
        if (varName.length = this.len) {
            include_varName = true;   
        }
        for ( var i=0; i<this.len; ++i ){
            if (include_varName) {
                YAHOO.util.Get.script(scripts[i], { onSuccess: loaded, scope: this, varName:[varName[i]] });
            } else {
                YAHOO.util.Get.script(scripts[i], { onSuccess: loaded, scope: this });
            }
        }
    } else {
        return new JP.Loader(scripts, loaded_func);
    }
};
test_obj = {
    all_done: function(){
        var end = new Date();
        alert(((end - start) / 1000) + 'seconds');
    }
};

var js_urls = [
    "delay1.php",
    "delay2.php", 
    "delay3.php", 
    "delay4.php", 
    "delay5.php"
];
var var_checks = [
    "d1",
    "d2", 
    "d3", 
    "d4", 
    "d5"
];
var js_scripts = new JP.Loader(js_urls, {onSuccess: test_obj.all_done, scope: test_obj, varName: var_checks});
</script>

The file can be viewed and tested here. This Javascript no longer blocks and multiple scripts are downloaded at a single time. Of course the Javascript to load the actual Javascript is now more complex. This method takes between 2.5 and 8.5 seconds depending on the browser to load script files.

The advantages of this method is that the scripts are no longer blocking and are loaded simultaneously dramatically decreasing download time. The disadvantages are there is no guarentee what order the scripts will finish downloading so none of the scripts can require another script until after they’ve all finished loading. The other disadvantage is the same as above that the scripts may not be initialized when events such as the window load or the Dom Ready events fire, as they may not be done loading yet.

Summary

Javascript Load Time in Seconds

  Firefox Internet Explorer Safari Opera
  2 3 6 7 3 9.5
Javascript in the Header 10.5 10.6 10.7 10.5 10.7 10.7
Javascript in the Footer 10.5 10.5 10.8 10.5 10.7 10.7
Including with YUI Get 10.8 11.5 10.9 11.4 10.9 2.8
Including with Multiple YUI Gets 6.6 2.5 6.9 6.4 4.6 4.6

Using the Multiple YUI Gets is the fastest across the board, except for Opera in which the single YUI Get call is the fastest. I’ll have to do more research to figure out what’s happening there, but it would appear Opera downloads the single Get method simultaneously as well. Internet Explorer and Firefox 2 are a bit slower as they enforce the limit of only 2 items per domain being able to be downloaded at once. On those browsers the scripts are downloading simultaneously but only two at a time, if the scripts were hosted on different domains then it could further speed up the download for IE and Firefox 2.

Which method should be used? It depends. One thing I can say for certain is I wouldn’t use the Javascript in the Header method unless there is a specific reason it absolutely needs to be there. Unless the are a lot of scripts and or large scripts I would tend to use the Javascript in the Footer method as it’s backwards compatible and the user isn’t waiting with a blank page. It’s also straight forward and easy to use. If there are a lot of scripts or page load speed is essential then I’d use the Multiple YUI Gets method.

In the end it really comes down to testing the different methods out on the specific pages and sites and see how much of a difference each makes.

One Single File or bunch of Smaller Files

YUI recently came out with their combined file functionality, which I’m very happy to see. Looking at the chart above I’m curious if it would be faster to include a bunch of individual files at once instead of one large file…

In the Multiple YUI Gets file I load these files (the YAHOO, and Get are already loaded):
http://yui.yahooapis.com/2.5.2/build/utilities/utilities.js
http://yui.yahooapis.com/2.5.2/build/autocomplete/autocomplete-min.js
http://yui.yahooapis.com/2.5.2/build/container/container-min.js
http://yui.yahooapis.com/2.5.2/build/menu/menu-min.js
http://yui.yahooapis.com/2.5.2/build/button/button-min.js
http://yui.yahooapis.com/2.5.2/build/history/history-min.js
http://yui.yahooapis.com/2.5.2/build/json/json-min.js
http://yui.yahooapis.com/2.5.2/build/resize/resize-beta-min.js
 http://yui.yahooapis.com/2.5.2/build/tabview/tabview-min.js

For the combined file I load the same files as above but using YUI’s combo file and include it using the Footer method:
http://yui.yahooapis.com/combo?2.5.2/build/utilities/utilities.js&2.5.2/build/autocomplete/autocomplete-min.js&2.5.2/build/container/container-min.js&2.5.2/build/menu/menu-min.js&2.5.2/build/button/button-min.js&2.5.2/build/history/history-min.js&2.5.2/build/json/json-min.js&2.5.2/build/resize/resize-beta-min.js&2.5.2/build/tabview/tabview-min.js

The conclusion is inconclusive. They are both pretty close in the download time with the combined being quicker sometimes and the individual files being quicker at others. It did seem that often the initial load the individual files were quicker, but then on refreshes the load times were neck and neck or the combined file was faster. As those files are set to be cached on YAHOO’s servers (even though I have my browsers set not to cache) it could be affecting it. Either way this will need more testing to determine if there is a noticable difference in the two methods.

Read other entries about Javascript
Written by James Punteney
blog comments powered by Disqus