This post was migrated from Justin’s personal blog, 'Codethinked.com.' Views, opinions, and colorful expressions should be taken in context, and do not necessarily represent those of Simple Thread (and were written under the influence of dangerous levels of caffeination).

After I posted my last post about my JavaScript bundler utility, I had a few comments from people who made comments that I needed to better integrate it into an ASP.NET or ASP.NET MVC application. I had approached the problem from the standpoint of a build. I wanted an executable that could be pointed at a series of files during a build, or some other automated process, and perform all of the work involved in minifying, combining, and compressing my JavaScript and CSS. I started thinking about it however, and realized that I could probably build something to do this with a small amount of effort.

The approaches that were put forth were excellent, and one of the comments was from a fellow blogger Milan Negovan who made a similar utility recently called Shinkansen which is an integrated ASP.NET control for compressing JavaScript and CSS. It is very impressive, you should go check it out! It appears to use a custom handler and an ASP.NET component in order to combine and minify (or crunch) your JavaScript files and then cache the result and spit out a reference to the handler. It seems to be a very efficient and clever solution!

Another comment was by Jeff Olson who said that he wanted better integration into an application via an executable which could scan a project and do replacements. He was advocating a similar approach to the one that I had already taken, but instead of specifying files manually, the tool needed to scan a project and compress and combine the needed files. While this is an interesting approach if you wanted a completely platform agnostic solution, but I decided that I would implement it in a bit different manner.

The first requirement that I thought was that it had to work in both ASP.NET and ASP.NET MVC. I also didn’t want to really have any setup or configuration. I also wanted it to output a physical file that I could simply pass a reference to. This way I could avoid having to do any manual caching and such. I just thought it would be easier to deal with. My only concern here revolves around the security of having a file actually written to disk inside of the website process. Some people could have a problem with this, and there could be issues around file locking, but nothing that couldn’t be coded around.

In order to use this, all you really need to do is create folder, throw the Bundler.Framework.dll in it, and then add a reference to the dll:

image

Once you have a reference to this dll, then you simply write your javascript into the page like this:

<%= 
    new Bundle()
        .AddJs("/Scripts/jquery-1.3.2.js")
        .AddJs("/Scripts/MicrosoftAjax.debug.js")
        .RenderJs("/Scripts/Combined.js")
%>

And that is it. You create a new Bundle class, add your javascript files, and then finally tell it where to output the combined and compressed javascript. Notice that this uses a tag which renders a string out into the page. This is what renders a script tag which references the combined script:

<script type="text/javascript" src="/Scripts/Combined.js?r=20100210233126"></script>

Notice that it appends the time that the combined file was generated as a querystring parameter. This is a little trick (which I borrowed from Nate Kohari’s Agile Zen) so that when the file is regenerated, the browser will download this file again instead of using the cached version. (This can be a problem if you have multiple servers all generating this file at potentially different times, and pointing to different files on disk, so I am going to have to rethink how this is implemented)

Another great thing is that if you are in debug mode, then it simply renders out the script tags as you would normally:

<script type="text/javascript" src="/Scripts/jquery-1.3.2.js"></script>
<script type="text/javascript" src="/Scripts/MicrosoftAjax.debug.js"></script>

Very neat stuff.

So, how does it all work? Well, basically the Bundle class keeps a reference to every file it has output. So when it is called the first time it pulls all of the files and minifies and combines them, writes them out to the disk, and then renders the script tag. When the page is called a second time, the Bundle class simply checks to see if it has already rendered that file, and if it has, then it simply renders the script tag back to the client. If anything changes, just reset the application (this can easily be done by making a change in the web.config to update its date/time) and the Bundle class will lose the cached output for the script tag and then regenerate the combined file.

I’ve simply hacked this up in pretty short order this evening, and so you can go check out the source in GitHub. If you don’t use Git, then just click on the "Download Source" button and they will be happy to zip it up for you! Check it out, let me know if I’ve done anything horribly stupid, and I’d love to hear some feedback!

4 Comments

Richard

How about adding an extra method which appends a version number rather than the datetime.

Admittedly this would make it a developer task to bump the version but it would mean that in a server cluster you could guarantee the same result across all the servers.

Reply
Javi

Hi Justin,

Great post!
Instead of adding the date when the file was generated you can compute the hash of the generated file contents (and cache it for further requests) and add it as a parameter to the URL.
This fixes the issue that you pointed out for web-farm scenarios.

Javi

Reply
Justin Etheredge

@Richard Yeah, that was my original thought, but I didn’t really want the developer to have to do the work of incrementing that every time.

@Javi You know, as soon as I saw your response I hung my head in shame because I had originally looking at using hashes to determine if the file needed to be regenerated, but decided that it would be too costly. However, at the time of bundling, generating and storing the hash would be perfect. Excellent idea, thank you.

Reply
jbland

Justin,
is it possible to have the concept of
1. resource sets
2. external config

1 is important, since in highly interactive pages i have bunch of includes which handle a particular area of concern. Some of these tend to get shared across views. For example, i have an event site which contains ajax driven google maps for both venues and events. This also contains jquery, my standard util libs etc. I’d prefer to be able to share resources without a 20 line long statement in the view.

2. Mainly for the above (to specify the contents of the resource sets), but also to be able to update resources without a reset.

Reply

Leave a Reply

Your email address will not be published. Required fields are marked *