Mobile Gmail and async script loading
Mobile is the current web frontier where we’ll see the greatest growth in users and companies over the next few years. In case you didn’t see the announcement, Dion Almaer and Ben Galbraith just joined Palm to head up their developer relations program. At this week’s Velocity kick-off meeting, mobile performance was highlighted as a primary focus for next year’s conference.
With mobile on my mind, I was blown away by the awesome performance tips in this blog post: Gmail for Mobile HTML5 Series: Reducing Startup Latency. This post hits on the main point of my recent book – the impact of loading JavaScript. This is the #1 performance issue for today’s web apps. The problem is even worse for mobile devices where download times can be significantly worse than on the desktop.
One of the best practices I evangelize is splitting the initial payload. The Google Mobile team echoes this advice, recommending that fast mobile web apps must separate their code into modules that are critical to page startup versus modules that can be lazy-loaded. It’s important to carefully consider when to lazy-load this additional JavaScript.
One strategy is to lazy load the modules in the background once the home page has been loaded. This approach has some drawbacks. First, JavaScript execution in the browser is single threaded. So while you are loading the modules in the background, the rest of your app becomes non-responsive to user actions while the modules load. Second, it’s very difficult to decide when, and in what order, to load the modules. What if a user tries to access a feature/page you have yet to lazy load in the background? A better strategy is to associate the loading of a module with a user’s action.
Loading JavaScript in the background does indeed freeze the UI and lockout the user. Even worse, they don’t know why this is happening. They didn’t invoke any action – the lazy-load was kicked off in the background. But, I’ve seen web apps adopt this recommendation of loading modules when the user requests the additional functionality, and that’s not pretty either. Waiting for a script to download, especially over mobile connections, injects too much of a delay. But the Google Mobile team found an ingenious workaround:
…we wrote each module into a separate script tag and hid the code inside a comment block (/* */). When the resource first loads, none of the code is parsed since it is commented out. To load a module, find the DOM element for the corresponding script tag, strip out the comment block, and eval() the code.
Your parents were wrong – you can have your cake and eat it, too! Commenting out the JavaScript code avoids locking up the browser, so the actual download can happen in the background without affecting the user experience. Once the code arrives, modules are eval’ed on an as-needed basis, without the delay of actually downloading the script. The eval does take time, but this is minimal and is tied to the user’s actions, so it makes sense from the user’s perspective.
I’ve been working on ways to download scripts asynchronously for two years. I’ve never seen this technique before. It’s very promising, and I hope to explore a few enhancements. It sounds like Google Mobile did the download as an HTML document in an iframe (“each module in a separate script tag”). This means it would have to be downloaded from the same domain as the main page – something common at Google but less typical on other web sites using CDNs or Google AJAX Libraries API, or sharding resources across multiple domains. It would be nice if each module could be a standalone script with the code commented out. This would avoid the same domain restrictions, and be faster since the scripts could be downloaded in parallel.
Hats off to the Google Mobile team. And thanks for sharing!
V1 | 27-Sep-09 at 4:03 am | Permalink |
If browsers vendors hurry up with the canvas implementation we could store JS in to image. And read it out using canvas. This way you could load up scripts async (as images don’t block ) and eval them when needed.
Madhu Jahagirdar | 27-Sep-09 at 9:16 am | Permalink |
Great Post. Kudos to google team.
Marcus Westin | 27-Sep-09 at 2:14 pm | Permalink |
Hi Steve,
using an iframe on the same domain which in turn loads a js file from a foreign domain (in which the code is in a comment block) would probably have the same performance benefit as loading the js directly into an iframe. It would also work for any website despite domain setup.
What do you think?
Clint Hall | 27-Sep-09 at 6:13 pm | Permalink |
This is really cool…
I wonder if it would be faster if you simply set the innerHTML of the script tag to the comment-stripped string, rather than eval? Doesn’t the code get executed in this manner with XHR injection?
James Cape | 27-Sep-09 at 6:51 pm | Permalink |
Clint,
Worth testing out, but I’m thinking if it’s more than a hundred microseconds faster, it’s because the browser’s doing something dumb.
Steve Souders | 27-Sep-09 at 6:51 pm | Permalink |
@V1: I’ve investigated this approach previously. It fires the onerror handler. So we’re relying on browsers caching error images – not a very solid foundation.
@Marcus: It would be easier to create script DOM elements in the main page than via an iframe.
@Clint: Hi, Clint! Setting the innerHTML will work and is probably slightly faster than eval.
V1 | 29-Sep-09 at 3:12 am | Permalink |
@Steve good point.
But one thing i’m wondering about on this technique is why they put the modules inside tags instead of plain HTML comments.
You can read the DOM HTML comments nodes, and inject eval its contents. This will probably speed up the page even more as the browser does not need to load a script tag at all.
(At least if my interpretations are correct on this )
Clint Hall | 30-Sep-09 at 8:29 pm | Permalink |
@James you’re probably right; but I’m operating in blind, best-practices mode.
@V1 ooo… I like that thought! But how would you divide that into “modules” of executable content?
But consider this… move a bunch of commented out, minified script to the bottom of the page? You could add an ID to each script block, grab the content, remove the comment and re-inject it into the block… that would incorporate a majority of this feedback, no?
I have a feeling we’re discussing milliseconds of time, though. It’s a great concept.
V1 | 01-Oct-09 at 2:06 am | Permalink |
@clint you could do something like
And just loop over the nodes. Maybe add prefix in each comments to id them..
eg.
Madhu Jahagirdar | 02-Oct-09 at 11:04 am | Permalink |
I did some analysis on the Microsoft outlook web access and interesting thing is that once the page is loaded user is able to type in username and password etc., but in the background loads of js and images are being dowloaded and the application doesn’t become unresponsive. Is it only when the JS is executing the application becomes unresponsive or even when the js is being parsed ? or is MS using some different techniques ?
serkan | 16-Jan-10 at 5:59 am | Permalink |
Sesli Chat
Sesli Sohbet