Written on 8/28/2014 in Web development

The poet part 3 - SquishIt with NancyFx


The poet NancyFx SquishIt

'The Poet' now has a few pages and I can use Razor views. Next up is bundling and minification. You can do this using SquishIt for both CSS and Javascript. Setting up squishIt involved a lot of swearing and a few curses. But don't worry, I retracted the curses on humanity. There isn't a lot of documentation on how to use SquishIt. There are a few blog posts, there is some documentation about how to set it up in nancy. But most of them are incomplete or lack some explanation. This is how I got it to work.

Squish it all.

Setting up SquishIt bootstrapper

First, install the SquishIt nuget package:

install-package SquishIt

After it's installed, you can start setting up your bundles. You can call bundle creation using the Nancy bootstrapper:

public class NancyBootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
    {
        base.ApplicationStartup(container, pipelines);

        // Create bundles
        NancyBundles.CreateBundles();
    }
}

And the NancyBundles class:

public static class NancyBundles
{
    public static void CreateBundles()
    {
        // For deployment purposes, base path is 'http://www.url.com/ThePoet'
        Bundle.ConfigureDefaults().UseDefaultOutputBaseHref("/ThePoet");

        CreateJsBundles();
        CreateCssBundles();
    }

    public static void CreateJsBundles()
    {
        Bundle.JavaScript()
            .Add("js/lib/angular.js")
            .Add("js/modules/autocomplete.js")
            .Add("js/modules/angular-thedeadpoet-main.js")
            .Add("js/services/AppHttpService.js")
            .Add("js/controllers/indexController.js")
            .Add("js/controllers/suggestController.js")
            .ForceRelease() // Forces minification, also in debug mode
            .AsCached("jsBundle", "/js/squished/jsBundle"); // Cached file
    }

    public static void CreateCssBundles()
    {
        Bundle.Css()
            .Add("css/normalize.css")
            .Add("css/autocomplete.css")
            .Add("css/site.css")
            .ForceRelease() // Forces minification, also in debug mode
            .AsCached("cssBundle", "/css/squished/cssBundle");
    }
}

This is simple enough. You just call the Bundle static class, create a JavaScript or Css bundle using the responding functions, and add the file locations to the bundles. Now, when you set this up without 'ForceRelease()' in debug, it will return all separate files and not minify them. Which is logical for debuggin purposes. I use 'ForceRelease' because for deployment, I used a virtual directory to add the application to an existing website. When I use 'ForceRelease()' in combination with 'Bundle.ConfigureDefaults().UseDefaultOutputBaseHref("/ThePoet");', it will look for the bundle on the appropriate url1. Without 'ForceRelease()', it looks for the files on the wrong url's without the basePath2. It's quite possible this is an error on my part, but then again, they did not make it clear enough how to do it properly.

For the bundle creation, there are 2 options: 'AsNamed' and 'AsCached'. 'AsNamed' will create the minified bundle and store it in a file location, which could result in problems when running your application with insufficient rights or on a cloud platform with multiple instances. 'AsCached' will create the minified bundle and store it in memory, which is a better option here since I'm working with Azure, and without testing it, I can say it's probably faster..

Accessing bundles

Now we need a way to access them. I copied a nancy module from somewhere to return the requested cached bundle:

public class SquishedModule : NancyModule
{
    public SquishedModule()
    {
        Get["/js/squished/{name}"] = parameters => CreateResponse(Bundle.JavaScript().RenderCached((string)parameters.name), Configuration.Instance.JavascriptMimeType);
        Get["/css/squished/{name}"] = parameters => CreateResponse(Bundle.Css().RenderCached((string)parameters.name), Configuration.Instance.CssMimeType);
    }
    Response CreateResponse(string content, string contentType)
    {
        return Response
            .FromStream(() => new MemoryStream(Encoding.UTF8.GetBytes(content)), contentType)
            .WithHeader("Cache-Control", "max-age=45");
    }
}

This module will retrieve a url of format "domain+basepath/js/squished/cssBundle" and look for a cached bundle with the name 'cssBundle'. I'm saying this because above, it might not be clear that in '.AsCached("cssBundle", "/css/squished/cssBundle");', the cssBundle parts need to be the same. Otherwise, the bundle will not be found because the name is different3.

Getting the bundle from our view

Now we need to call the bundles from our views. You need to add the SquishIt assembly and namespace to the web.config:

<razor disableAutoIncludeModelNamespace="false">
    <assemblies>
        <add assembly="SquishIt.Framework" />
        <add assembly="System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    </assemblies>
    <namespaces>
        <add namespace="System.Linq" />
        <add namespace="SquishIt.Framework" />
    </namespaces>
</razor>

This is all you need to use the following in your razor view:

@Html.Raw(SquishIt.Framework.Bundle.Css().RenderCachedAssetTag("cssBundle"))

'RenderCachedAssetTag("")' will create a script tag with the correct url. I believe this is also where it goes wrong with the virtual folder in debug mode. The script tags added here for deminified scripts point to the wrong location because it doesn't use the basePath.

This got SquishIt up and running in my solution. My honest opinion is that it's still way too difficult, but once set up, adding and removing bundles is easy. What I don't like is that I didn't get intellisense to work for SquishIt in the razor views. I would also like the issue with the virtual folder to be solved without 'ForceRelease()'.

Newer Older Top
blog comments powered by Disqus