Everything about Web and Network Monitoring

Website Performance: Downloading JavaScript

Web­site Per­formance: Tax­o­no­my of Tips in­tro­duced a clas­si­fi­ca­tion scheme to help us or­ga­nize the many per­for­mance tips found on the In­ter­net.    My last ar­ti­cle in­tro­duced the synchronicity ca­t­e­go­ry.  Today’s ar­ti­cle pre­sents one much-discussed group of tips that falls into this ca­t­e­go­ry – downloading JavaScript.

Summary

Client-side script­ing blocks sub­se­quent down­load­ing and ren­der­ing.  Even if the <script> tag fol­lows eve­ry­thing else, it still blocks on­load.  This makes the web­page ap­pear to load more slow­ly be­cause it de­lays the point of in­ter­ac­ti­vi­ty.  As much as pos­si­ble, seri­a­lize the fol­low­ing ac­tions when loading a webpage:

  1. Download and execute the needed-nowscripts.
  2. Wait for onload.
  3. Download and execute the needed-soonscripts.
  4. Wait for execution to complete.
  5. Download and execute the maybe-needed-laterscripts.
  6. Wait for execution to complete.
  7. Download and execute the scripts that are likely to be needed on the next page.

The Problem

In 2009, Steve Souders showed us that old­er brow­sers stop pars­ing when they get to a script.  They down­load the script and ex­e­cute it in its en­tire­ty be­fore con­tinu­ing.  This de­lays the down­load­ing of oth­er scripts, CSS, and eve­ry­thing else.  Page ren­der­ing is com­pletely halted.

New­er brow­sers be­have some­what bett­er, but they’re not per­fect.  Even if we can guar­an­tee that every user is us­ing an up-to-date brow­ser, we still can­not es­cape all ser­i­a­li­za­tion.  This was still an is­sue in No­v­em­ber 2010, when Aaron Peters dis­co­v­er­ed that his web pages were tak­ing up to 140 sec­onds to ful­ly load.  Al­though he had taken steps to deal with the prob­lem, he dis­cover­ed the hard way that third par­ty scripts were load­ing other scripts, and those other scripts were serial­ly blocking onload.

The Solution

Many ar­ti­cles and books have been writ­ten on this sub­ject since 2009 as the tech­ni­cal com­mu­ni­ty ex­plored va­ri­ous so­l­u­tions.  The fol­low­ing steps are based on those discussions.

#1  Se­pa­rate the Java­Scripts in­to lo­gi­cal group­ings and put each group in­to its own file.  Place the files in­to di­rect­o­r­ies in what­ev­er fa­sh­ion makes sense to fu­ture de­ve­lo­pers.  This makes it ea­si­er to find the code when it’s need­ed, which im­proves maintainability.

#2  As de­scribed in Synchronicity, au­to­mate the build pro­cess to make three Java­Script files (needed-now, needed-soon, and maybe-needed-later) for each web page.  The first file con­tains all the needed-now scripts that make the page view­a­ble and un­der­stan­d­able.  The se­cond file con­tains all the needed-soon scripts that will most like­ly be need­ed short­ly af­ter page in­ter­ac­ti­vi­ty has been achieved.  The third file con­tains all the re­main­ing scripts that are used on the current page.

This ap­proach al­lows us to post­pone down­load­ing the lat­ter two groups so in­ter­ac­ti­v­i­ty can be achiev­ed soon­er.  Some peo­ple have re­com­mend­ed that we com­bine all Java­Script to­ge­th­er in­to one da­ta stream, but this down­loads eve­ry­thing be­fore ex­e­cut­ing the first line of code.  [We can’t assume that put­t­ing eve­ry­thing in­to one da­ta stream is bet­t­er or worse than load­ing all com­po­nents asyn­chro­nous­ly.  The on­ly way to know which is bet­ter is by try­ing dif­fe­rent ap­proach­es for each web page and se­lect­ing the ap­proach that pro­vides the best per­for­m­ance.  Mo­ni­tis’ free page load test­er is a han­dy tool to use for these measurements.]

If the cache ex­piry dates of the scripts with­in each file are too dif­fer­ent, that file can be split in­to small­er files, each with its own expiry date.

#3  If the script and the HTML have si­mi­lar ex­pi­ry dates,  in­line the needed-now file (the first of the three files).  If the ex­piry dates are too dis­si­mi­lar, use an ex­ter­nal Java­Script in­stead.  In ei­th­er case, put the script tag im­me­di­ate­ly be­fore the </body> tag.  Ne­v­er use document.write to cre­ate a <script> tag.

#4  Use the tech­nique de­scribed be­low to down­load and ex­e­cute the sec­ond .js file after the page is fully loaded.

#5  Down­load and ex­e­cute the third .js file ser­i­al­ly af­ter the pre­vi­ous one has fi­nished ex­e­cu­t­ing.  This can be ac­com­plished by load­ing the third file as a call­back from the sec­ond file.  Call­backs are de­mon­strat­ed in Nicholas Zakas’ loadScript function.

#6  Now that eve­ry­thing is ta­ken care of for the cur­rent page, down­load the script(s) for the next page(s) the user is like­ly to visit.  Once again, com­plete the needed-now files be­fore the needed-soon files.  This step as­sumes that the files con­tain no top-level code (i.e., functions only).

How to Download and Execute After Onload

This tech­nique can be used to down­load and ex­e­cute Java­Script af­ter the page is ful­ly load­ed:

  <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 on­load event, which oc­curs af­ter the page is ful­ly load­ed, trig­gers the func­tion de­fined on lines 4-9.  The func­tion cre­ates a <script> node and sets its type and src at­tri­butes ap­pro­pri­ate­ly.  When the node is ap­pend­ed to the bo­dy of the doc­u­ment, the named Java­Script file is down­load­ed and executed.

There is one got­cha here, though.  Be­fore call­ing any of the func­tions in the down­load­ed file, we must first en­sure that it has been ful­ly down­load­ed and pro­cessed.  Af­ter all, we can’t call a func­tion that hasn’t been creat­ed yet.  Nicho­las Za­kas shows how to in­cor­po­rate call­backs in­to the above code so that the call­backs are ex­e­cut­ed af­ter the script file is ful­ly load­ed and has com­plet­ed ex­ecu­tion.  His tech­nique can be used to make sure func­tions ex­ist be­fore they are called.  [The code shown above is a KISS teach­ing tool that sim­ply shows the ba­sic con­cept; Nich­o­las Za­kas’ ar­ti­cle pre­sents some­thing more use­ful for real-world de­ve­lop­ment projects.]

One fi­nal note:  Files down­load­ed this way can­not use document.write, so we’ll have to go through the DOM tree in­stead.  Warn­ing:  We must be care­ful not to change the sizes or lo­ca­tions of page ele­ments.  When the lay­out of ele­ments changes, pages ap­pear to jump around while the user is try­ing to in­teract with them.

Async & Defer

The re­la­tive­ly new async and defer at­tri­butes can be used in a <script> tag to ex­e­cute the script asyn­chro­nous­ly now or later.

Using the tech­nique de­scribed above is re­com­mend­ed over the use of async or defer be­cause the stan­dard for these new at­tri­butes says noth­ing about when to down­load them or in­sert them into the DOM tree.  Al­so, us­ing these at­tri­butes 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.

About Warren Gaebel

Warren wrote his first computer program in 1970 (yes, it was Fortran).  He earned his Bachelor of Arts degree from the University of Waterloo and his Bachelor of Computer Science degree at the University of Windsor.  After a few years at IBM, he worked on a Master of Mathematics (Computer Science) degree at the University of Waterloo.  He decided to stay home to take care of his newborn son rather than complete that degree.  That decision cost him his career, but he would gladly make the same decision again. Warren is now retired, but he finds it hard to do nothing, so he writes web performance articles for the Monitor.Us blog.  Life is good!