codethinked (kōdthĭngked) adj. To be consumed by or obsessed with code.

Bundler Part 2 - ASP.NET Integration

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!

Comments

pingback

Pingback from blog.cwa.me.uk

The Morning Brew - Chris Alcock  » The Morning Brew #537

blog.cwa.me.uk

February 11. 2010 03:31

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.

Richard

February 11. 2010 03:46

United Kingdom
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

Javi

February 11. 2010 07:28

Belgium
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.

Justin Etheredge

February 11. 2010 08:39

United States
trackback

Bundler Part 2 - ASP.NET Integration

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com

February 11. 2010 09:56

trackback

Использование Bundler для сборки, сжатия и публикации JavaScript-файлов в ASP.NET

Thank you for submitting this cool story - Trackback from progg.ru

progg.ru

February 11. 2010 10:02

trackback

.Net Pulse February 11, 2010

.Net Pulse February 11, 2010

Cadred (dotNET)

February 11. 2010 14:19

trackback

Social comments and analytics for this post

This post was mentioned on Twitter by dotnetlinks: Bundler Part 2 - ASP.NET Integration by @justinetheredge http://bit.ly/bQyisf

uberVU - social comments

February 11. 2010 20:25

pingback

Pingback from blog.webferia.ru

Оптимизация JavaScript | Уменьшение JavaScript | Клиентский код в ASP.NET | Bundler | WEBFeria

blog.webferia.ru

February 14. 2010 03:37

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.

jbland

March 15. 2010 19:55

United States

Add Comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading