Using Async to Avoid Render-Blocking
I came on board at JTech in 2005 and now serve as Vice President of Technology. Although this involves fulfilling a variety of programming and system administration roles, much of my time is spent fine-tuning our custom systems such as JTech's proprietary framework, which powers our advanced websites and gives us the granular control we need for responsive website design, animations and a high level of interaction.
We improve JTech's framework iteratively, with research and development on multiple fronts. In this blog post I'll be talking about how we use asynchronous loading (async) to improve the speed and consistency of website rendering using HTML, JavaScript and CSS.
Render Blocking: Why Async?
When browsers render a website, they must load certain external files sequentially in the order they appear in the code — JavaScript and CSS, for instance — before parsing the rest of the page's HTML and displaying the website to the viewer. We refer to this wait to view the page while waiting for scripts to load as render-blocking, which can add a significant delay for modern websites employing generous amounts of CSS or JavaScript.
Asynchronous JavaScript
HTML5 introduced the async attribute for <script> tags to combat render-blocking. Async specifies that scripts can load asynchronously without causing render-blocking as the browser reads the HTML document — and execute as soon as they’ve finished loading. JTech uses async for all of our external scripts to improve delivery time of the website's layout. Although embedding all JavaScript inline would avoid render blocking, it would create a larger initial load. Pages are perceived as loading faster if the layout appears while the JavaScript is still loading behind the scenes — in our testing, async has been successful because the layout appears quickly and the JavaScript functionality is ready by the time users begin interacting with the site.
Example:
<script src=“js/main.js” async></script>
<script src=“js/main.js” async></script>
Although all modern browsers support async, we still attempt to provide a useful solution for users stuck on obsolete browsers. One option for older browsers is inline JavaScript in the <head> tag to inject another <script> tag that uses async. This forces asynchronous loading even for browsers without explicit support. This is the method we are currently using in the latest version of our framework.
Example:
<script>
var newScript = document.createElement(‘script’);
var firstScript = document.getElementsByTagName(‘script’)[0];
newScript.async = 1;
newScript.src = ‘js/main.js’;
firstScript.parentNode.insertBefore(newScript, firstScript);
</script>
<script>
var newScript = document.createElement(‘script’);
var firstScript = document.getElementsByTagName(‘script’)[0];
newScript.async = 1;
newScript.src = ‘js/main.js’;
firstScript.parentNode.insertBefore(newScript, firstScript);
</script>
Another option is to embed <script> tags just before the ending </body> tag, which provides one of the main benefits of async — browsers are able to render all HTML prior to the script before render-blocking is triggered. This technique can be used in conjunction with async for the JavaScript file, but it's not an appropriate solution for files that define the page's layout; e.g. CSS. Because this method allows the script to be seen last, the browsers may fetch other external files before the script, which may cause a queue to load the script. This works well for obtaining additional resources for display purposes, such as images or fonts, but has the potential to delay functionality offered by JavaScript for a faster display. We have used this method before, but I’ve decided that the potential to delay functionality outweighs delaying an image or font.
Example:
<html>
<body>
…
<script src=“js/main.js” async></script>
</body>
</html>
<html>
<body>
…
<script src=“js/main.js” async></script>
</body>
</html>
Asynchronous CSS
Because our sites’ layouts are dependent on CSS, we need to load it before the content — otherwise the page will "flicker" as it loads and the layout changes. We speed things up quite a bit by embedding our main CSS inline rather than linking to an external style sheet. Although inline CSS can be wasteful if your website has many pages, JTech's websites are 100% AJAX — meaning we're not reloading the HTML and inline CSS as you navigate through the site. Even so, CSS can get quite large, particularly when using inline images to avoid further network requests. Therefore we employ a split solution: most of our CSS is loaded inline with the HTML, while we asynchronously load inline CSS images. The result of loading the main CSS with the HTML and splitting off inline images is that the page's layout loads instantly, followed by the JavaScript and images as they become available.
The <link> tag does not have async support like the <script> tag does, so we use inline JavaScript to inject a link tag with a reference to the CSS file containing our inline images. We use a setTimeout to ensure the link tag is not added until after the first parsing has been accomplished. Otherwise the browser will not async load the css. With some older browsers, most notably IE9, the CSS rule evaluation order cannot be guaranteed and overwriting styles is a risk. For this reason, we keep our external CSS definitions very narrow — using only background-image rather than the more general background selector to avoid potential conflicts and overwriting definitions in background-color or other attributes. IE10 and IE11 and other modern browsers evaluate CSS rules more predictably, which will allow us to make bolder declarations using asynchronously loaded styles in the future.
Example:
<style type=“text/css”>/* inline css */</style>
<script>
setTimeout(function() {
var newLink = document.createElement(‘link’);
var firstStyle = document.getElementsByTagName(‘style’)[0];
l.type = ‘text/css’;
l.rel = ‘stylesheet’;
l.href = ‘css/images.css’;
firstStyle.parentNode.insertBefore(newLink, firstStyle);
}, 0);
</script>
<style type=“text/css”>/* inline css */</style>
<script>
setTimeout(function() {
var newLink = document.createElement(‘link’);
var firstStyle = document.getElementsByTagName(‘style’)[0];
l.type = ‘text/css’;
l.rel = ‘stylesheet’;
l.href = ‘css/images.css’;
firstStyle.parentNode.insertBefore(newLink, firstStyle);
}, 0);
</script>
The Future: Asynchronous Fonts
Fonts are a bit more tricky. Unlike CSS and JavaScript, external fonts are not loaded until the browser encounters that font being used on the page. We can't force font pre-loading without using a JavaScript solution or heavy feature-detection, neither of which are solutions that I'm happy with. In the near future this problem will solve itself as browsers that do not support the woff font format will fade away. In the shorter term, I expect to begin using a hybrid approach with inline woff fonts and external otf/ttf files as a fallback.
Going Forward Asynchronously
Async is now widely supported, and as older browsers go out of circulation we'll be able to provide ever-cleaner and increasingly-efficient page loads while limiting the amount of trickery involved in getting the browser to play nice. As support becomes better, we'll be able to not only deliver pages more quickly, but also more seamlessly — without any render flickering or as many noticeable gaps in the layout or behavior as the page is loaded by the browser. In the present, there are a wide variety of approaches to asynchronous loading. If you have a clever approach or new ideas, we'd love to hear them.
Reference Pages:
Spec: http://www.w3.org/TR/html5/scripting-1.html#attr-script-async
Usage: http://www.w3schools.com/tags/att_script_async.asp
Compatibility: http://caniuse.com/#feat=script-async