October 2013

Volume 28 Number 10

Cutting Edge - Programming CSS: Bundling and Minification

By Dino Esposito | October 2013

Dino EspositoAn old mantra of Web development says that too many requests affect page performance. If you know a trick to reduce the number of HTTP requests triggered by your Web pages, then by all means, apply it. As Web pages get filled with richer visual content, the cost of downloading related resources such as CSS, scripts and images grows significantly. Surely, for the most part, these resources may be cached locally by the browser, but the initial footprint can be difficult to sustain. In addition, fewer and smaller requests help keep bandwidth low, reduce latency and lengthen battery life. These are critical factors in mobile browsing. A widely accepted approach to address these issues consists of a combination of two actions: bundling and minification.

In this article, I’ll cover bundling and minification of CSS files from the unique perspective of software tooling available in ASP.NET MVC 4. This follows my previous column, “Creating Mobile-Optimized Views in ASP.NET MVC 4, Part 2: Using WURFL” (msdn.microsoft.com/magazine/dn342866).

Basics of Bundling and Minification

Bundling is the process of rolling up a number of distinct resources together into a single downloadable resource. For example, a bundle may consist of multiple JavaScript or CSS files you bring down to the local machine by making a single HTTP request to an ad hoc endpoint. Minification, on the other hand, is a transformation applied to a resource. In particular,  minification is the removal of all unnecessary characters from a text-based resource in a way that doesn’t alter the expected functionality. This means shortening identifiers, aliasing functions, and removing comments, white-space characters and new lines—in general, all characters that are usually added for readability but take up space and don’t really serve any functional purpose.

Bundling and minification can be applied together but remain independent processes. Depending on the need, you can decide to only create bundles or minify individual files. Usually, however, on production sites there are no reasons not to bundle and minify all CSS and JavaScript files—except perhaps common resources such as jQuery that are likely to be on well-known content delivery networks (CDNs). At debug time, though, it’s an entirely different story: A minified or bundled resource is quite hard to read and step through, so you might not want bundling and minification enabled.

Many frameworks provide bundling and minification services with slightly different levels of extensibility and different feature sets. For the most part, they all offer the same capabilities, so picking one over the other is purely a matter of preference. If you’re writing an ASP.NET MVC 4 application, the natural choice for bundling and minification is the Microsoft ASP.NET Web Optimization Framework, available through a NuGet package (bit.ly/1bS8u4B) and shown in Figure 1.

Installing the Microsoft ASP.NET Web Optimization Framework
Figure 1 Installing the Microsoft ASP.NET Web Optimization Framework

Using CSS Bundling

As I see things, the best way to understand the mechanics of CSS bundling is to start from a truly empty ASP.NET MVC project. This means creating a new project using the Empty Project template and removing unused references and files. Next, say you add a layout file that links a couple of CSS files:

<link rel="stylesheet"
  href="@Url.Content("~/content/styles/site.css")"/>

If you display the page and monitor its network activity with Fiddler or Internet Explorer Developer Tools, you see two downloads that go in parallel. This is the default behavior.

Note that in ASP.NET MVC 4 you can rewrite the previous markup in a far more compact way using the new Styles.Render facility:

@Styles.Render(
  "~/content/styles/site.css",
  "~/content/styles/site.more.css")

Located under System.Web.Optimization, the Styles class is far more powerful than it might seem at first. The Styles.Render method also supports bundles. This means the method has a variety of overloads, one of which accepts an array of CSS URLs. Another overload, though, takes the name of a previously created bundle (more on this shortly). In this case, it emits a single <link> element and makes it point to an auto-generated URL that returns all the style sheets as bundled or minified.

Creating CSS Bundles

Typically, you create bundles programmatically in global.asax. In accordance with the ASP.NET MVC 4 pattern for configuration code, you might want to create a BundleConfig class under the App_Start folder and expose a static initialization method out of it:

BundleConfig.RegisterBundles(BundleTable.Bundles);

A CSS bundle is simply a collection of style sheets. Here’s the code you need to group the two aforementioned CSS files in a single download:

public class BundleConfig
{
  public static void RegisterBundles(BundleCollection bundles)
  {
    // Register bundles first
    bundles.Add(new Bundle("~/mycss").Include(
      "~/content/styles/site.css",
      "~/content/styles/site.more.css"));
    BundleTable.EnableOptimizations = true;
  }
}

You create a new Bundle class and pass the constructor the virtual path that will be used to reference the bundle from within a view or Web page. You can also set the virtual path later through the Path property. To associate CSS files with the bundle, you use the Include method. This method takes an array of strings representing virtual paths. You can indicate CSS files explicitly as in the previous example or you can indicate a pattern string as shown here:

bundles.Add(new Bundle("~/mycss")
  .Include("~/content/styles/*.css");

The Bundle class also has an IncludeDirectory method that allows you to indicate the path to a given virtual directory, and possibly a pattern-matching string and a Boolean flag to also enable search on subdirectories.

The EnableOptimization Boolean property you see set on the Bundle­Table class in the preceding code snippet refers to the need to explicitly enable bundling. If not enabled programmatically, bundling just doesn’t work. As mentioned, bundling is a form of optimization; as such, it mostly makes sense when the site is in production. The EnableOptimization property is a convenient way to set up bundling as it should be in production, but you should disable it until the site is compiled in debug mode. You might even go with the following code:

if (!DEBUG)
{
  BundleTable.EnableOptimizations = true;
}

More Advanced Bundling Features

As far as CSS bundles are concerned, there’s not much else that’s relevant except minification. However, the BundleCollection class is a general-purpose class that can be used also for bundling script files. In particular, the BundleCollection class has a couple of features that are worth mentioning even though they’re mostly useful when script files—rather than CSS files—are bundled.

The first feature is ordering. The BundleCollection class has a property named Orderer of type IBundleOrderer. As obvious as it may seem, an orderer is a component responsible for determining the actual order in which you want files to be bundled for download. The default orderer is the DefaultBundleOrderer class. This class bundles files in the order that results from the settings set via FileSetOrderList—a property of BundleCollection. The FileSetOrderList is designed to be a collection of BundleFileSetOrdering classes. Each of these classes defines a pattern for files (for example, jquery-*), and the order in which BundleFileSetOrdering classes are added to FileSetOrderList determines the actual order of files. For example, given the default configuration, all jQuery files are always bundled before Modernizr files.

The impact of the DefaultBundleOrderer class on CSS files is more limited. If you have a file named “reset.css” or “normalize.css” in your Web site, these files are automatically bundled before any of your CSS files, and reset.css always precedes normalize.css. For those not familiar with reset/normalize style sheets, the goal of such style sheets is to provide a standard set of style attributes for all HTML elements so your pages don’t inherit browser-specific settings such as fonts, sizes and margins. While some recommended content exists for both types of CSS files, the actual content is up to you. If you have files with these names in your project, then ASP.NET MVC 4 makes an extra effort to ensure that they’re bundled before anything else. If you want to override the default orderer and ignore predefined bundle file set orderings, you have two options. First, you can create your own orderer that works on a per-bundle basis. Here’s an example that just ignores predefined orderings:

public class PoorManOrderer : IBundleOrderer
{
  public IEnumerable<FileInfo> OrderFiles(
    BundleContext context, IEnumerable<FileInfo> files)
  {
     return files;
  }
}

You use it like so:

var bundle = new Bundle("~/mycss");
bundle.Orderer = new PoorManOrderer();

In addition, you can reset all orderings using the following code:

bundles.ResetAll();

In this case, the effect of using the default orderer or the poor man’s orderer shown earlier is the same. Note, though, that ResetAll also resets script orderings.

The second, more advanced feature is the ignore list. Defined through the IgnoreList property of the BundleCollection class, it defines the pattern-matching strings for files that are selected for inclusion but should be ignored instead. The major benefit of bundles as implemented in ASP.NET MVC 4 is that you can get all JavaScript files (*.js) in the folder in a single call. However, it’s likely *.js also matches files you don’t want to download, such as vsdoc.js files. The default configuration for IgnoreList takes care of most common scenarios while giving you a chance to customize.

CSS Bundling in Action

CSS bundling is a powerful optimization feature, but how does it work in practice? Consider the following code:

var bundle = new Bundle("~/mycss");
bundle.Include("~/content/styles/*.css");           
bundles.Add(bundle);

The corresponding HTTP traffic is shown in Figure 2.

The Second Request Is a Bundle with Multiple CSS Files
Figure 2 The Second Request Is a Bundle with Multiple CSS Files

The first request is for the homepage; the second request doesn’t point to a specific CSS file but refers to a bundle that contains all CSS files in the content/styles folder (see Figure 3).

An Example of Bundled CSS
Figure 3 An Example of Bundled CSS

Adding Minification

The Bundle class is concerned only with packing multiple resources together so they’re captured in a single download and cached.

As you can see in the code in Figure 3, though, the downloaded content is padded with blanks and newline characters for readability purposes. However, browsers aren’t concerned with readability, and for a browser, the CSS code in Figure 3 is perfectly equivalent to the string in the following minified code:

html,body{font-family:'segoe ui';font-size:1.5em;margin:10px}
  html,body{background-color:#111;color:#48d1cc}

For the simple CSS I’m working with in this example, minification is probably not a big deal. However, minified CSS makes a lot of sense for business sites with large style sheets.

How would you add minification? It’s as simple as replacing the Bundle class with StyleBundle. StyleBundle is surprisingly simple.It inherits from Bundle and just consists of a different constructor:

public StyleBundle(string virtualPath)
  : base(virtualPath, new IBundleTransform[] { new CssMinify() })
{
}

The Bundle class has a constructor that accepts a list of IBundleTransform objects. These transforms are applied one after the next to the content. The StyleBundle class just adds the CssMinify transformer. CssMinify is the default minifier for ASP.NET MVC 4 (and newer versions) and is based on the WebGrease optimization tools (webgrease.codeplex.com). Needless to say, if you want to switch to a different minifier, all you need to do is get the class—an implementation of IBundleTransform—and pass it via the constructor of class Bundle.

No More Excuses

The Web Optimization Framework bundled with ASP.NET MVC 4 adds a tiny bit of functionality, but it’s functionality that’s much better to have than not. There’s simply no reason for not minifying and bundling resources. Up until now, a possible excuse was lack of native support in the ASP.NET platform. This is no longer the case with ASP.NET MVC 4 and newer versions.

As far as CSS files are concerned, though, there’s another aspect to consider: dynamic generation of styles. Designed to be a mere skin applied to pages, CSS is turning into a more dynamic resource to the point that some pseudo-programming languages have been introduced to generate CSS programmatically. Next time I’ll be back to cover just that.


Dino Esposito is the author of “Architecting Mobile Solutions for the Enterprise” (Microsoft Press, 2012) and the upcoming “Programming ASP.NET MVC 5” (Microsoft Press). A technical evangelist for the .NET and Android platforms at JetBrains and frequent speaker at industry events worldwide, Esposito shares his vision of software at software2cents.wordpress.com and on Twitter at twitter.com/despos.

Thanks to the following technical expert for reviewing this article: Christopher Bennage (Microsoft)
Christopher Bennage is a developer at Microsoft on the Patterns & Practices team. His job is to discover, collect and encourage practices that bring developers joy. Amongst his recent technical interests are JavaScript and (casual) game development. He blogs at dev.bennage.com.