Lots of people are excited about the new bundling and minification feature in the next version of ASP.NET and MVC. One major drawback I see a lot of people clamoring about is the fact that you cannot conditionally disable bundling or minification when you are in debug mode. Out of the box (and to be clear, I’m referring to the version that ships with MVC 4 beta) it’s impossible to debug your CSS and Javascript.

I expect this will change in the release version, but for now you are forced to create your own custom bundles (something you’d end up doing anyway) and conditionally check if you’re in debug mode to short-circuit the bundling/minification.

Disabling minification while in debug mode

It’s as simple as an #if DEBUG line and creating a transformer that does nothing. For example:

01           protected void Application_Start()
02           {
03               IBundleTransform jsTransformer;
04           #if DEBUG
05               jsTransformer = new NoTransform("text/javascript");
06           #else
07               jstransformer = new JsMinify();
08           #endif
09          
10               var bundle = new Bundle("~/Scripts/js", jsTransformer);
11          
12               bundle.AddFile("~/Scripts/script1.js");
13               bundle.AddFile("~/scripts/script2.js");
14          
15               BundleTable.Bundles.Add(bundle);
16           }

Now when you reference this javascript bundle like <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script> in a view it will render a single bundled and minified script when in release mode, but as a single bundled, non-minified file while in debug mode.

Disabling bundling while in debug mode

The above approach improves this situation, but I don’t think it goes far enough. If I’m going to have multiple source files, I want to debug with the same multiple source files, at least initially. It would get too confusing writing code in a several files and then debugging it in a single monolithic file.

As an experiment to see if it was possible, I ended up building a better bundler that does just what I want: bundles and minifies in release mode, but doesn’t bundle or minify when the build is set to debug.

The entire class is below, explanation to follow:

01           public static class BetterBundler
02           {
03               private static bool _debug;
04               const string CssTemplate = "<link href=\"{0}\" rel=\"stylesheet\" type=\"text/css\" />";
05          
06               public static void Init()
07               {
08           #if DEBUG
09                   _debug = true;
10           #endif
11                   var bundle = new Bundle("~/content/css", new CssMinify());
12          
13                   bundle.AddFile("~/Content/test.css");
14                   bundle.AddFile("~/Content/site.css");
15                   
16                   BundleTable.Bundles.Add(bundle);
17               }
18          
19               public static MvcHtmlString ResolveBundleUrl(string bundleUrl)
20               {
21                   return _debug ? BundledFiles(BundleTable.Bundles.ResolveBundleUrl(bundleUrl)) : UnbundledFiles(bundleUrl);
22               }
23               
24               private static MvcHtmlString BundledFiles(string bundleVirtualPath)
25               {
26                   return new MvcHtmlString(string.Format(CssTemplate, bundleVirtualPath));
27               }
28          
29               private static MvcHtmlString UnbundledFiles(string bundleUrl)
30               {
31                   var bundle = BundleTable.Bundles.GetBundleFor(bundleUrl);
32          
33                   StringBuilder sb = new StringBuilder();
34                   var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);
35          
36                   foreach (var file in bundle.EnumerateFiles(new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundleUrl)))
37                   {
38                       sb.AppendFormat(CssTemplate + Environment.NewLine, urlHelper.Content(ToVirtualPath(file.FullName)));
39                   }
40          
41                   return new MvcHtmlString(sb.ToString());
42               }
43          
44               private static string ToVirtualPath(string physicalPath)
45               {
46                   var relativePath = physicalPath.Replace(HttpContext.Current.Request.ServerVariables["APPL_PHYSICAL_PATH"], "");
47                   return relativePath.Replace("\\", "/").Insert(0, "~/");
48               }
49          
50               public static MvcHtmlString CssBundle(this HtmlHelper helper, string bundleUrl)
51               {
52                   return ResolveBundleUrl(bundleUrl);
53               }
54           }

To summarize, I’m using the same technique to determine debug mode, and of course this could be extended to conditionally bundle or not based on any boolean. The interesting code is in the UnbundledFiles(string bundleUrl) method.

Currently, there is no concept of named bundles – bundles are specified simply by the virtual path of the resultant bundle. This means all our calling code in the view has to give is the virtual path of the bundle. We have to start from that and uncover all the physical files deeper within the BundleTable.

var bundle = BundleTable.Bundles.GetBundleFor(bundleUrl);

This line retrieves the bundle that we created from the BundleTable.

bundle.EnumerateFiles(new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundleUrl))

This gets all of the physical files from the bundle.

The rest is just boilerplate code to turn those raw physical files back into relative virtual paths and into the proper html tags.

Finally, you’ll note that I have an HtmlHelper method in there, CssBundle(this HtmlHelper helper, string bundleUrl). To render a bundle link in a view, this must be used. Since the result of a bundle could be one or multiple files, I decided the simplest approach would be to allow the BetterBundler to render the full html tag itself. This could easily be changed or enhanced.

1              In the view:
2             
3              @Html.CssBundle("~/content/css")

The Result

In release mode:

<link href="/content/css?v=7GiB-1k9Pr1JbbYY72bT3T2EOpxXf0rGPdEOXVKl5oQ1" rel="stylesheet" type="text/css" />

In debug mode:

<link href="/content/test.css" rel="stylesheet" type="text/css" />
<link href="/content/site.css" rel="stylesheet" type="text/css" />