Home > ASP.NET MVC, CSharp, JavaScript, Performance Optimization > Performance tips – minification and deferred JavaScript loading

Performance tips – minification and deferred JavaScript loading

In the past few years a lot of research has been done on page load times and where the hot spots occur. Surprisingly around 90% of the page load time is spent inside the browser. Why could this be happenning and what can be done in order to reduce these numbers?

How browsers work?

When a page is requested it is downloaded, parsed and rendered. During the rendering phase a lot is going on behind the scenes – static resources referenced by the page are downloaded, parsed and executed (in case of CSS and JavaScript). These static resources require additional HTTP requests which might dramatically slow down the loading of the page. Browsers have a limit of requests that could be performed in parallel, hence the resources are downloaded somewhat sequentially. The numbers differ from browser to browser, but in general two image files could be downloaded in parallel from a single domain, only a single JavaScript file could be downloaded at a time which also blocks the rendering of the page.

JavaScript hot spots

In this article the subject of interest are the bottlenecks that arise from requesting JavaScript files. There are different steps that could be taken to tune the page load time that is influenced by script files – minification and compression, combining all scripts in a single file (this is the recommended approach for CSS as well). There are several tools that are available for this purpose – JSMin and YUI Compressor (there is a .NET version that could be used in a MSBuild task) are some of them. You could make significant performance gains by minimizing and compressing – that could really be felt if your users are on a slow internet connection. By combining your scripts the browser will make only a single HTTP request which results in faster load times.

I will show you how to use YUI Compressor for .NET to combine and minify JavaScript files with MSBuild. To get started you need to copy Yahoo.Yui.Compressor.dll and EcmaScript.NET.modified.dll assemblies to your project. Create a so called project file (XML file that is constructed according to the MSBuild XML Schema definition) that is used to instruct the build engine what to do. I will not get into the details because MSBuild is not the subject of this post. Here is the project file:

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/MsBuild/2003">
  <UsingTask
      TaskName="CompressorTask"
      AssemblyFile="Yahoo.Yui.Compressor.dll" />

  <Target Name="MyTaskTarget">
    <ItemGroup>
      <JavaScriptFiles Include="..\Scripts\jquery-1.3.2.js"/>
      <JavaScriptFiles Include="..\Scripts\MicrosoftAjax.js"/>
      <JavaScriptFiles Include="..\Scripts\MicrosoftMvcAjax.js"/>
    </ItemGroup>
    <CompressorTask
        CssFiles="@(CssFiles)"
        DeleteCssFiles="false"
        CssOutputFile="$(CssOutputFile)"
        CssCompressionType="YuiStockCompression"
        JavaScriptFiles="@(JavaScriptFiles)"
        ObfuscateJavaScript="True"
        PreserveAllSemicolons="False"
        DisableOptimizations="Nope"
        EncodingType="Default"
        DeleteJavaScriptFiles="false"
        LineBreakPosition="-1"
        JavaScriptOutputFile="$(JavaScriptOutputFile)"
        LoggingType="ALittleBit"
        ThreadCulture="en-us"
        IsEvalIgnored="false"
            />
  </Target>
</Project>

The important part is the ItemGroup element in which you specify the scripts that will be acted upon by YUI Compressor. Next you should edit the Build event script in order to run the MSBuild task. Right-click the project and choose Properties, then select Build Events. In the Post-build event command line enter the following script:

$(MSBuildBinPath)\msbuild.exe "$(ProjectDir)MSBuild\MSBuildXML.xml" /p:JavaScriptOutputFile="$(TargetDir)..\Scripts\JavaScriptFinal.js"
Project Properties - Build Events

Project Properties - Build Events

Now when you build your project your JavaScript files would be combined and minified – the output can be seen in this screenshot:

MSBuild output

MSBuild output

Another important aspect is where exactly in the page are the references for the JavaScript files. You should put them in the head section of the HTML document only if the scripts are essential for the page rendering phase (generation of HTML), otherwise you should put them at the end of the page to prevent blocking of the rendering. If you know that your script won’t be needed until a specific time on your page you could defer its loading. This way the browser won’t make a request for it and you will improve the page load time. It is really easy to accomplish – all you need to do is to attach an event handler to the onload event and download the file after a specified amount of time. Here is a sample HTML extension method for ASP.NET MVC that I wrote but this could be easily rewritten for ASP.NET:

private static string RegisterDeferredLoader(this HtmlHelper helper)
{
    HttpContextBase context = helper.ViewContext.HttpContext;
    if (context.Items.Contains("__DeferredLoader"))
        return "";

    context.Items.Add("__DeferredLoader", "__DeferredLoader");

    StringBuilder strBuilder = new StringBuilder();
    strBuilder.AppendLine("<script type=\"text/javascript\">");
    strBuilder.AppendLine("function loadScript(file){");
    strBuilder.AppendLine("var script = document.createElement('script');");
    strBuilder.AppendLine("script.type = \"text/javascript\";");
    strBuilder.AppendLine("script.src = file;");
    strBuilder.AppendLine("document.body.appendChild(script);");
    strBuilder.AppendLine("};");
    strBuilder.AppendLine("</script>");

    return strBuilder.ToString();
}

This private helper method outputs a JavaScript function on the page that initiates the request for the script file itself – it simply creates a SCRIPT DOM element and sets its src attribute to the URI of the file. When it is appended to the document the script file is downloaded automatically. The method also ensures that the JavaScript function is written only once in the HTML document – we do not want it multiple times which increases the overall size of the document that would be transferred.

Here is the extension method that would be used to defer the loading of scripts on the page:

public static string RegisterScript(this HtmlHelper helper, string resourceUri, int delayInMilliseconds)
{
    HttpContextBase context = helper.ViewContext.HttpContext;
    if (context.Items.Contains(resourceUri))
        return "";

    context.Items.Add(resourceUri, resourceUri);
    StringBuilder strBuilder = new StringBuilder();
    strBuilder.AppendLine(helper.RegisterDeferredLoader());

    strBuilder.AppendLine("<script type=\"text/javascript\">");
    strBuilder.AppendFormat("if (window.addEventListener) window.addEventListener('load', function(){{window.setTimeout(function(){{loadScript(\"{0}\");}}, {1});}}, false);", resourceUri, delayInMilliseconds);
    strBuilder.AppendFormat("else if (window.attachEvent) window.attachEvent('onload', function(){{window.setTimeout(function(){{loadScript(\"{0}\");}}, {1});}});", resourceUri, delayInMilliseconds);
    strBuilder.AppendLine("</script>");

    return strBuilder.ToString();
}

This method outputs JavaScript code that attaches an event handler to the onload event of the window in which the loadScript function is called after a specified number of milliseconds. As you can see all of this is accomplished with a minimum amount of code and there is no complex logic. In order to use the extension method in your pages you should add its namespace to the pages/namespaces element inside the web.config file:

<pages>
    ...
    <namespaces>
        ...
        <add namespace="JSOptimizations.Helpers"/>
    </namespaces>
</pages>

What follows is a simple test of the extension method. This is a very simple JavaScript file that renders the time at which it is executed:

document.body.innerHTML += 'I am now loaded at ' + new Date();

And the HTML for the page is as follows:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>DeferLoading</title>
    <%= Html.RegisterScript("../../Scripts/DeferLoading.js", 5000) %>
</head>
<body>
    <script type="text/javascript">
        document.body.innerHTML += 'Page is rendered at: ' + new Date() + '<br/>';
    </script>
</body>
</html>

When you open it you would see something similar to the screenshot:

Deferred Loading of JavaScript

Deferred Loading of JavaScript

As you can see the JavaScript file is loaded exactly 5 seconds after the page is rendered.

You could download the sample test project with both the MSBuild task and the extension methods from here.

Summary

Simply by utilizing some techniques for loading JavaScript files you could dramatically increase the load times of your pages. Both minifying and combining several files into a single one can save significant amount of bandwidth. Deferred loading allows you to download scripts after the page has loaded thus reducing the network round trips to the server while rendering the page. There is no single golden rule that will help you achieve the best performance but when you use different techniques for your specific scenario you could really make great improvements.

Advertisements
  1. Anonymous
    December 10, 2009 at 10:10 pm

    Brilliant. Simple, tight and consistent.

  2. Tisho
    December 14, 2009 at 12:28 am

    Good article! Next one – automatic generation of CSS Sprites in ASP.NET MVC 😉 as images probably make 90% of the static resources loaded in browsers.

  3. cypressx
    December 15, 2009 at 12:05 am

    Hi, great article indeed!
    One thing coming up to my mind is that you can use the Microsoft CDN to reference the jquery for instance ( http://weblogs.asp.net/scottgu/archive/2009/09/15/announcing-the-microsoft-ajax-cdn.aspx ). This can help you boost your site loading time, too.

    Keep up the great work!

    • December 15, 2009 at 9:28 am

      Yes, that’s true. Microsoft recently announced that their CDN is already SSL enabled 🙂

  4. August 7, 2011 at 2:18 pm

    very helpful post. thank you for sharing.

  1. December 16, 2009 at 4:16 am
  2. July 5, 2011 at 4:35 pm
  3. November 30, 2012 at 2:25 am

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: