Mar 202015
 
Article Server Administration

The total page load time is an important factor to be considered in the optimization of a web site. Among the many elements that contribute to the total time required to load a page, one of the most relevant may be the loading and execution of the external javascript code referenced in the page.

This post explains how to load the javascript code asynchronously to decrease the page load time, and the technical considerations involved in the implementation of this improvement.

Analysis of the page load time

There are several online tools available to ascertain the contribution of each of the resources used in a web page to the time it takes to fully load the page. PageSpeed Insights from Google is one of them.

To analyze the page with PageSpeed, just point your browser to https://developers.google.com/speed/pagespeed/insights/, enter the URL to be analysed and click on “Analyze”. After a short while, the results appear differentiated for mobile devices and desktop computers:

pagespeed-report-1

Looking at the desktop report, the first suggestion offered to lower the load time makes reference to the javascript and CSS code that blocks the rendering of the top part of the page visible on the screen.

Clicking on the “Show how to fix” link immediately below, the list of the blocking elements is presented:

render-blocking-js-en

In this screenshot, we can see that the page analyzed first loads the well known jQuery library.

Next, it also loads an ancillary javascript library jquery-migrate. This library is used bywebsites that run the WordPress CMS, to ensure the compatibility with some plugins developed for older versions of jQuery.

Finally, a third javascript library merged2.min.js is loaded. This library contains javascript code specific to the site being analyzed.

The performance penalty in sequentially loading the javascript code

In order to show a page on screen, browsers must download first the HTML code of the page, and then parse it to render the content.

But often, the <head> section at the beginning of the HTML code includes references to external resources, such as javascript libraries:

<html>
  <head>
    ...
    <script type='text/javascript' src='http://.../jquery.js?ver=1.11.1'></script>
    <script type='text/javascript' src='http://.../jquery-migrate.min.js?ver=1.2.1'></script>
    <script type='text/javascript' src='http://.../merged2.min.js?ver=4.4.7'></script>
    ...
</head>
<body>
    ...

Each time a reference to an external javascript file is found , the browser stops processing the HTML code, downloads and executes the javascript file, and only then resumes processing the HTML code. This happens secuentially, as follows:

Download HTML > Process HTML header > Download Javascript > Execute javascript > Process HTML body

Solution 1 – Move references to external javascript immediately above the </body> tag

This fix does now actually decrease the total page load time, but allows the above-the-fold HTML content to be processed and rendered before the javascript code is downloaded:

Download HTML > Process HTML header and body > Download Javascript > Execute javascript

With this fix, the PageSpeed tool will stop reporting the javascript code as render-blocking. However, a dependency issue might happen in some cases: If the HTML code includes some inline javascript code making calls to functions defined in the external libraries, those calls will fail. This issue can be solved as explained below, under “How to solve dependency issues”.

Solution 2 – Use the “defer” attribute in references to external javascript

The “defer” attribute is supported by all major browsers. The references to external javascript would be rewritten as in the following example:

    <script defer src='http://.../jquery.js?ver=1.11.1'></script>
    <script defer src='http://.../jquery-migrate.min.js?ver=1.2.1'></script>
    <script defer src='http://.../merged2.min.js?ver=4.4.7'></script>

In this example, the attribute “type=’text/javascript'” which is not required by HTML5, has been replaced with the attribute “defer”.

Using “defer”, the external javascript code is not executed until all the HTML code has been processed:

Download HTML > Process HTML header > Download Javascript > Process HTML body > Execute javascript

The dependency problem can also happen if some inline javascript code appears inside the HTML code of the page. Other than that, libraries with the “defer” attribute are guaranteed to be executed in the same order as they appear referenced in the HTML code.

Solution 3 – Use the “async” attribute in references to external javascript

If the “async” attribute is added to the reference to external javascript, the processing of the HTML code continues in parallel with the download and execution of the javascript code:

Download HTML > Process HTML header --> Process HTML body
                                    \-> Download Javascript > Execute javascript

This is also true if there are several javascript resources referenced in the HTML code. In our previous example, the download and execution of the three javascript files referenced would be done simultaneously:

Download HTML > Process HTML header --> Process HTML body
                                    |-> Download jquery.js> Execute jquery.js
                                    |-> Download jquery-migrate.min.js > Execute jquery-migrate.min.js
                                    |-> Download merged2.min.js > Execute merged2.min.js

Using the async attribue to download the required resources concurrently can really reduce the total page load time, whilst the previous solutions presented here only fixed the render-blocking issue.

On the other hand, as in the previous solutions, a dependency problem can happen if there is inline javascript code making calls to functions defined in the external javascript files. Moreover, in this case it is not guaranteed that the javascript resources will be executed in the order they appear in the HTML code. Specifically, the browser might execute the code in “jquery-migrate.min.js” before the code in “jquery.js”. But, because “jquery-migrate.min.js” makes calls to functions and objects defined in “jquery.js”, those calls would fail. The next section explains how to fix these dependency issues.

How to solve dependency issues

To illustrate the problem and how to fix it, consider the case of a page that loads the jquery library, containing some inline javascript code that makes a call to jquery:

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>

</head>
<body>

<p id="test1">This is a simple text.</p>

<script>
$(document).ready(function(){
    $("#test1").html("<b>Hello world!</b>");
});
</script>

</body>
</html>

When this page is loaded in a browser, the text “This is a simple text” is briefly displayed, but it is replace almost instantly by the text “Hello world!”

Then, the “async” attribute is added to the reference to the jquery library

<script async src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>

Now, if the page is reloaded in the browser, we’ll see that the text “This is a simple text” remains displayed. The javascript console shows that a javascript error has happened:

ReferenceError: $ is not defined

To fix the problem, we need to place the inline javascript code inside a function “waitForJQuery()”. This function will check first that the jquery library has been loaded. In that case, it will proceed with the execution of the inline code. Otherwise, the function will schedule the execution of itself in 100ms time. This will happen recursively, until the jquery library has been loaded and the jQuery object is available:

<script>

function waitForjQuery() {
    // check that the jQuery object has been defined
    if (typeof jQuery === "undefined") {
        // jQuery not available -> retry in 100ms
        setTimeout(waitForjQuery,100);
        return;
    }

    // jQuery is available -> execute the inline code
    $(document).ready(function(){
        $("#test1").html("<b>Hello world!</b>");
    });
}
waitForjQuery();

</script>

Now, reloading again the page in a browser, we wan verify that the problem is fixed and the dynamically generated text “Hello world!” is displayed again.

Executing “jquery-migrate” after “jquery”.

Ensuring that the ancillary library “jquery-migrate” used by WordPress is executed only after the core jquery library has been loaded and the jquery object is available can be done using the same fix as described above.

First, locate the library in your wordpress installation. In a typical wordpress installation, the library can be found in “/wp-includes/js/jquery/jquery-migrate.min.js”

Now, edit the file to place the code inside a “checkJQuery()” function:

<script>

function checkJQuery() {
    // is the jQuery object available ?
    if (typeof jQuery === "undefined") {
        // not yet. Try again in 100ms
        setTimeout(checkjQuery,100);
        return;
    }

    // jQuery available -> run the original jquery-migrate code
    /*! jQuery Migrate v1.2.1 | (c) 2005, 2013 jQuery Foundation, Inc. and other contributors... */
    jQuery.migrateMute===void 0&&(jQuery.migrateMute=!0),function(e,t,n){function r(n){var r=...
}
checkJQuery();

</script>

Finally, modify the code generated by WordPress to add the “async” attribute to the external javascript resources loaded.

This can be done defining a filter in the “functions.php” of the theme used, as follows:

function async_script_loading($script_tag) {
    return str_replace("<script type='text/javascript' ","<script async ",$script_tag);
}
add_filter('script_loader_tag', 'async_script_loading');

Now we can run again the Google PageSpeed test to verify the impact of the change:

non-render-blocking

And that’s it!

 Posted by at 9:20 am

 Leave a Reply

(required)

(required)