Asp.Net Bundling, minification and convention

The new “bundling” support in Asp.Net MVC 4 looks massively appealing to .Net web developers – finally, an integrated way to dynamically package up multiple Javascript/CSS files into one request, and minify them on your behalf. Not only that, the results can be cached and reused until the files change. What’s not to like?

Today me and @evadi discovered another little sparkle to this already very useful feature. I’ll highlight this specially to try and get across its importance:

The bundling framework (by default) will automatically favour JS files which end in .min.js.

This means that, when you enable default bundling in global.asax:

BundleTable.Bundles.EnableDefaultBundles();

Then you put some scripts in a folder:

And then you reference the bundle (as-per the new syntax) from your page:

<script src="@System.Web.Optimization.
BundleTable.Bundles.ResolveBundleUrl("~/MyScripts/js")"></script>

Only script.min.js is actually returned in the request; script.js is completely ignored. I tried changing the filename of the former to script-min.js and I found that both scripts were being included in the returned script, so there is definitely some logic going on to try to make use of existing minified scripts if they are available, provided they follow this convention.

Not only that, but files with a debug.js extension are completely ignored.

Controlling this behaviour

To begin with, you can turn this behaviour off by setting the EnableFileExtensionReplacements property on Bundle to false:

Bundle bundle = new Bundle("~/myscripts/js", new JsMinify());
bundle.EnableFileExtensionReplacements = false;

After doing this, all the scripts in the specified folder which match the file mask will be loaded. This flag does not seem to affect files with a debug.js extension, they are still ignored.

You can also have influence over which extensions get treated as “special”. For example’s sake, say your convention is to have all minified Javascript files have the extension of .minified.js, then you can register this extension by adding it to the FileExtensionReplacementList collection on the Bundle table:

BundleTable.Bundles.FileExtensionReplacementList.Add("minified");

It gets a bit sticky now because this opens up the possibility of including multiple minified files, especially if you have a mix of conventions in one directory. So what else can you do?

Build a custom bundle type

You can also create a new Bundle type which inherits from System.Web.Optimization.Bundle and alter the behaviour there. Luckily, a method named EnumerateFiles can be overridden, and a new implementation supplied. As another example, imagine that I wanted to filter out anything that contained the word ‘min’ in order to make sure that my Javascript files were processed by a funky custom minifier that I had just written, and I didn’t want to use files that had already been minified by some other process:

// CustomBundle.cs
public class CustomBundle : Bundle
{
  public CustomBundle(string vpath, IBundleTransform transform)
    : base(vpath, transform)
  {
  }

  public override IEnumerable<System.IO.FileInfo> EnumerateFiles(BundleContext context)
  {
    var files = base.EnumerateFiles(context);

    // select only non-minified files (according to naming convention)
    files = files.Where(c => c.Name.Contains("min") == false).ToList();

    return files;
  }
}

// global.asax.cs
Bundle bundle = new CustomBundle("~/myscripts/js", new JsMinify());
bundle.AddDirectory("~/myscripts", "*.js");
BundleTable.Bundles.Add(bundle);

Unfortunately even at this stage any files which end with debug.js are filtered out for us – so far I haven’t found a way to extend the framework to prevent this behaviour. I’m keeping in mind that this isn’t a final release, and that some work may be done in this area before the final version appears.

In conclusion, this certainly eases my mind when throwing the default bundler at a folder which contains a mix of minified and unminified libraries, knowing that – provided the filenames follow convention – it will pick out the correct one and minify it if necessary. Also, if needed I can provide implementations which can select the files I want to bundle up without having to manually add each individual file manually; for a large project, this can easily become unwieldy.

10 thoughts on “Asp.Net Bundling, minification and convention”

  1. Nice article, Steve.

    My only concern is whether you can specify script bundles in a view, but have them rendered outside the closing tag of the _layout page for best performance.

    Do you know if this is possible?

    1. Thanks Neil.

      You are effectively including the bundled script just like any other Javascript file, so to include the bundle after the body tag, you would actually have to put your script tag there, just like you would any other script.

      If you’re asking if you can include the bundle in any view, but still have it rendered after the body tag, then I’d say it’s not possible.

      1. Thanks Steve.

        Sadly that’s what I thought, but I’ve just had a suggestion to use a custom section outside the closing body tag on the _layout page and each view can use @section to include its required script tags where necessary.

        That should work, right?

        1. Ah yes, of course – that will work. I haven’t used them for such a long time I almost forgot that they existed!

  2. Glad to see you figured out how to use the replacementList functionality, I wasn’t sure we named that property in a discoverable way.

    Also, the files that are ignored by default, come from BundleCollection.IgnoreList which ignores the following by default:

    IgnoreList.Ignore(“*.intellisense.js”);
    IgnoreList.Ignore(“*-vsdoc.js”);
    IgnoreList.Ignore(“*.debug.js”);

    You can call IgnoreList.Clear() to reset this and control it yourself.

    Hope that helps!
    -Hao

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>