(down)Loading JavaScript as strings

December 7, 2009 10:58 am | 13 Comments

The Gmail mobile team and Charles Jolley from SproutCore have recently published some interesting techniques for loading JavaScript in a deferred manner. Anyone building performant web apps is familiar with the pain inflicted when loading JavaScript. These new techniques are great patterns. Let me expand on how they work and the context for using them. FYI – Charles is presenting this technique at tomorrow’s Velocity Online Conference. Check that out if you’re interested in finding out more and asking him questions.

When to defer JavaScript loading

I’ve spent much of the last two years researching and evangelizing techniques for loading scripts without blocking. These techniques address the situation where you need to load external scripts to render the initial page. But not all JavaScript is necessary for loading the initial page. Most Web 2.0 apps include JavaScript that’s only used later in the session, depending on what the user clicks on (dropdown menus, popup DIVs, Ajax actions, etc.). In fact, the Alexa top ten only use 25% of the downloaded JavaScript to load the initial page (see Splitting the Initial Payload).

The performance optimization resulting from this observation is clear – defer the loading of JavaScript that’s not part of initial page rendering. But how?

Deferred loading is certainly achievable using the non-blocking techniques I’ve researched – but my techniques might not be the best choice for this yet-to-be-used JavaScript code. Here’s why: Suppose you have 300K of JavaScript that can be deferred (it’s not used to render the initial page). When you load this script later using my techniques, the UI locks up while the browser parses and executes that 300K of code. We’ve all experienced this in certain web apps. After the web app initially loads, clicking on a link doesn’t do anything. In extreme situations, the browser’s tab icon stops animating. Not a great user experience.

If you’re certain that code is going to be used, then so be it – parse and execute the code when it’s downloaded using my techniques. But in many situations, the user many never exercise all of this deferred code. She might not click on any of the optional features, or she might only use a subset of them.

Is there a way to download this code in a deferred way, without locking up the browser UI?

Deferred loading without locking up the UI

I recently blogged about a great optimization used in mobile Gmail for loading JavaScript in a deferred manner: Mobile Gmail and async script loading. That team was acutely aware of how loading JavaScript in the background locked up mobile browsers. The technique they came up with was to wrap the JavaScript in comments. This allows the code to be downloaded, but avoids the CPU lockup for parsing and execution. Later, when the user clicks on a feature that needs code, a cool dynamic technique is used to extract the code from the comments and eval it.

This technique has many benefits. It gets the download delays out of the way, so the code is already in the client if and when the user needs it. This technique avoids the CPU load for parsing and executing the code – this can be significant given the size of JavaScript payloads in today’s web apps. One downside of this technique results from cross-site scripting restrictions – the commented out code must be in the main page or in an iframe.

This is where Charles Jolley (from the SproutCore team) started his investigation. He wanted a technique that was more flexible and worked across domains. He presents his new technique (along with results from experiments) in two blog posts: Faster Loading Through Eval() and Cut Your JavaScript Load Time 90% with Deferred Evaluation. This new technique is to capture the deferred JavaScript as strings which can be downloaded with negligible parsing time. Later, when the user triggers a feature, the relevant code strings are eval’ed.

His experiment includes three scenarios for loading jQuery:

  • Baseline – load jQuery like normal via script tag.  jQuery is parsed and executed immediately on load.
  • Closure – load jQuery in a closure but don’t actually execute the closure until after the onload event fires.  This essentially means the jQuery code will be parsed but not executed until later.
  • String – load jQuery as a giant string.  After the onload event fires, eval() the string to actually make jQuery ready for use.

The results are promising and somewhat surprising – in a good way. (Note: results for IE are TBD.)

Charles reports two time measurements.

  • The load time (blue) is how long it takes for the onload event to fire. No surprise – avoiding execution (“Closure”) results in a faster load time than normal script loading, and avoiding parsing and execution (“String”) allows the page to load even faster.
  • The interesting and promising stat is the setup time (green) – how long it takes for the deferred code to be fully parsed and executed. The importance of this measurement is to see if using eval has penalties compared to the normal way of loading scripts. It turns out that in WebKit, Firefox, and iPhone there isn’t a significant cost for doing eval. Chrome is a different story and needs further investigation.

These techniques for deferred loading of JavaScript are great additions to have for optimizing web site performance. The results for IE are still to come from Charles, and will be the most important for gauging the applicability of this technique. Charles is presenting this technique at tomorrow’s Velocity Online Conference. I’m hoping he’ll have the IE results to give us the full picture on how this technique performs.

13 Responses to (down)Loading JavaScript as strings

  1. I agree that both the “comment” trick and the “JS variable” trick are good to have in your arsenal.

    But, two caveats to keep in mind with the JavaScript variable version of the trick:

    1. Depending on which string delimiter you use (” or ‘), you’ll have to add extra “weight” to the script for all the \ escaping of that delimiter. I did some tests awhile back and found that on average, my code had anywhere from 3-6% of either ‘ or ” characters in it, so this could be a fairly non-trivial amount of added characters to download over the wire.

    Then, there’s the actual server time involved (either at build-time or load-time) to add all those \ characters in.

    2. To throw it into a JavaScript variable, you probably have to put it into some global JavaScript variable so that it’s available to something else. Or, do a JSON-P approach and call some function with the code as the parameter. In either case, this gets a little messy.

    —-
    The comment technique doesn’t suffer either of those caveats, but it isn’t able to be used cross-domain like the variable trick is.

    As always, whatever you choose, be aware of the properties of each technique to get the best results.

  2. Have you tried experimenting with different techniques to execute the javascript?

    – eval
    – document.createElement(“script”).text
    – (Function(“”))();

  3. In my (still experimental) framework, I use a slightly different technique. It is based on my analysis of how long it takes a user to fully register changes to a page (approx 300ms). In this time it is possible to request and parse the additional JS which may be needed later in the application.

    Hence the app actually makes two JS requests, the first is the normal code required for the initial state, and the second is launched just after the initial state is reached.

  4. Hi,

    Thanks for the mention Steve. I wanted to let you know that I did run the tests in IE8 today. The results are not as good as the others.

    Basically you get no benefit from the deferred evaluation approach, though it is not significantly worse. Statistically, given the high variability of IE, in fact, deferred evaluation is the same as regular baseline.

    There might be some confounding factors here though, so I think this technique needs some more refinement in IE to really work well. Outside of IE, it seems to have some promise however.

  5. The blog post has been updated with results for IE8.

  6. … as Charles mentioned yesterday in his comment – sorry!

  7. I wonder what the performance profile is for including the code in an inline script tag, but setting the script tag’s “type” attribute to something other than “text/javascript”… like “script/cache” for instance.

    This is a takeoff of John Resig’s micro-templating, where he includes an html template in the fake-type tag, but in this case, we actually include real code that we just want ignored for the time being.

    In all browsers I know about and have tested, the code will not be parsed/executed in any way (ignored by the browser), but it is present in the DOM nonetheless.

    So, when you want to execute it, the script element itself exists in the DOM and so you can retrieve the code by accessing the .text property of the script element, and then either eval() the code or do another script-injection.

    NOTE: Not all browsers will download an external resource via src=”…” with the fake type (some do, some don’t), so it couldn’t be used by itself for caching per se. But, it might still be a valid approach for the use-case of inlining code that you can control/defer the execution, like on a mobile site (mobile gmail, etc).

  8. @kyle the downside of that would be that you could not cache it like you could with external files.

  9. @V1 true, some browsers will fetch a resource with the fake mimetype into cache (IE, Safari, Chrome), while others won’t fetch (FF, Opera). So the caching with external resources wouldn’t be reliable cross browser. But that doesn’t mean you couldn’t use it for the browsers that do support it. LABjs does exactly this, in fact (and uses other methods for FF/Opera).

    But, moreover, not all use-cases call for the importance of loading external resources and caching. For instance, the comment-trick, AFAIK, was pioneered by google for their mobile gmail product. For them, the key was not caching or external resources, but simply deferring parsing/execution.

    So, in that spirit, this trick will accomplish that use case. I think it’s something valid to explore for mobile web apps, and may have some limited use cases for regular web apps too.

  10. Great! It’s strange how new techniques on loading javascript emerge almost every single day!!!

  11. Hmmm! Interesting. Perhaps sometimes it’s just better to hit the server instead?

  12. I think you can do something like this:

    Build an onload/onDOMready handler that parses any/all script tags matching our custom MIME type, resets their types to “text/javascript”, and then sets each tag’s data-src as the new src. The custom MIME type prevents any execution until we reset it, and the later setting of the src attribute controls when the resource is downloaded.

  13. Erg, the HTML tags got stripped out of my previous post. My example code was

    <script type=”text/deferred-js” data-src=”/path/to/my.js”></script>