Sunday, November 29, 2009

Automatically compress embedded JavaScript resources with Microsoft Ajax Minifier

Recently Microsoft has announced yet another useful addition to an ASP.NET developer tool belt: Microsoft Ajax Minifier, a tool that enables you to reduce the size of a JavaScript file by removing unnecessary content from it. Clearly this is an extremely useful tool and the ASP.NET development community has gladly embraced it (just google it up to see a lot of positive responses). The tool indeed works very well and there have even been a number of articles comparing it to other well-known JavaScript compression tools that proved its quality. So there are no doubts whether to use the Minifier or not.

The question however is how to use it. Microsoft Ajax Minifier package does include a set of documentation that explains how to use the tool and even discusses a couple of usage scenarios. Unfortunately the way I wanted to use the Minifier is not covered in the documentation and for some reason I haven't found useful information on the Internet too so I've decided to do some research and share my findings with the community.

So how would I like to use the Minifier? Here are the requirements:
  1. I want to automatically compress embedded JavaScript resources in any project of my web application solution.
  2. I want the compressed JavaScripts have the original names so I don't have to change the references in HTML mark-up.
  3. I want the compression to be done only when I switch to the Release mode in Visual Studio. When I am in the Debug mode I want all the JavaScript files to be uncompressed for easier debugging.
  4. I want compressed JavaScript files never overrides the original JavaScript files and I don't want to keep the compressed JavaScripts so whenever I modify my JavaScript code the compressed resources are always up-to-date.

I believe that the requirements above describe one of the most common web application solution configuration so if there is a way to achieve them it would be very useful.

Can I achieve the requirements above with the Microsoft Ajax Minifier? The answer is yes. The solution is kind of obvious: since the Microsoft Ajax Minifier includes an MSBuild task I just need to modify a project file where I have embedded JavaScript resources that I need to be compressed to include the Microsoft Ajax Minifier build task. It does not sound complicated and below is the solution which is simple indeed. Just include the following XML in your project file right before the closing </Project> tag (in most cases):

<!-- Minify all JavaScript files that were embedded as resources -->
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" />
<Target Name="MinifyJavaScript" Condition=" '$(ConfigurationName)'=='Release' ">
<Copy SourceFiles="@(EmbeddedResource)" DestinationFolder="$(IntermediateOutputPath)" Condition="'%(Extension)'=='.js'">
<Output TaskParameter="DestinationFiles" ItemName="EmbeddedJavaScriptResource" />
<AjaxMin SourceFiles="@(EmbeddedJavaScriptResource)" SourceExtensionPattern="\.js$" TargetExtension=".js" />
<EmbeddedResource Remove="@(EmbeddedResource)" Condition="'%(Extension)'=='.js'" />
<EmbeddedResource Include="@(EmbeddedJavaScriptResource)" />
<FileWrites Include="@(EmbeddedJavaScriptResource)" />

What this script does is when the solution configuration is 'Release' it finds all the project embedded resource files with the extension '.js' and creates their compressed versions with the same names in the intermediate output folder where they are picked from later by the build process.

A few tips

As you may have noticed the Microsoft Ajax Minifier MSBuild task is referenced from the default folder where it was copied to by the installer. If you want to reference it from a different location, for example if you have a shared development environment and want to have similar setting for everyone, just copy two files ajaxmin.dll and ajaxmintask.dll to another location and include the <UsingTask> tag (below) instead of the <Import> tag in the script above:

<UsingTask TaskName="AjaxMin" AssemblyFile="$(MSBuildProjectDirectory)\..\Build\AjaxMinTask.dll" />

The presented script performs so called 'Normal Crunching' (see the Ajax Minifier documentation) of the JavaScript code which already does a pretty good job that is good enough in most of the cases: compression rate is over 50%. If you would like to turn on the 'Hypercrunching' mode (see the Ajax Minifier documentation) you only need to modify one line of the script to include the 'LocalRenaming' option of the Ajax Minifier:

<AjaxMin SourceFiles="@(EmbeddedJavaScriptResource)" SourceExtensionPattern="\.js$" TargetExtension=".js" LocalRenaming="CrunchAll" />

And the last note is: don't use the default Hypercrunching mode or RemoveUnneededCode option with the ASP.NET AJAX Framework as it does not work properly with it.

Thursday, November 19, 2009

Using the Microsoft Ajax Library 3.5 with the CDN

Recently Microsoft has announced its Microsoft Ajax content delivery network (CDN) which can significantly improve the performance of any ASP.NET AJAX web application. When it was first announced the CDN was almost useless for the current web applications based on ASP.NET 3.5 since it did not host the Microsoft Ajax library 3.5 and did not support content delivery via SSL. However in a very short period of time Microsoft was able to fix those problems (good job!) and now web applications built on ASP.NET 3.5 can benefit from using Microsoft Ajax CDN.

So how do you make MicrosoftAjax.js file being referenced from the CDN as opposed to the embedded file? Quite easy actually with the help of the ScriptManager control. Add the following declaration to a page (or Master page) where there is a ScriptManager control:

<asp:scriptmanager runat="server" enablepartialrendering="false">
    <asp:scriptreference name="MicrosoftAjax.js" path="http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.js" />

What that declaration means is that the script with a name MicrosoftAjax.js which is always automatically referenced by a ScriptManager from the System.Web.Extensions dll now should be referenced from that location: http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.js.

This is how it is referenced by default:

<script src="/ScriptResource.axd?d=eYUqBJhfSVL41hIDYkBL0tfaps9hoQId_48PydfbcyWH41vNvL68sk-l7P9FLAPz7b4vtI8WkZ-ezAF0b_ZkyG52wt9oUtaQ5ezFfGBr7LY1&t=ffffffffef976216" type="text/javascript"></script>

and after we include the new ScriptReference declaration:

<script src="http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.js" type="text/javascript"></script>

ScriptManager is even smart enough to automatically reference the debug version MicrosoftAjax.debug.js from the CDN when debugging is enabled in the web.config file:

<compilation debug="true">

<script src="http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.debug.js" type="text/javascript"></script>

So this is clear: we have our MicrosoftAjax.js referenced from the CDN and that improves our web application performance and saves us and the visitors some bandwidth since an internet browser will reuse the same cached copy of the MicrosoftAjax.js from the CDN for different web applications that reference it.

Using the CDN conditionally

What if we only wanted to reference MicrosoftAjax.js from the CDN when the web application is deployed to a production environment and use an embedded version in a development environment? That would make sense in order for the developers to work without having to be connected to the Internet. Once again it can be done but this time we'll need to write some code. We are going to add the MicrosoftAjax.js script reference dynamically depending on the debug value in the web.config file; we only add the script reference when debug="false":

protected void Page_Load(object sender, EventArgs e)
  if (!Context.IsDebuggingEnabled)
    ScriptManager sm = ScriptManager.GetCurrent(this);
    sm.Scripts.Add(new ScriptReference 
      {Name = "MicrosoftAjax.js", Path = "http://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.js"});

Using the CDN via SSL

Another major case is when our web application has pages that are served via SSL. In this case we want to automatically select the correct CDN URL for the MicrosoftAjax.js. In order to do that we just modify the previous code:

protected void Page_Load(object sender, EventArgs e)
  if (!Context.IsDebuggingEnabled)
    ScriptManager sm = ScriptManager.GetCurrent(this);
    sm.Scripts.Add(new ScriptReference 
       {Name = "MicrosoftAjax.js", Path = Request.Url.Scheme + "://ajax.microsoft.com/ajax/3.5/MicrosoftAjax.js"});

So that's it for now. Enjoy using the Microsoft Ajax CDN.