Non-blocking JavaScript Downloads

July 22, 2008 at 10:41 am by Stoyan Stefanov | In Development, Performance |

Stoyan Stefanov.About the Author: Stoyan Stefanov is a Yahoo! web developer working for the Exceptional Performance team and leading the development of the YSlow performance tool. He also an open-source contributor, conference speaker and technical writer: his latest book is called Object-Oriented JavaScript.

External JavaScript files block downloads and hurt your page performance, but there is an easy way to work around this problem: use dynamic scripts tags and load scripts in parallel, improving the page loading speed and the user experience.

The problem: scripts block downloads

Let’s first take a look at what the problem is with the script downloads. The thing is that before fully downloading and parsing a script, the browser can’t tell what’s in it. It may contain document.write() calls which modify the DOM tree or it may even contain location.href and send the user to a whole new page. If that happens, any components downloaded from the previous page may never be needed. In order to avoid potentially useless downloads, browsers first download, parse and execute each script before moving on with the queue of other components waiting to be downloaded. As a result, any script on your page blocks the download process and that has a negative impact on your page loading speed.

Here’s how the timeline looks like when downloading a slow JavaScript file (exaggerated to take 1 second). The script download (the third row in the image) blocks the two-by-two parallel downloads of the images that follow after the script:

Timeline - Blocking behavior of JavaScript files

Here’s the example to test yourself.

Problem 2: number of downloads per hostname

Another thing to note in the timeline above is how the images following the script are downloaded two-by-two. This is because of the restriction of how many components can be downloaded in parallel. In IE <= 7 and Firefox 2, it’s two components at a time (following the HTTP 1.1 specs), but both IE8 and FF3 increase the default to 6.

You can work around this limitation by using multiple domains to host your components, because the restriction is two components per hostname. For more information of this topic check the article “Maximizing Parallel Downloads in the Carpool Lane” by Tenni Theurer.

The important thing to note is that JavaScripts block downloads across all hostnames. In fact, in the example timeline above, the script is hosted on a different domain than the images, but it still blocks them.

Scripts at the bottom to improve user experience

As Yahoo!’s Performance rules advise, you should put the scripts at the bottom of the page, towards the closing </body> tag. This doesn’t really make the page load faster (the script still has to load), but helps with the progressive rendering of the page. The user perception is that the page is faster when they can see a visual feedback that there is progress.

Non-blocking scripts

It turns out that there is an easy solution to the download blocking problem: include scripts dynamically via DOM methods. How do you do that? Simply create a new <script> element and append it to the <head>:

var js = document.createElement('script');
js.src = 'myscript.js';
var head = document.getElementsByTagName('head')[0];
head.appendChild(js);

Here’s the same test from above, modified to use the script node technique. Note that the third row in the image takes just as long to download, but the other resources on the page are loading simultaneously:

Non-blocking JavaScript timeline

Test example

As you can see the script file no longer blocks the downloads and the browser starts fetching the other components in parallel. And the overall response time is cut in half.

Dependencies

A problem with including scripts dynamically would be satisfying the dependencies. Imagine you’re downloading 3 scripts and three.js requires a function from one.js. How do you make sure this works?

Well, the simplest thing is to have only one file, this way not only avoiding the problem, but also improving performance by minimizing the number of HTTP requests (performance rule #1).

If you do need several files though, you can attach a listener to the script’s onload event (this will work in Firefox) and the onreadystatechange event (this will work in IE). Here’s a blog post that shows you how to do this. To be fully cross-browser compliant, you can do something else instead: just include a variable at the bottom of every script, as to signal “I’m ready”. This variable may very well be an array with elements for every script already included.

Using YUI Get utility

The YUI Get Utility makes it easy for you to use script includes. For example if you want to load 3 files, one.js, two.js and three.js, you can simply do:

var urls = ['one.js', 'two.js', 'three.js'];
YAHOO.util.Get.script(urls);

YUI Get also helps you with satisfying dependencies, by loading the scripts in order and also by letting you pass an onSuccess callback function which is executed when the last script is done loading. Similarly, you can pass an onFailure function to handle cases where scripts fail to load.

var myHandler = {
    onSuccess: function(){
        alert(':))');
    },
    onFailure: function(){
        alert(':((');
    }
};

var urls = ['1.js', '2.js', '3.js'];
YAHOO.util.Get.script(urls, myHandler);

Again, note that YUI Get will request the scripts in sequence, one after the other. This way you don’t download all the scripts in parallel, but still, the good part is that the scripts are not blocking the rest of the images and the other components on the page. Here’s a good example and tutorial on using YUI Get to load scripts.

YUI Get can also include stylesheets dynamically through the method YAHOO.util.Get.css() [example].

Which brings us to the next question:

And what about stylesheets?

Stylesheets don’t block downloads in IE, but they do in Firefox. Applying the same technique of dynamic inserts solves the problem. You can create dynamic link tags like this:

var h = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.href = 'mycss.css';
link.type = 'text/css';
link.rel = 'stylesheet';
h.appendChild(link);

This will improve the loading time in Firefox significantly, while not affecting the loading time in IE.

Another positive side effect of the dynamic stylesheets (in FF) is that it helps with the progressive rendering. Usually both browsers will wait and show blank screen until the very last piece of stylesheet information is downloaded, and only then they’ll start rendering. This behavior saves them the potential work of re-rendering when new stylesheet rules come down the wire. With dynamic <link>s this is not happening in Firefox, it will render without waiting for all the styles and then re-render once they arrive. IE will behave as usual and wait.

But before you go ahead and implement dynamic <link> tags, consider the violation of the rule of separation of concerns: your page formatting (CSS) will be dependent on behavior (JS). In addition, this problem is going to be addressed in future Firefox versions.

Other ways?

There are other ways to achieve the non-blocking scripts behavior, but they all have their drawbacks.

Method Drawback
Using defer attribute of the script tag IE-only, unreliable even there
Using document.write() to write a script tag
  1. Non-blocking behavior is in IE-only
  2. document.write is not a recommended coding practice
XMLHttpRequest to get the source then execute with eval().
  1. eval() is evil”
  2. same-domain policy restriction
XHR request to get the source, create a new script tag and set its content
  1. more complex
  2. same-domain policy
Load script in an iframe
  1. complex
  2. iframe overhead
  3. same-domain policy

Future

Safari and IE8 are already changing the way scripts are getting loaded. Their idea is to download the scripts in parallel, but execute them in the sequence they’re found on the page. It’s likely that one day this blocking problem will become negligible, because only a few users will be using IE7 or lower and FF3 or lower. Until then, a dynamic script tag is an easy way around the problem.

Summary

  • Scripts block downloads in FF and IE browsers and this makes your pages load slower.
  • An easy solution is to use dynamic <script> tags and prevent blocking.
  • YUI Get Utility makes it easier to do script and style includes and manage dependencies.
  • You can use dynamic <link> tags too, but consider the separation of concerns first.

Share and extend: Bookmark with Yahoo! My Web | Bookmark with del.icio.us | digg it! | reddit!

32 Comments »

RSS feed for comments on this post. TrackBack URI

  1. I’ve been using this method via the YUILoader from quite some time now.

    This also means that the combo-filter that has been talked about here recently might not be a better approach.

    Comment by Eric Ferraiuolo — July 22, 2008 #

  2. Hi,

    I saw Steven Souders discussing much of this at Google I/O (http://sites.google.com/site/io/even-faster-web-sites). Maybe I’m wrong, but shouldn’t he receive some credit, or at least a mention in this post?

    Comment by Todd — July 22, 2008 #

  3. It’s worth noting that you should *only* use the CSS loading technique for CSS that depends on JavaScript functionality. For example, several of the YUI widgets include layout CSS and style CSS - these would be ideal candidates to load in after the initial page load, since they only apply to JavaScript-created widgets.

    Loading some or all of the general site styling CSS with JavaScript will leave users with JavaScript disabled with a partially or completely unstyled site. If you have a performance issue loading your site CSS, there are probably better techniques you can use (minification and/or concatenation) to speed that up without breaking the separation of concerns between CSS and JavaScript.

    Comment by Steve Webster — July 22, 2008 #

  4. Hi Eric, why not use both? I mean, use the DOM include method to include a single script created with the combo handler?

    Comment by Stoyan — July 22, 2008 #

  5. @Eric — Keep in mind that YUI Loader *does* serialize the JS downloads — so if it loads five files, it does so one at a time (although those five loads don’t block other downloads taking place at the same time). The best of all worlds is YUI Loader using the Combo Handler; we’ll try to have that for you in the next release. -Eric

    Comment by Eric Miraglia — July 22, 2008 #

  6. @Steve - very good point about the difference between JS-related/widget CSS vs. general site layout CSS!

    Comment by Stoyan — July 22, 2008 #

  7. @Todd - Steve did great work for Yahoo’s Exceptional Performance team while he was here (he was my colleague on the Exceptional Performance team), and he’s obviously continued that at Google. The non-blocking script technique described in this article was not derived from Steve’s work, and it’s something I’ve been promoting internally at Yahoo for quite awhile. I gather Steve has made the same discovery as he continues his own research in this area, and so were msn.com, Yahoo! China and probably others.

    Comment by Stoyan — July 22, 2008 #

  8. You can also use multiple separate Get calls to download the JavaScript files simultaneously if the download order isn’t important. Depending on the browser that will either download all the files at once or the 2 per domain. Though you are left on your own to determine when they’ve all been downloaded.

    Comment by James — July 22, 2008 #

  9. Hi,

    I see that you don’t specify the type for the script element, like: js.setAttribute(”type”, “text/javascript”). Is this a mandatory condition for breaking the script blocking rule?
    I’ve been using this method of adding script elements into HEAD of some years now, but as far as I know, the js script was still blocking everything else. Do you think that setting the type of the script has anything to do with this?

    Comment by Daniel R — July 22, 2008 #

  10. […] rendering isn’t very good. I can also understand the well-written and interesting discussion of non-blocking Javascript downloads posted by Stoyan Stefanov at the YUI Blog, which clearly explains why and how Javascript can mess […]

    Pingback by Yahoo! Cool thing of the Day » Blog Archive » Off the blocks — July 22, 2008 #

  11. When you say “put scripts on the bottom”, does that go for YUI only or all scripts?
    Thanks!

    Comment by Jessica — July 22, 2008 #

  12. YAHOO.util.Get.script loading order is not reliable in Safari < 3.0.

    Comment by YuppY — July 23, 2008 #

  13. @James: good point!

    @Daniel R: I left out the type attribute for brevity. With and without it should be the same thing - scripts added dynamically with DOM methods will not block

    @Jessica: scripts at the bottom is a Yahoo! performance rule valid for all JavaScripts. More info here: http://developer.yahoo.com/performance/rules.html#js_bottom
    The article above though describes an alternative approach, where you can keep the scripts in the head, but add them with DOM method, not directly as <script…> tags.

    Comment by Stoyan — July 23, 2008 #

  14. […] Stefanov writes about Non-Blocking JavaScript Downloads on the Yahoo User Interface Blog.  Good content all around, but I’d like to add a note on an […]

    Pingback by Cross-Browser Dynamic JavaScript Loading | Danny Thorpe — July 23, 2008 #

  15. The Dojo JavaScript Toolkit allows you to load JS module files in this way (dynamically appended script tags) via the xdomain loader. Another benefit of doing an xdomain Dojo build: it works out the module dependencies correctly.

    In contrast to the YUILoader, Dojo’s xdomain loader loads the script tags as soon as it see the dojo.require call: it does not serially load one, then wait for it to load before loading the other one.

    Plus, the dojo.require functionality allows you to load modules after the page loads, so you can speed up your initial load even faster by just loading a bare minimum of modules first.

    YUI could look at using the Dojo loader to get these benefits. I did a prototype that loaded YUI via the dojo loader.

    Comment by James Burke — July 23, 2008 #

  16. Interesting method.
    I had know only ‘iframe’ method to loads scripts non-blocking.

    The problem with outer scripts is that they usually should be placed in specific place of document, for social bookmarks, for example, or ads. Not sure these scripts will work correctly if append them on the fly to required element.

    Btw, I’ve writen online tool to check how fast your site loads in browsers with page dependencies recursively - Site-Perf.com
    Maybe you’ll find it useful.
    It doesn’t apply script loading blocking - and now your article adds extra work to me :)

    Comment by zuborg — July 24, 2008 #

  17. Does onDOMReady still fire after the js is loaded if you load it using this method?

    Comment by Luke Chambers — July 24, 2008 #

  18. ArcehtypeJS uses both dynamic script and dynamic css loading (event html can be handled that way).

    It’s quite efficient and we are very happy with this system :)

    Comment by temsa — July 25, 2008 #

  19. I worry about using multiple hostnames for images; that would require an extra DNS lookup per name (at least on initial page load) and those tend to be rather expensive.

    Comment by Glen — July 25, 2008 #

  20. @Luke: DOMContentLoaded will fire before the JS, the experiment files are here: http://www.jspatterns.com/javascript-includes-and-domcontentloaded-event/

    @Glen: this is a valid concern. Yahoo’s best practices suggest max 2-4 domains per page, the research is linked to from this article.

    Comment by Stoyan — July 26, 2008 #

  21. You state that the IE-only technique which uses the defer attribute of the script tag is unreliable - can you please elaborate? The attribute seems to fix onDOMReady problems in IE.

    Comment by Andrew — July 27, 2008 #

  22. Do you have a pointer to more information about the defer attribute of the script tag being unreliable in IE?

    Comment by Simon — July 27, 2008 #

  23. Simon, Andrew, what I meant about defer is that using this attribute is not a guarantee that the script will wait, it depends on what other components are on the page. I’ve experimented in the past and found that for example if you have a non-deferred script that’s taking a while, the deferred might be executed meanwhile.

    Comment by Stoyan — July 28, 2008 #

  24. […] the YUI blog had a great post on how javascript blocks downloads and makes the page take longer to load. This is because any JS file can modify the DOM and so the […]

    Pingback by Stefan Hayden » Javascript Event onJSReady Fires When All JS Files Have Loaded — July 29, 2008 #

  25. Nicely presented but definately not the first time I’ve seen this technique…. Lots of JS libraries have similar functionality, some for several years now.

    With all due respect perhaps you should check for existing solutions first and then spend your free time fixing problems that have no good solution as yet?

    Comment by Pingu — July 31, 2008 #

  26. […] Stefanov en parle lui aussi, en […]

    Pingback by Performance web » Javascript non bloquant — August 4, 2008 #

  27. Take it to the extreme!!! Load an empty html, start imediately loading js (break it in several files), css AND the remaining html, all in parallel ( you can load more than 2 in parallel, using subdomains, the limit is 2/url, studies show that optimal is 4(subdomains)x2 ).

    Comment by mike river — August 6, 2008 #

  28. […] http://yuiblog.com/blog/2008/07/22/non-blocking-scripts/ […]

    Pingback by speeding up page loads « null pointers … — August 7, 2008 #

  29. how do i install javascript on my computer

    Comment by sherita — August 7, 2008 #

  30. […] Stoyan Stefanov is a member of Yahoo’s Exceptional Performance team; he’s worked on a variety of performance-related projects at Yahoo, including the popular YSlow plugin for Firebug. He’s also a contributing author here on YUIBlog. […]

    Pingback by Free Chapter: “Coding and Design Patterns” from Stoyan Stefanov’s Object-Oriented JavaScript » Yahoo! User Interface Blog — September 26, 2008 #

  31. Stoyan, I noticed the code on the 2nd example having some kind of loop. My javascript knowledge is tiny - is this some kind of way of loading multiple js files that’s not covered above? It almost looks like it could loop through a few js files…

    Comment by russell — November 8, 2008 #

  32. Hi Russell, it’s the YUI GET utility that’s doing the loop, you only need to call it passing an array:

    YAHOO.util.Get.script(urls);

    Here urls is an array. You may be confused by the bracket notation used to define an array in JavaScript (called “array literal notation”).

    This:
    var myarray = [1,2,3,4];
    is the same as:

    var myarray = new Array();
    myarray[0] = 1;
    //...
    myarray[3] = 4;

    Comment by Stoyan — November 10, 2008 #

Leave a comment

Note: Comments are moderated for first-timers. Spam deleted.

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Hosted by Yahoo!

Copyright © 2007 Yahoo! Inc. All rights reserved. Privacy Policy - Terms of Service

Powered by WordPress on Yahoo! Web Hosting.