Minifying and obfuscating your resources (CSS/JS) is a great practice. It reduces bandwidth and makes it harder for users to look at what your JS is doing. However, you don’t want that in your dev or QA environments where you probably need to look at the JS to debug an issue. And, chances are, staging and/or prod resources get pushed to a CDN so your paths change when you get into the higher environments, and the path to the files, or the CDN hostname, probably changes between the higher environments. Furthermore, Microsoft’s bundling, though not awesomely implemented, is a good idea to reference groups of files that you might need in certain places around your application.

Basically, what you have is the following requirements:

Choose whether to pull minified/obfuscated resources by environment

Choose whether to pull local or CDN resources by environment

If using CDN, provide different CDN paths by environment

Resource bundling

This can be quite laborious to figure out how to get something like this to work. So, let’s get to it.

Configuration

First, let’s get configuration figured out as that will impact everything else. With the wishlist defined above, the configuration class is pretty much defined.

Using Microsoft’s Unity (though you can certainly use another DI), you can configure the configuration using DI and design time configuration, meaning you set this all up in your application’s web.config. Then, using web.config transformations, you can change the values on an environmental basis.

As is best practices, and to get this working with Unity, let’s define our resource configuration interface.

The first three properties are self-explanatory, but the Bundles property requires some explaining. A resource bundle, in this context, is a named group of resources (as a best practice, separate JS and CSS as the former should be at the foot of your page’s HTML and the latter should be at the top in the HEAD). With that known, you can use the following JSON format to define bundles:

What we ended doing, which has worked great, was a couple of conventions to make this easier on us:

Prefix the bundle names with the type of resources. JS bundles got the prefix “scripts.” and CSS bundles go the prefix “style.”

We keep our page-related (non-global) JS in a “Pages” folder. So, this would result in a contact us page JS being something like “/Scripts/Pages/contact-us.js.” For page bundles, this would result in a “scipts.pages.” and “styles.pages.” naming convention

Also, the code took from MSFT’s bundling to require “~/” prefixes for all local resources. This is important as non-local resources can be skipped and spit out as is onto the page.

As you can see, the ResourceConfiguration takes in a string to point to the resources configuration JSON file. This means you can change which resource file to look between environments using web.config transformations and DI.

The resource engine

Let’s get the resource engine up and running. This engine needs to use the configuration we defined above to appropriately spit out the correct files and file paths and needs to split up configured bundles into individual file requests (you could make an HTTP handler that takes a bundle name and returns a stream with the files concatenated; we chose not to do that). As such, the engine needs the following:

Resolve a resource by its path

Resolve an array of resources by a bundle name

Determine if a minified version exists*

Convert a resource path to a minified version of the path

*Minified version checks can only really be made when you’re not referencing CDN resources. Otherwise, you’re making HTTP requests to the CDN to determine if the file exists. You can do this, but we just decided to assume that if we’re turning on minifying, we’re assuming the file is up on the CDN. Furthermore, we follow the pattern .min. for all minified files.

Looking at this implementation, you’ll see that we have a Configuration property. This property will accept any implementation of our IResourceConfiguration interface. It also uses my abstract factory pattern, which you can remove if you do not wish to use that. In any case, this allows you to inject configuration.

Now that we the configuration and the engine figured out, it’s time to get your resources onto the page.

Getting resources onto the page

To get resources onto the page, we created an MVC HtmlHelper extension that is pretty self-explanatory.

As you can see, we are passing all of our configuration using a web.config set up. Using web.config transformation, we can then change any of these values during deployment. You can turn CDN usage on and off (though, if on, you need to supply a CdnPrefix), you can target different bundle configuration files, and you can turn min file usage on and off.