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
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.
<script src="jquery.min.js" />
<script src="bootstrap.min.js" />
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.
<script src="..." defer="defer"></script>
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.
<script data-main="/content/scripts/main" src="/content/scripts/require.js"></script>
In the configuration script you find code as below: