Sep 30, 2011

Yet another scripts and styles combining and minification in ASP.NET

In recent project we had to optimize application for mobile devices with slow internet connection. Obviously among other we need to combine, minify and gzip scripts/styles to reduce their size. And that’s how project I’m going to describe here has started. Its free, open source and hosted on bitbucket.

Examples on how to use it you can find on the wiki page. Here I want describe a little about how it it done and how it works.

nuget

Of course I’ve created a nuget package Smile. During installation it registers required http handler and creates required configuration section. The only issue I can see here is that when you are going to update on new versions it may reset your adjustments in configuration. But currently its not a lot of them so you can restore it.

Rendering

During debug mode all the scripts/styles references will be outputted separately and non minified. Its useful for debugging and development.

In release mode all the references are combined in single style/script reference with query string looking something like:

/Scripts/assets.axd?js=jquery-1.4.1,main&cache=akqipjXPAq2UFLgARybjJ9QwjiA%3d

In this case client browser will do less web requests for the page and will receive all the static content in one go.

Client side cache

URL for assets has two parameters. File names and cache. Meaning of first one is obvious, but the second one is more interesting. It is SHA1 code of combined file contents of all assets that are included in this reference. So if you change any of that scripts this parameter will change. It gives us the ability to easily allow public caching of results for the reference meaning that even proxy servers will cache it. And the uniqueness of this URL will allow you to update files without asking users to press ctrl + f5 in order to get new version of content.

Server side cache

On server side two things are cached.

Minified js and css files. Its obvious that we shouldn’t minify files on each request, so its done once and placed in cache. Also each minified cache entry has a cache dependency on file system meaning that if you change file it will expire the cache. So you can modify static content just in production without any restrictions – new file will be delivered on next request.

Digital signature. signature is calculated once and is places in cache. It also has a dependency on files its signing. So if anything changes, signature will also change.

Unit tests

I’ve tried MSpec on this project as testing framework. And I really liked it. For me the main problem in tests was the test fixture setup. It was always bigger than action, code under test and results verification part.  But look at this test:

[Subject(typeof(AssetsHandler))]
public class When_compress_is_true_and_client_accepts_gzip : compression_spec
{
    Establish context = () => {
        Compress = true;
        AcceptEncoding = "gzip";
    };

    Because of = () => handler.ProcessRequest(httpContext.Object);

    It should_set_gzip_response_filter = () => 
        response.Filter.ShouldBeOfType<GZipStream>();

    It should_set_gzip_response_header = () => 
        responseMock.Verify(x => x.AppendHeader("Content-Encoding", "gzip"));
}

Its just great! All unnecessary code ceremony is moved to compression_spec class. In the test itself you see only required part.

Where to store script references before rendering?

In order to output all references at one go assets optimizer needs a place where to accumulate all required references. In ASP.NET its easy to implement because we have an AssetsManager control. During Init event it places this pointer into Page.Items state (before writing this manager, I didn’t know this state even exists Smile) and verifies that no other assets managers has placed themselves to this storage. Each proxy object in own event just finds that single manager in page state and registers all the styles and scripts there.

In ASP.NET MVC it’s a little bit more interesting. We don’t have manager there and need to find another state where to store references. The best place for the references I’ve found is HttpContext.Items. So each call to Assets.Script or Assets.Style just places a reference and renders nothing. And when Assets.Render() is called all the collected references are picked up from HttpContext. So if some of your scripts are not rendered, make sure that you register it before Render  method is called.

What next

What next I think would be useful:

  1. Components – ability to output assets in groups, not in one go
  2. Control over order – maybe automatic, maybe by providing some king of sort order property that developer could set
  3. Routing – nice url for handler

1 comment:

  1. I like to give good names to base classes also;

    When_compress_is_true_and_client_accepts_gzip : compression_spec

    can be turned into

    When_compress_is_true_and_client_accepts_gzip : with_come_comporession_options

    or

    When_compress_is_true_and_client_accepts_gzip : using_compression_context

    ReplyDelete