Combine, minify, compress, and cache bust JavaScript and CSS files with ClientDependency Framework

01 December 2011

With the increased mobile browser usage and search engines rewarding fast loading sites, squeezing extra bytes from the request is becoming increasingly important. It really comes down to two important issues: file size & number of requests. Web developers already employ various techniques to combat this such as image sprites, clean code, and IIS-based compression but we can take it a step further with our CSS & JS files using the ClientDependency Framework.

What is the ClientDependency Framework

The original purpose of ClientDependency was for the Umbraco Backoffice as a means to simplify the usage of <link> or <script> tags used on various controls or pages. With this, they were able to reference the particular dependencies for a single control or page and ClientDependency took care of rolling everything up. It would spit out a single tag per unique file even if a CSS or JS file was referenced more than once in multiple places.

The team took this base feature set several steps further though. ClientDependency is also able to combine and cache all those individual files into as few HTTP requests as possible. It’s smart enough to both leave external CDN references as-is or go out and request code that isn’t local. Output is compressed (IIS is nice enough to not compress it again) and cached as local binary files, typically in the App_Data folder although you can specify this location. This cleverly allows the cached data to be resistant to any app pool reset.

But what about local browser cache? You know that scenario where you’ve made a change and uploaded it but the client can’t see it without a hard refresh or clearing the cache? ClientDependency also implements a standard cache busting mechanism by applying a version number to the end of the file path.

To top it all off, they even added minification by utilizing the very efficient JSMin and CSSMin libraries. In my experience, I’ve had some weird conflicts on certain libraries and it also chokes easily on syntax errors so they aren’t perfect but there are a few easy workarounds: fix your code (ideal), reference the problematic file old school (meh), or turn off this feature via the config (last resort).

And for the incredibly lazy, there is even a catch-all method that handles what they term “rogue” scripts, aka files or JSON that you either didn’t/can’t wrap with special controls.

Installing

The easiest method to include ClientDependency within your existing project is via NuGet.

  1. Open Visual Studio and ensure you have the NuGet Package Manager extension. If you don’t have it, open up Visual Studio’s Extension Manager, search for “NuGet”, and install it.
  2. Fire up your ASP.NET project (WebForms or MVC).
  3. Ensure your Package Manager Console is open (View > Other Windows > Package Manager Console).
  4. Run the following NuGet command: Install-Package ClientDependency (For MVC-based projects, use Install-Package ClientDependency-Mvc)

NOTE: If you’re using Umbraco, it’s even easier because as of version 4.1, ClientDependency ships alongside the Umbraco bits. The modules, handlers, and configuration are already included and referenced correctly.

So…what just happened?

  • A packages.config file was added. This is NuGet package tracking goo and can be ignored.
  • The ClientDependency.Core.dll (and/or ClientDependency.Core.Mvc.dll) was added as a project reference.
  • A handler and module definition was added to the web.config.
  • A special configuration section named clientDependency was added to the end of the web.config. For more information about what these different configuration properties do, refer to the documentation.

At this point, I like to pull out the entire clientDependency element and put it in a separate config file for clarity but that’s up to you. Just use <clientDependency configSource=”FLDR\PATH_TO_CONFIG” />.

It’s a tad complex but the creators have done a terrific job with extensibility points and providers. The immediate config attributes to be concerned with are clientDependency/@version and system.web/complication/@debug.

  • version: Will re-cache any changed files. You can also delete the temporary files in App_Data to force a re-cache as an alternative.
  • debug: Toggling this from false to true either merges all of the dependencies together (debug=”false”) or renders each tag separately (debug=”true”). When you’re coding locally, you’ll definitely want to leave this as true. Occasionally flip it though to identify any syntax conflicts early.

Implementing

Using ClientDependency in Web Forms

  1. Add your page declarative to the ClientDependency.Core assembly and ClientDependency.Core.Controls namespace. Alternatively, you could add this to your web.config under system.web/pages/controls.
  2. Wrap each CSS file with a CssInclude control.
  3. Wrap each JS file with a JsInclude control. Use the Priority attribute to force order files.
  4. Place a ClientDependencyLoader control where you want your <link> & <script> tags to be rendered.
  5. Ensure your project is using IISExpress (we’re running modules and handlers here) and F5 your site.
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="WebForms.SiteMaster" %>
<%@ Register Assembly="ClientDependency.Core" Namespace="ClientDependency.Core.Controls" TagPrefix="cd" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
<title></title>
<cd:ClientDependencyLoader ID="cdLoader" runat="server" />
<cd:CssInclude ID="cssincStyles" runat="server" FilePath="~/Styles/site.css" />
<cd:JsInclude ID="jssincJquery" runat="server" FilePath="~/Scripts/jquery.1.4.1.min.js" Priority="0" />
<cd:JsInclude ID="jssincCustom" runat="server" FilePath="~/Scripts/custom.js" Priority="1" />
<asp:ContentPlaceHolder ID="HeadContent" runat="server"></asp:ContentPlaceHolder>
</head>

But what does it look like on the browser? The multiple files are converted to <link> and <script> tags that include a path to the handler, a hash to represent the files, and a version number.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head><title>Home Page</title>
<link href="/DependencyHandler.axd/acaf610bf78766b61ad7b090b2186d62.1.css" type="text/css" rel="stylesheet"/>
<script src="/DependencyHandler.axd/b0340c2a8c1565a01701c1f8bbeee479.1.js" type="text/javascript"></script>
</head>

Using ClientDependency in Composite Controls

Since there is no HTML design-time support for composite controls, the ClientDependency framework also allows you to attribute define dependencies for the class.

using System.Web.UI;
using ClientDependency.Core;

namespace Controls
{
[ClientDependency(ClientDependencyType.Css, "~/Css/CustomControl.css")]
public class CustomControl : Control
{
}
}

Using ClientDependency in MVC

The process is similar to WebForms but instead of control references, you use Html Helpers. You also need the extra ClientDependency.Core.Mvc.dll and remember, there is a separate NuGet package for the MVC-based flavor (Install-Package ClientDependency-Mvc). Also, if you’re using Razor, output is automatically HTML escaped so we have to wrap the Html.RenderJsHere() and Html.RenderCssHere() with the @MvcHtmlString.Create() helper method.

@using ClientDependency.Core
@using ClientDependency.Core.Mvc
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
@{
Html.RequiresCss("~/Content/Site.css");
Html.RequiresJs("~/Scripts/jquery-1.4.4.min.js", 0);
Html.RequiresJs("~/Scripts/custom.js", 1);
}
@MvcHtmlString.Create(@Html.RenderJsHere())
@MvcHtmlString.Create(@Html.RenderCssHere())
</head>

Testing

So far so good. But what about a real world example of the performance gains? Using FireBug with browser cache disabled, I compared a site with and without ClientDependency enabled.

DISABLED

  • CSS size: 61.8 KB
  • JS size: 225.0 KB (jQuery + Omniture)
  • Total size:  286.6 KB

ENABLED

  • CSS size: 11.3 KB
  • JS size: 149.4 KB (Omniture still excluded)
  • Total size: 160.7 KB

Boom! You’re looking at page weight savings of over 55%.

Summary

The ClientDependency Framework is a dead-simple way for you to combine, minify, compress, server cache, and client cache bust your JavaScript and CSS files. It’s extremely simple to implement and “just works”. With it’s extensibility areas, provider model, and options for WebForms, MVC, and Composite Controls, most of your uses cases are covered.  Even though .NET 4.5 is introducing a similar bundling and minification support baked in, you’ve got current projects to finish and existing ones to support. I love this framework because you can see immediate decreases in page load times and total bytes transferred with minimal effort.

Hope this helps,
Jim

Loading Next Article