Tuesday, March 10, 2015

Using Cassette to Bundle and Minify Files in MVC2, MVC3 and Older WebForms Apps

Over the last few years, we have seen the emergence of highly interactive web applications.  In order to achieve this level of interactivity, most web pages now contain multiple JavaScript and CSS files that power this behavior.

As the number of files required by a web page goes up though, the performance of the page can be degraded, because the browser has to load each one of these dependent files from the web server.  This impacts performance in three ways

  • Number of Downloads - Each separate file is a round trip from the browser to the server, and each round trip will incur network latency, the amount of time it takes for a packet to travel from the browser to server and back.  Downloading many small files is generally sower than downloading a single large file due to the impact of network latency.
  • Download Size - Multiple files to download adds up in terms of the amount of data that must be downloaded by the browser from the web server.  Simply put, larger pages take longer to download and ultimately display to the user, so we want to trim download size wherever possible,
  • Number of Concurrent Downloads - This is something that is not realized by most developers, but your browser will only download so many files concurrently from a given host at a time.  For most browsers, the limit is 6 concurrent downloads.  For IE 11, the number is 13 (check out all browsers at http://www.browserscope.org/).  What this means is that if you have 20 files that need to be downloaded from your website, only N will downloaded at a time, and the remaining files will queue behind these N files.  When a file from that group finishes, then the next download can start.  So if we can minimize the number of items that need to be queued, we can download and render our page faster.

Minification and Bundling

So how does minification and bundling help these problems?

Mnification - Looking at any JavaScript file, you will notice that there are lots of spaces, line feeds and comments in the file.  As a developer, these are good because this is what makes the file readable for when we have to work with it.  But from the point of view of the JavaScript interpreter in the browser, this information is extraneous.  It doesn't care if there is one space or eight, so long as it can parse the file.  So by removing these bytes from the file, we can shrink the size of the file, which means fewer bytes have to be sent over the wire from the server to the browser.  And these savings can be dramatic.  For example, the minified version of jQuery 2.1.3 is only 34 KB, where as the non-minified version is over 87 KB (click on the links to see the differences between a minified and non-minified file).  And minification doesn't just apply to JavaScript files.  You can see dramatic savings in CSS as well.

Note that minification is not compression.  Compression is the process of encoding information such that statistical redundancies in the data are reduced such that data can be represented in a shorter, more concise format.  Minification is the process of removing extraneous information from the file.  So what we ultimately want to do is first minify our JavaScript and CSS files and then compress them using HTTP compression so that we minimize the number of bytes that need to be sent over the wire.

Bundling - Bundling is the process of concatenating multiple files together so they can be downloaded as a single file.  Lets say that you site makes use of three CSS files to define styles.  And we want to keep these as three separate files because this makes the editing and maintenance of these files easier.  But, this means that the browser now has to perform three separate downloads in order to get each of these files.  As we said above, we are going to incur additional network latency by having these as three separate downloads, and these three files will all count against our concurrent download limit, which may block the browser from starting to download other resources required by the page.

The answer here is bundling.  These three files can be concatenated together such that now only one file needs to be downloaded by the browser.  The total size of the file will be just the sum of the size of each individual file, but we save in terms of not having separate downloads for each one and by freeing up some of the concurrent network connections the browser has to perform other work, like downloading other resources

How Do I Accomplish This?

You could manually minify your JavaScript by using a tool like UglifyJS.  And many people do just that in their build process.  Bundling presents a bigger challenge though, because we really want separate files when we are developing, and then only to combine them at deployment or run time.   It is possible to build this into your deployment process with a number of scripts, but this is a hassle to maintain.

In ASP.NET 4.5, Microsoft introduced the Bundling and Minification API, which is well covered in this excellent article by Rick Anderson.  The bundling and minification allows you to define JavaScript and CSS files that should be bundled together in your code, and then at run time, it will automatically minimize, concatenate and serve these files for your site.  The beauty of this is that it all happens transparently to you.  You work with files as you normally would in development, and then with some simple configuration, they are automatically optimized at run time.

However, many web projects exist today that are on older versions of Microsft frameworks.  Yes, there are many MVC2, MVC3 and older WebForms apps out there.  And due to constrained IT budgets, higher priority projects and most of all time, it is not always possible to simply lift these projects up to the latest version of the framework.  So these new features are out of reach for your apps using older frameworks.  Or are they?

Enter Cassette

Cassette is a package created by Andrew Davey that brings the same bundling and minification functionality available in ASP.NET 4.5 to prior versions of ASP.NET.  While the syntax varies somewhat, the concept is the same.  We can define 'bundles' of either CSS or JavaScript files in our ASP.NET application, and at runtime, these files will be bundled together and minified, thereby increasing the performance of our web site.  Lets take a look how this happens.

First, we want to add Cassette to our application.  This is most easily done with the NuGet.  The package you want is called Cassette.AspNet.  You can install this package using the GUI:


Or you can use the Package Manager Console where you will see something like this when you install:

Once Cassette is installed, you will see a new file in the root directory of your ASP.NET called CassetteConfiguration.  You want to edit this file to configure the various bundles needed by your project.

Defining Bundles

The next step is to define how you want to bundle various files together for your site.  A bundle can be either a Stylesheet bundle or a Script bundle, but not both.  What you want to do is think about how your stylesheet and JavaScript files logically map to pages.  If you have multiple stylesheets that are included in your master page or master layout view, then a bundle that contains all of these files makes sense.  A similar bundle for all of your JavaScript files that are on your master page makes sense as well.  Then you may have additional bundles that represent scripts that are only present on a subset of your pages.

Bundles are defined in the CassetteConfiguration.cs file that was shown above.  There are a number of different ways to define a bundle, but I prefer to create a List of the files to be included in the bundle and then add that list to the BundlesCollection object as shown below.


Here, I am defining three bundles.  The first contains all of the CSS files that are in my master layout view, the second all of the JavaScript I include in my master layout view and the third a single script used for working with Google Maps on one of the pages in my site.

You might ask, why would I create a bundle for a single file, because after all, the word bundle implies there should be multiple files.  What I am after here is minification of this JavaScript file.  By including this file in a bindle, Cassette will automtically minify the file for me at runtime.  So now, I can work with a readable file like normal in Visual Studio, but be assured that Cassette will take care of minification when the time comes.  Further, we will see in a bit that Cassette also adds a cache header for each bundle, which further improves performance.

When you are calling the bundles.Add() method, you will see intellisense in Visual Studio as follows:


The first argument is what Cassette calls applicationRelativePath.  Indeed, you can use this to point to a path in your application in order to include files in the bundle.  However, also note that it says this does not to be a real directory path.  And that is what I am doing.  I am using this parameter to give a meaningful name to the bundle, a meaningful name we will use in a moment when we go to include the bundle in one of our web pages.

There are a number of other ways to create your bundles, like providing a subdirectory name and allowing Cassette to create a bundle of all of the files in that subdirectory.  I like this method though, where I explicitly define the files in a collection and then add them to the bundles, giving each bundle a meaningful name.  I think this makes it very easy for someone else to follow what I am doing.  Do know though, there are other options available, and these are covered in the Cassette Documentation.

Adding Bundles To Our Web Pages

In this article, I am working with an MVC3 project.  You can however use Cassette on MVC2 and WebForms projects.  The syntax will be slightly different, but the concepts are the same.

In my View, first you need to add a code block at the top that defines the stylesheet and script bundles you are going to use in the view.  Note, you can do this in both a master layout view and an individual page view (the example below is actually in my _Layout.cshtml file).




Then, you call Bundles.RenderStylesheets() and Bundles.RenderScripts() at the points in your page where you want the stylesheet and script bundles to appear respectively.

For stylesheets, these go in the <head> tag



And it is best for performance is your scripts are placed just before the closing body tag.

There is a way that you can have scripts render in different places in your HTML page, but I will save how to accomplish that for a later blog post.

Turning on Cassette

There is one last step, and that is to turn on Cassette.  For Cassette to bundle and minify your files, you need to set the debug flag to false.



If the debug flag is set to true (as it might be for building on your local machine), then Cassette will just output the links to the regular version each CSS and JavaScript file.  This is useful for when you are debugging and might need to debug through some JavaScript code.  But in your production environments, you would have debug set to false to get the full benefit of bundling and minification (among other things).

So how does this look when things are working.  Here is the view from the Network tab in the Chrome Developer Tools.


So we can see what Cassette is doing.  It is using an HTTP Handler to service the request, and this handler bundles and minifies all of the associated files for this bundle.  Again, the nice thing is this all happens to us transparently as developers.  Cassette puts the correct links in each of our pages.  All we have to do is set the bundles up correctly.

But Wait, There is More...

One of the things Cassette also does is add the appropriate caching headers to your bundles that are sent down to the browser.  This means once a browser has downloaded the bundle the first time, it will not have to download it again for another one year.



So in the case above where we have bundles named MasterCss and MasterScripts containing the CSS and JavaScript files we use on every page in our site, these bundles will only be loaded once, not on every page a user navigates to while visiting our site.  And if they come back and visit our site again tomorrow or next week, again, these bundles will be caches locally on their browser and not need to be reloaded.  By caching these assets on the browser after the first page load, we'll save significant bandwidth on ever subsequent page load.

What happens though if we need to change one of these files?  Perhaps we find a bug in our JavaScript or need to change our stylesheets to support a new color scheme?  How does the browser know that a particular bundle has changed so that it should be downloaded again?

What Cassette does is calculate a SHA1 hash over the contents of the bundle, base 64 encodes this hash and then embeds this base 64 encoded hash value in the name of the bundle.  That is the long string in the name of the bundle in the screeenshot in the previous section.

In this way, if the contents of any of the files in the bundle changes, the computed SHA1 hash will change and hence the name of the bundle will change.  When the browser sees this new bundle name, it will realize it does not have this version of the bundle cached and download the new bundle from the web server.  This makes sure that for any changes that you need to make to your files, the browser will always download the correct version, and saves us from manually having to version our files and manage this process within our web application.

Summary

Using Cassette, we get all of the benefits of bundling and minification in our ASP.NET projects on earlier versions of the framework.  We can continue to work with the unminified, separate versions of each file while developing the project, and Cassette will automatically optimize these at runtime.  Further, each bundle will include the appropriate cache headers such that the bundles contents will be cached in the browser.

From a performance standpoint, we achieve better performance in by three major elements:
  • CSS and JavaScript files sent to the browser will be minimized, thereby removing unnecessary information like spaces and comments and reducing the number of overall bytes that need to be sent down to the client.
  • We create bundles of CSS and JavaScript files that can contain multiple files concatenated together.  This reduces the total number of files the browser must download, which reduces the penalty associated with network latency that you have to pay for each individual file download.  Also, all browsers have a limit on the number of files they will concurrently download from a site, so bundling files together helps reduce queuing of files that must be downloaded by the browser.
  • By including an aggressive cache header, a bundle will only have to be downloaded once by a browser and then can be served from cache on all subsequent page views that need that bundle.  This again reduces the number of bytes that need to be sent down to the browser.  Further, in the case where a file does change in the bundle, it is already built into Cassette to calculate a new hash value and embed the value in the name such that the browser will automatically know that it needs to download a new version of the bundle.
The example I showed here was from the MVC Music Store application.  However, I have used Cassette in production on a large consumer website that received hundreds of thousands of hits every month and it has worked flawlessly.  Of course, you want to testing of any new component like you normally would, but I can say from experience that I have had success with the package in some large scale environments.

We all know that many of these ASP.NET applications on older versions will still be around for a few more years.  So if you have one of these applications that you are responsible for, I urge you to use Cassette to get the advantages of bundling and minification to improve the performance of that application.






4 comments:

  1. As one of the leading manufacturers of deer feeders and trail cameras, Moultrie is no stranger to the needs of today's hunter, and they have brought their expertise to the fore again with the Moultrie Game spyhunter free I-45 trail camera. 

    ReplyDelete
  2. This article will provides you with the basic understanding of Business setup in Dubai. The process of business setup in Dubai involves due diligence as you have been provided with the number of options to choose from. Also in this article you will find the possible number of business types you can setup as a foreign expat. You will get the idea https://cad.cheapsoftwaredownload.net/archicad.html the governing authority that you have to get the permission before you start your business in Dubai. Also it will put some light on the benefits you may get for specific type of business you are willing to setup in Dubai.

    ReplyDelete
  3. I’d need to verify with you here. Which is not one thing I usually do! I take pleasure in reading a submit that will make individuals think. Additionally, thanks for permitting me to remark! buy coreldraw graphics suite x8

    ReplyDelete
  4. Nice article. It's very helpful to me. Thank you for share with us. Can you please check my article Fix Autoptimize WP plugin JavaScript optimize issue

    ReplyDelete