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 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.