Website Performance: Taxonomy of Tips introduced a classification scheme to help us organize the many performance tips found on the Internet. My last article introduced the synchronicity category. Today’s article presents one much-discussed group of tips that falls into this category – downloading JavaScript.
Summary
Client-side scripting blocks subsequent downloading and rendering. Even if the <script> tag follows everything else, it still blocks onload. This makes the webpage appear to load more slowly because it delays the point of interactivity. As much as possible, serialize the following actions when loading a webpage:
- Download and execute the needed-nowscripts.
- Wait for onload.
- Download and execute the needed-soonscripts.
- Wait for execution to complete.
- Download and execute the maybe-needed-laterscripts.
- Wait for execution to complete.
- Download and execute the scripts that are likely to be needed on the next page.
The Problem
In 2009, Steve Souders showed us that older browsers stop parsing when they get to a script. They download the script and execute it in its entirety before continuing. This delays the downloading of other scripts, CSS, and everything else. Page rendering is completely halted.
Newer browsers behave somewhat better, but they’re not perfect. Even if we can guarantee that every user is using an up-to-date browser, we still cannot escape all serialization. This was still an issue in November 2010, when Aaron Peters discovered that his web pages were taking up to 140 seconds to fully load. Although he had taken steps to deal with the problem, he discovered the hard way that third party scripts were loading other scripts, and those other scripts were serially blocking onload.
The Solution
Many articles and books have been written on this subject since 2009 as the technical community explored various solutions. The following steps are based on those discussions.
#1 Separate the JavaScripts into logical groupings and put each group into its own file. Place the files into directories in whatever fashion makes sense to future developers. This makes it easier to find the code when it’s needed, which improves maintainability.
#2 As described in Synchronicity, automate the build process to make three JavaScript files (needed-now, needed-soon, and maybe-needed-later) for each web page. The first file contains all the needed-now scripts that make the page viewable and understandable. The second file contains all the needed-soon scripts that will most likely be needed shortly after page interactivity has been achieved. The third file contains all the remaining scripts that are used on the current page.
This approach allows us to postpone downloading the latter two groups so interactivity can be achieved sooner. Some people have recommended that we combine all JavaScript together into one data stream, but this downloads everything before executing the first line of code. [We can’t assume that putting everything into one data stream is better or worse than loading all components asynchronously. The only way to know which is better is by trying different approaches for each web page and selecting the approach that provides the best performance. Monitis’ free page load tester is a handy tool to use for these measurements.]
If the cache expiry dates of the scripts within each file are too different, that file can be split into smaller files, each with its own expiry date.
#3 If the script and the HTML have similar expiry dates, inline the needed-now file (the first of the three files). If the expiry dates are too dissimilar, use an external JavaScript instead. In either case, put the script tag immediately before the </body> tag. Never use document.write to create a <script> tag.
#4 Use the technique described below to download and execute the second .js file after the page is fully loaded.
#5 Download and execute the third .js file serially after the previous one has finished executing. This can be accomplished by loading the third file as a callback from the second file. Callbacks are demonstrated in Nicholas Zakas’ loadScript function.
#6 Now that everything is taken care of for the current page, download the script(s) for the next page(s) the user is likely to visit. Once again, complete the needed-now files before the needed-soon files. This step assumes that the files contain no top-level code (i.e., functions only).
How to Download and Execute After Onload
This technique can be used to download and execute JavaScript after the page is fully loaded:
<html><head>...</head><body>
... (the main body of the page) ...
<script type="text/javascript">
window.onload = function(){
var s = document.createElement("script");
s.type = "text/javascript";
s.src = "thispage.needed-soon.js";
document.body.appendChild(s);
};
</script></body></html>
The onload event, which occurs after the page is fully loaded, triggers the function defined on lines 4-9. The function creates a <script> node and sets its type and src attributes appropriately. When the node is appended to the body of the document, the named JavaScript file is downloaded and executed.
There is one gotcha here, though. Before calling any of the functions in the downloaded file, we must first ensure that it has been fully downloaded and processed. After all, we can’t call a function that hasn’t been created yet. Nicholas Zakas shows how to incorporate callbacks into the above code so that the callbacks are executed after the script file is fully loaded and has completed execution. His technique can be used to make sure functions exist before they are called. [The code shown above is a KISS teaching tool that simply shows the basic concept; Nicholas Zakas’ article presents something more useful for real-world development projects.]
One final note: Files downloaded this way cannot use document.write, so we’ll have to go through the DOM tree instead. Warning: We must be careful not to change the sizes or locations of page elements. When the layout of elements changes, pages appear to jump around while the user is trying to interact with them.
Async & Defer
The relatively new async and defer attributes can be used in a <script> tag to execute the script asynchronously now or later.
Using the technique described above is recommended over the use of async or defer because the standard for these new attributes says nothing about when to download them or insert them into the DOM tree. Also, using these attributes will block onload.
For Further Research
For those so inclined, here are a couple of topics that could use a little more investigation:
- Most authors agree that <script> tags should be placed immediately before the </body> tag, but this guarantees serialization because downloading and executing the script will wait until everything else is finished. Is there a way to put the script tag in the head and have the download occur asynchronously with everything that follows, but at a lower priority level? The async attribute comes to mind, but its standard does not say that downloading is asynchronous, only its execution. And it downloads and executes at the same priority level as everything else.
- What is the best way to serialize activities between asynchronous JavaScript code? Tight loops that wait for a global variable to be set are too CPU-intensive. Using a timer is too artificial and is not guaranteed to work. Is there any way to use an operating system semaphore? If not, is there any way to create a semaphoring system within JavaScript?
References
Best Practices for Speeding Up Your Web Site by Yahoo’s Exceptional Performance Team. Published by Yahoo at developer.yahoo.com/performance/rules.html. Accessed 2012.01.13.
The Best Way to Load External JavaScript by Nicholas C. Zakas. Published 2009.07.28 by NCZ Online at nczonline.net/blog/2009/07/28/the-best-way-to-load-external-javascript. Accessed 2012.01.13.
Even Faster Web Sites: Performance Best Practices for Web Developers (ISBN 978-0-596-52230-8) by Steve Souders. Published June 2009 by O’Reilly Media.
Fast Loading JavaScript by Aaron Peters. Published 2011.11.10 by SlideShare.net at slideshare.net/startrender/fast-loading-javascript. Accessed 2012.01.13.
High Performance Web Sites: Essential Knowledge for Front-End Engineers (ISBN 978-0-596-52930-7) by Steve Souders. Published September 2007 by O’Reilly Media.
HTML5: A vocabulary and Associated APIs for HTML and XHTML: The Script Element (Editor’s Draft). Published 2012.01.13 by the World-Wide Web Consortium at dev.w3.org/html5/spec/the-script-element.html. Accessed 2012.01.13.
JavaScript: Defer Execution. Published 2004.01.16 by WebSiteOptimization.com at websiteoptimization.com/speed/tweak/defer. Accessed 2012.01.13.
JavaScript Madness: Dynamic Script Loading by Jan Wolter. Published 2007.03.19 by Unix Papa at unixpapa.com/js/dyna.html. Accessed 2012.01.13.
Loading JavaScript Without Blocking by Nicholas C. Zakas. Published 2009.06.23 by NCZ Online at www.nczonline.net/blog/2009/06/23/loading-javascript-without-blocking. Accessed 2012.01.13.
Loading Scripts Without Blocking by Steve Souders. Published 2009.04.27 by Steve Souders at SteveSouders.com/blog/2009/04/27/loading-scripts-without-blocking. Accessed 2012.01.13.
Monitis Free Page Load Testing Tool. Published by Monitis at pageload.monitis.com. Accessed 2012.01.13.
Monitis Transaction Monitoring. Published by Monitis at portal.monitis.com/index.php/products/transactions-monitoring. Accessed 2012.01.13.
Performance on the Yahoo! Homepage by Nicholas C. Zakas. Published by SlideShare.net at www.slideshare.net/nzakas/performance-yahoohomepage. Accessed 2012.01.13.
Performance Research, Part 4: Maximizing Parallel Downloads in the Carpool Lane by Tenni Theurer and Steve Souders. Published 2007.04.11 by Yahoo at yuiblog.com/blog/2007/04/11/performance-research-part-4. Accessed 2012.01.13.
The Truth About Non-Blocking JavaScript by Nicholas Zakas. Published 2010.12.13 by Planet Performance at calendar.perfplanet.com/2010/the-truth-about-non-blocking-javascript. Accessed 2012.01.13.
Web Performance Best Practices. Published by Google at code.google.com/speed/page-speed/docs/rules_intro.html. Accessed 2012.01.13.
Website Performance: Taxonomy of Tips by Warren Gaebel. Published 2011.12.29 by Monitis at blog.mon.itor.us/2011/12/website-performance-taxonomy-of-tips. Accessed 2012.01.13.
Why Loading Third Party Scripts Async is Not Good Enough by Aaron Peters. Published 2011.11.23 by Aaron Peters at AaronPeters.nl/blog/why-loading-third-party-scripts-async-is-not-good-enough. Accessed 2012.01.13.
Try Monitis For Free. A 15-day free trial. Your opportunity to see how easy it is to use the Monitis cloud-based monitoring system. Credit card not required. Accessed 2012.01.13.
The Monitis Exchange at GitHub. This is the official repository for scripts, plugins, and SDKs that make it a breeze to use the Monitis system to its full potential. Accessed 2012.01.13.







