In
the first installment of this series, I explored a few of the new features in
ASP.NET MVC 4, including the new default project templates, mobile templates,
and display modes. Since that article, ASP.NET MVC 4 has been released to beta.
For brevity’s sake, when I refer to MVC the design pattern, I’m referring to the
ASP.NET implementation of the pattern. In this installment, I’m going to focus
on one of MVC’s most useful features: integrated JavaScript and CSS bundling and
minification.
One
of the most important considerations in any Web application is the size of the
content rendered to the browser. Bundling and minification handle two important
tasks. First, all of the disparate JavaScript and CSS files are combined into
one or more files. Second, the JavaScript and CSS code is minified by means of
removing all of the carriage returns and line feeds as well as verbose variable
names in favor of shorter (less verbose) alternatives.
With
bundling, there are fewer resources that need to be rendered to the browser.
With minification, the size of such resources is much smaller, often by as much
as 70% or more!
These
are not new concepts. Tools like JSMin and YUI have been around for several
years. You have always been able to incorporate these bundling and minification
solutions. What’s new are the integrated features baked into the MVC framework
that handle these bundling and minification tasks out of the box.
Bundling and Minification “Out
of the Box”
Figure 1 illustrates what
you get out of the box for bundling and minification. Like all ASP.NET
applications, everything starts in the Global.asax Application_Start() event
handling code.
Figure 1: These code windows represent the out-of-the-box bundling and minification implementation in Global.asax and _Layout.cshtml.
In
that code, there’s a call to:
BundleTable.Bundles.RegisterTemplateBundles();
The
code for this method is burned into the System.Web.Optimization.dll. Figure 2
illustrates what that code looks like.
Figure 2: This code represents the out-of-the-box implementation code for RegisterTemplateBundles in System.Web.Optimization.
The
code is straightforward in that bundles are created and files are added to the
bundles. There are two ways to add files. One way is to refer to the file
specifically. The second way is to specify a directory along with a file pattern
search string. In the second method, the code can selectively traverse
subdirectories to add content.
Each
method has their respective pros and cons. When you specify the files, you know
exactly what is getting into your bundled and minified files. This method
requires more code. When you add directories, you don’t have to write as much
code, but the possibility exists for unwanted code in the bundled and minified
file. Choosing between adding files, directories or some combinatin thereof is a
matter personal preference.
To
use this functionality, there are a few concepts you must be familiar
with:
- Bundle Class: Encapsulates one or more files to be bundled and minified
- AddFile() Method: Adds a specifically named file to the bundle
- AddDirectory() Method: Adds files that match a file search string within the specified directory
- Transform Property: An instance of IBundlerTransform, the object in this property performs the minification task
- Path Property: The string that identifies the bundle within the Bundles Member in BundleTables
As
you will see in a moment, as is custom in MVC, if you don’t like the default out
of the box functionality in MVC, you can substitute your own functionality.
First, take a look at what the default functionality gives you. Figure 3
illustrates how the disparate javaScript (JS) and Cascading Style Sheet
(CSS) files are rendered to the browser.
Figure 3: The network view lists the bundled and minified CSS and JS files (with a query string parameter that is explained in the next section).
Note
the file paths:
/Content/css/Content/themes/base/css/Scripts/js
These
are the paths specified when the bundles were created. Figure 4
illustrates how the minified JavaScript appears.
Figure 4: This is the minified and bundled JavaScript content as rendered to the browser.
Why Is a Query String
Parameter Added to the JavaScript and CSS Files?
Every
time the page is refreshed, the server-side code is executed, causing the
_Layout.cshtml Razor Template to evaluate. You always want the client to
recognize the latest JavaScript and CSS content. If the file were always named
the same, the browser may reference its cache. This behavior can always be
configured at the browser level. However, that is not usually a feasible
solution because it requires each computer to be specifically configured. If you
have the chance to control behaviors that are essential to your applications at
the server level, take it! With the addition of the query string, the client
will interpret this to be a new file and therefore, rely on its cache.
You
might be thinking that the call to ResolveBundleUrl is required for the bundling
and minification to work. It’s not. To illustrate, I’ll replace the code for
/Content/css with the following:
<link href="~/Content/css" rel="stylesheet" type="text/css" />
Figure 5 illustrates that
bundling and minification still takes place. The only difference is the file
name. Without the call to ResolveBundleUrl, the query string parameter is not
added.
Figure 5: If the ResolveBundleUrl call is omitted, a query string parameter will not be added.
Essentially, that’s it! That’s what
we get for free. But what about debugging? I don’t want my code minified in
those cases. Am I stuck?
Of
course not! In MVC, with almost no exception, you can override the default
out-of-the-box behavior for your own behavior. In the next section, you will see
how to create your own custom bundler and minification class.
Creating a Custom
Bundler
If
there are limitations to how the bundling and minification feature is
implemented in the beta, these two would be at the top of the list:
While
bundling is something you probably always want, minification isn’t. While
debugging, you need the unminified code to be rendered. Out of the box, using
the default bundler, you cannot conditionally bundle. Let’s solve that problem
now. The custom bundler code in Listing 1 solves the problem.
What’s the Second Bool
Variable in AddFile()?
The
second Bool Variable is the throwIfNotExist parameter. If the specified file
does not exist, you can elect to have an exception thrown. In the default beta
implementation, this parameter is set to false.
If
you don’t want to be explicit with your files, the code could be simplified to
what is illustrated in Listing 2.
The
AddDirectory() method has four parameters:
In
this simplified code, every js file under /Scripts and every CSS file under
/Content is included.
In
both cases, a compiler directive is added to specify the transformer used to
drive the minification process. Out of the box, there is a class called
NoTransform. As the name implies, this class does not minify. You need such a
class because the bundler instance requires a transformer:
var bundle = new Bundle("~/Scripts/js", jstransformer);
The
bundler does not care what the transformer does. As long as it gets an instance
that conforms to IBundleTransformer, the Bundle instance will be happy.
Listing 3 shows what the NoTransform class is.
I Like the YUI Minifier; Can
I Use That?
The
nice thing about the way bundling and minification was implemented in ASP.NET
MVC is that you don’t have to give up using utilities that you are currently
using. The implementation works very much like the way IDependencyResolver
works. In Version 3, an inversion of control container adapter was added to
abstract away the details of any specific IoC container from the framework. The
bundling and minification process illustrated here works very much the same way.
The process begins with creating a custom instance of IbundlerTransform, as
shown in Listing 4.
To
take advantage of the YUICompressor Transform Class, the code in Listing 2
that loads the proper transformer must be changed to the
following:
bool isDebug;#if DEBUG isDebug = true; #endif if (isDebug) { jstransformer = new NoTransform("text/javascript"); csstransformer = new NoTransform("text/css"); } else { jstransformer = new YUITransform(contentType.javascript); csstransformer = new YUITransform(contentType.css); }
I
changed the debug-checking process slightly, because I am gradually moving to a
solution that is testable. The next step involves creating a custom abstraction
over the base Bundle Class that begins to abstract away the details of which
files to add. I’ll leave that exercise to you to explore.
File Ordering within a
Bundle
By
default, JavaScript files are first ordered alphabetically within a bundle.
Then, the files are restacked around known libraries. For example, jQuery -
related files occur first in the bundle. Within the jQuery group, the files are
sorted alphabetically. For CSS files, the files are first sorted alphabetically.
Then, if the files reset.css or normalize.css exist, that content appears at the
top of the bundled CSS file. Figure 6 illustrates this behavior in the
bundled CSS file. Like everything else, this behavior is completely
customizable. The Bundle Class has an Orderer property that conforms to the
IBundleOrderer interface.
Figure 6: The Bundle.Orderer Property controls how files are ordered within the bundle.
Conclusion
With
each release, the ASP.NET MVC Framework gets better and better. Bundling and
minification is an essential process that needs to be in every production Web
app that relies on significant JavaScript and CSS resources. Out of the box, the
functionality is pretty good. There are, however, some missing pieces.
Fortunately, the process was designed with customization and extensibility in
mind. With a little effort, it was easy to toggle minification based on whether
or not the application was being run under debug mode.
One
final point, this article was based on beta software. The usual disclaimer
applies - this functionality may change when the product is
released to manufacturing (RTM).
John V. Petersen
|
& |
SPONSORED SIDEBAR: Learn ASP.NET MVC in a Day! ASP.NET is one of the world’s most popular Web development environments. We can help you with all ASP.NET projects as well as related technologies, such as HTML (4 and 5), JavaScript, jQuery, CSS, AJAX, services, and many more. (Learn more from www.codemag.com/consulting.) That’s why CODE Training is offering a full day of training in ASP.NET MVC from CODE Consultants, experts in Web development. CODE Training and EPS Software will be holding an intensive one-day lecture, June 5, 2012, on ASP.NET MVC specifically designed for developers of business applications. Learn how, when and why to use ASP.NET MVC for the best result in your projects. Only $399! Visit www.codemag.com/training to find out a little bit more about the class, or send an e-mail to info@codemag.com for more information. | |||||||||||||
|