Script Loading between HTTP/1.1 and HTTP/2

Web pages increasingly suffer from JavaScript-library bloat. Because it is difficult to avoid the awkward wait while these libraries load, there are some techniques for making the loading of these script files less evident to the page-load time. The introduction of HTTP/2 opens up further opportunities to defer script load or do it asynchronously in parallel. Dino explains.

There are more and more JavaScript script files in web pages. These are mostly external libraries and sometimes full-fledged frameworks. Sometimes one wonders if, at the start of a new project someone on the team generally comes up asking “why don’t we just use framework X this time?”, as if the whole point of the project was to try the framework out rather than to build some functionality.

As obvious as it may sound, JavaScript code can make the page a lot more interactive and enjoyable for the end user. From the same operating context, the user can collapse and expand blocks of information, receive timely notification of changes and post or upload information. The inevitable down-side is that too much JavaScript code slows down the page. You open the home page of a popular web site and immediately see some content appear that you want. You immediately scroll down to find just the content you were looking for, in the assumption that it is located a little bit down the page. Puzzlingly, It then takes a while, and the page now seems a bit unresponsive. Most likely, this sluggishness is because the browser is stuck trying to download and initialize the rest of the script required by the page to work properly.

Script code is good to have in web pages but there is such a thing as too much. What’s ‘too much’? There’s no magic answer to that, as far as the number and location of script references is concerned. In this article, I’ll review the various options and offer the pros and cons of each from my own past experience.

What Browsers Do With SCRIPT Elements

The HTTP/1.1 specification recommends that browsers download no more than two components in parallel per host name. This recommendation never applies to script files though. In fact, nearly all browsers download script files one at a time in a synchronous manner. Because of this, the total time it takes to download all script resources is at least the sum of all the individual downloads. Worse yet, the browser stops all work on the page while script resources download. Put another way, every time a SCRIPT element is met while parsing the source code of a web page, the browser stops rendering the page and just waits for the script to download. Next, it executes the script and resumes with page rendering.

As you may know, HTTP/2.0 is coming up and this changes things quite a bit. First and foremost, HTTP/2.0 won’t become the new standard protocol of all web sites overnight. The good news is that the standard is finalized and adoption in the real world will start soon to spread out. Guess what? HTTP/2.0 comes just to fix the shortcomings of HTTP/1.1 and most of these shortcomings relate to downloading external resources and setting up continuous communication between client and server.

In particular, browsers that are loading an HTTP/2 page are encouraged to send several requests in rapid succession, using the same physical connection. The classic request/response pair will cease to exist or, at least, not in the context of the same transaction. Browsers will send a request but won’t wait until a response comes back. This eliminates the need of synchrony and multiple connections between the client and the server. Another key fact in HTTP/2.0 is the ability for the server to push data back to the client without a specific request.

As a result, the advent of HTTP/2.0 will make most of today’s common optimizations in the area of resource handling unnecessary, or even harmful. The Transition to HTTP/2 will take time to arrive, and last for several years. Its introduction will force us, over the coming years, to abandon common practices such as sprites, bundling, minification, reuse of connections. This said, let’s review some of the typical optimizations we do in HTTP/1.1 to improve the usage of external resources.

Placing Scripts at the Bottom of the Page

Why do browsers download script files synchronously, even beyond the recommendation of the HTTP/1.1 standard? They mostly do that to stay on the safe side. What if a downloaded script file includes instructions such as JavaScript immediate functions or calls to document.write that could modify the status of the current DOM? I don’t remember ever having seen, let alone placed, any call to document.write in the past few years; but it certainly can be done. Immediate JavaScript functions are a different matter: they are common these days so, all considered, browsers are right in treating script files synchronously, even though it pauses page-rendering and so slows down the whole process.

What would happen, though, if you place all script references at the bottom of the page, just before the closing </body> element? When you do this, browsers don’t need to interrupt the page-rendering process to load scripts. When browsers attack with the first script download the DOM of the page is mostly ready and displayed. Sure, there’s no guarantee the view is definitive but it’s clearly a valid early view of the page for users to enjoy.

The net effect is that users can get familiar with the contents of the page in its early form while the browser goes ahead and downloads all the remaining script files synchronously. Once downloaded, each script file is given a chance to execute and further edit the running, and visible, DOM.

Top vs. Bottom

It is perfectly legal and valid to place scripts at the bottom of the page but there can be side effects. Consider the following ASP.NET MVC fragment of a Razor view.

The view works beautifully as long as the partial view doesn’t include any jQuery or Bootstrap-specific script. Even something as simple as a modal dialog box or tab strip in the child view (or in the rest of the body) is enough to invalidate the deferred loading of script files.

The point here is that there is a lot more to optimizing for speed of page loading than merely moving a bunch of SCRIPT elements elsewhere in the page. The structure of the page and the process that fills elements out must both reflect the way that you load the scripts.

If there is little impact on the page-load time by referencing script files from the HEAD section at the top of the page then, in my opinion, you have no real need to restructure the page-flow and put scripts at the bottom. After all, with scripts at the top the page is immediately and fully usable once displayed to users. For what it is worth, the vast majority of my Razor views load scripts at the top through references placed in the HEAD section. Not all pages are the same, but placing scripts at the bottom should be the result of a clear attempt to optimize a specific page. In my opinion, it should not be a default approach.

The Defer Attribute

You don’t strictly have to physically move SCRIPT elements around the page to postpone the loading of some script files. You can achieve the same by simply adding an extra attribute to the SCRIPT element-the defer attribute.

Part of HTML4, the defer attribute instructs the browser to defer the processing of the script to the end of the page processing. In the end, the net effect of using defer on a SCRIPT element is the same as placing the script reference near the closing body tag.

Note therefore that any SCRIPT element decorated with the defer attribute implicitly states that the downloaded script is not doing any direct document writing and it’s safe for it to be loaded at the end. At the same time you are, by using the defer attribute, stating to the browser that the script is not setting any state or global definition that may be requested during the rendering of the page.

The Async Attribute

The purpose of the defer attribute is similar to the async attribute you find in the HTML 5 specification, but it is not exactly the same. Let’s find out more about these details and differences.

When defer or async attributes are used, the browser downloads the script file while it is parsing the HTML. This has a consequence that makes it different to just placing script files at the bottom. If files are placed at the bottom, both download and processing occurs when the page is partly rendered. This means that the user can see and read the page but may not be fully able to work with it until all scripts download and are processed. This may be frustrating for users. Using defer (or async in HTML5) at least bas the benefit of absorbing the download time of script files in the overall time it takes to parse the HTML content.

There’s a subtle difference between defer and async. In both cases, scripts are downloaded in parallel with parsing the HTML, but what happens when the script is processed is different. The behavior supported by the HTML5 async attribute is purely asynchronous, meaning that the browser pauses the parser when download completes so as to execute the script and it then resumes with parsing using any new state determined by the script execution.

The effect of the defer attribute, by contrast, is that when parsing is over, the scripts are executed in the same order in which they were listed in the source file.

The Bottom Line

When many options exist, it isn’t easy to answer the question “what would work for me”. It depends on the nature and content of the specific script file. A single technique is unlikely to work for all scripts you may have. So, in general, you should be ready to use defer here and async there or even place SCRIPT elements at the top or the bottom without any further attribute.

Here are a couple of general rules for choosing between defer and async when using the SCRIPT reference approach.

The script is self-contained module and doesn’t have any dependencies whatsoever. In this case, I definitely suggest you use async. This will give you the maximum of parallelism in download and intersperses the script execution with page loading. When the page is rendered to users, it is fully usable. The async attribute works typically with any script you use to support interactive operations within the page, such as button clicks.

The script has dependencies. If the script is relied upon by other scripts, or relies upon other script files, I suggest you go with the defer attribute. This will give you the maximum of parallelism in download and ensure that files are executed in a predictable order to fulfill the expected chain of dependencies you set through determining the order in which SCRIPT elements appear in the page source.

Is it better to use the defer attribute than to place script files at the bottom? I can dare to say ‘yes’ to this: The reason is that defer ensures the maximum of parallelism in download. At the same time, though, you might want to be sure that the browser’s support for the defer attribute is appropriate. In particular, older versions of Internet Explorer (before version 10) had nasty bugs around the implementation of the defer attribute, which defeated the whole purpose of the attribute.

The RequireJS Library

The more you use script libraries, the more you introduce dependencies between scripts. One motivation for adding more libraries is that you just need the functionality of several script libraries. The other reason is that authors of script libraries rely on existing libraries in the implementation of their own features. As a result, this introduces hidden dependencies and makes the graph of required script files more and more complex.

RequireJS is a special framework that attempts at taking over most of the tasks involved with the management of multiple script files. The library works by centralizing script management and takes care of downloading the right files at the right time. You create a single configuration script (usually called main.js) and pass it to the library via a single SCRIPT element.

In the configuration script you find code as below:

The require function takes the name of a JavaScript file in the same folder as the library and executes the function named someMethod on the root object which is expected to be an object defined in the script file.

RequireJS is quite a popular framework that attempts at making JavaScript-intensive programming scenarios just easier to handle. In small contexts, though, it can be more pain than gain. And anyway, like nearly everything else script-related these days, it is useful only if it really helps.

Summary

If, someone had asked me a decade ago to speculate about browser technologies of today, I would have stammered out something about compiled code running within the browser. Ten years ago JavaScript and CSS were dead technologies. I really don’t know what’s coming along in the next decade but I have a strong feeling that script-load-time  is close to be a serious bottleneck for intensive client-side web applications, but it is not much of a problem if you take JavaScript in the way it was originally intended: a simple language to make the content of web pages more interactive. And HTTP/2 is refreshing the protocol to make it more appropriate for what we need today-performance rather than bandwidth.