I am a big fan of Cassette library. It is an ending to lots of problems you will face in managing your web application scripts and style sheets. Basically what it does is serving JavaScript files and CSS files in a much more efficient and manageable way.
In short these are benefits of using Cassette:
- bundling assets
- multiple files concatenation into single file, so fewer HTTP requests
- reference management
- avoiding including unnecessary assets in a page
- performance improvements
- assets auto versioning and caching
- performance achievements through caching
- HTTP compression
- good transforms
- script minification
- support for CoffeeScript
- support for SASS
- support for LESS
- support for hogan html templates which means precompiled html templates
- embedding small images into CSS files
- a little bit of access control over script files.
- debug friendly
Although Cassette is for both JavaScript and style sheets, if you do it for JavaScript files the same process is for style sheets and HTML templates. So, let start by JavaScript files.
Setting up the Cassette in your web project
First of all we need to do a little work to make Cassette features working in our web projects.
The automatic way:
Using nuget package, just run the following command:
install-package cassette.aspnet
The manual way:
You may be a control freak like me, I need to do the things manually in order to make sure that I do know what is happening in my web application, and hence I know what is wrong in case of a failure.
So here are the things that should be done to make Cassette working:
- Configuring web.config to
- add required HTTP module and HTTP handler
- add Cassette.Views to razor page namespaces, you have to do this step for areas manually, even if using nuget package.
- Adding CassetteConfiguration class
- Add references to required assemblies:
- Cassette
- Cassette.Views
- Cassette.Aspnet
- AjaxMin
For all the features:
- Cassette.CoffeeScript
- Cassette.Sass
- Cassette.Hogan
- IronRuby
- IronRuby.Libraries
- IronRuby.Libraries.Yaml
- Jurassic
This is the HTTP module you need to add to
configuration>system.web>httpModules:
<add name="CassetteHttpModule" type="Cassette.Aspnet.CassetteHttpModule, Cassette.Aspnet" />
You may add the same to
configuration>system.webServer>httpModules.
This is the HTTP handler you need to add to
configuration>system.web>httpHandlers
<add name="CassetteHttpHandler" path="cassette.axd" preCondition="integratedMode" verb="*" allowPathInfo="true" type="Cassette.Aspnet.CassetteHttpHandler, Cassette.Aspnet" />
You may add the same entry to
configuration>system.webServer>httpHandlers.
And you have to include Cassette.Views in
configuration>system.web>pages>namespaces:
<add namespace="Cassette.Views" />
If you are using Asp.Net MVC Areas, Add the above namespace to every area's web.config.
How to implement Cassette
Simply there is only three steps:
1. create bundles to bundle a set of scripts together by writing the following line in CassetteConfiguration:
bundles.Add<ScriptBundle>("lib/js");
This will create a bundle named
lib/js which bundles all files in
lib/js folder.
2. reference bundles to specify which script is required in your razor page by writing:
Bundles.Reference("lib/js");
It records a flag that bundle
lib/js should be rendered when rendering scripts.
3. create the script tag(s) to load all the referenced libraries. in your razor page you write:
@Bundles.RenderScripts();
It renders all the so far mentioned bundles into one or several
script tags.
Step 1. Creating and configuration of bundles
In step one you specify which files to be included in a bundle called "
lib/js", you may also specify more advanced setting for bundle, like how to search for files, which files should be ignored, etc.
When bundling you specify a folder path, then according to some rules, script files within that path will be added to the bundle. Default rules exclude -vsdoc.js files and also if alternative .min or .debug file exists, then only one file is picked.
Step 2. Adding references to scripts
In step two you only mention that the specified bundle is required, you can do this 'referencing bundles' anywhere, it could be the layout page, a partial view. So you ask for a bundle to be included only when it is required and at the place that requirement is raised. For example you have a partial view that requires a specific script, so you add a reference to that script in that partial view. Assuming that no other view or partial view has a demand for that script, then that script only will be loaded for pages which contains that partial view.
The reference you specify is a bundle path. So if you write @Bundles.Reference("X") then you should have bundles.Add("X") in your Cassette configuration, or you should have a bundles.Add("Y") where X can be find in Y.
Step 3. Rendering the actual script tags
In step three you actually create script tags. So the bundles you have mentioned so far using the
@Bundles.Reference will be rendered into one or multiple script tags. If your
compliation debug attribute is set to
true, then Cassette does not join scripts and does not minify them, so you still can debug your scripts. In contrast, if
debug="false", then Cassette concatenates scripts files into a single file and also minifies them.
It may be a little bit confusing, at least it was for me. As I described it here, the step one may seems unnecessary, but you should note that in step two
"lib/js" does not refer to a scripts folder, but it is referring to the bundle we created at step one.
Rendering script tags is where you get the point of Cassette, the way that Cassette renders script tags and brings scripts into your page has several benefits. But before going forward to discuss the benefits, first let me describe some facts about
how Cassette works, so then I can tell how those benefits are achieved.
- Cassette exposes a HTTP handler for sending back scripts to client.
- Cassette computes a hash for each bundle. If the scripts change, the hash value will change.
- Cassette will return a bundle even if you asked for a single script file. For example you asked for "lib/js/MySpecificLib.js", then Cassette will return bundle "lib/js".
- Cassette will include dependencies and order scripts based on their dependencies.
So the benefits are:
- You can prevent direct access to your script libraries. So if you are using Cassette for all your resources then you may restrict direct access to libraries folder.
- Unless you created a bundle you can not reference to a script file. So there is a limited number of bundles all defined in CassetteConfiguration class. Each bundle is accessed via Cassette's HTTP handler and has a URL which is consisted of bundle's hash. So if you change the scripts then the hash changes and the URL changes, so the browser would not use the cached version and hence you forced browser to fetch the new version of bundle. And as long as the scripts are the same you can rely the browser caching. Here I have to point out an important guideline: put third parties libraries and your own libraries in different bundles, because they have different change frequencies Your app's scripts change more often, so they bundle need to be reloaded more often.
- When referencing you can be specific about exactly which scripts you need, you don't have to mention the bundle itself. If you specify a script file instead of a bundle, then cassette will render a script tag pointing to any bundle which contains that file, chances are that file already has been loaded once. However because you specified the file name rather than the bundle name, you can move that file to another bundle, for example make your bundles more granular and specific, without being worried about places which that bundle was referenced.
- You only need to reference the scripts you directly need, you don't need to be worried about if you have included the required libraries which your scripts requires.
- One important thing you should know is that the way script tags are rendered is dependent on
compilation debug attribute. If it's value is true, Cassette will generate debug friendly script tags and script files are rendered as it is. In contrast, if debug value is false, then Cassette will generate efficient script tags and script files are concatenated and minified.
- Probably you are using layout pages in a way that your pages are not concerned with the head section of html. Using Cassette you render scripts only in layout page and then reference the bundles in page whenever they are required, so when each page is served the scripts(bundles) related to that specific page is rendered.
Note: For simplicity, I have written this article for scripts. However the same story happens for style sheets, the only difference is that instead of
ScriptBundle you use
StylesheetBundle. And instead of
@Bundles.RenderScripts() you will write
@Bundles.RenderStylesheets() .
Targeted Bundles
When defining a bundle you can restrict it to a specific target. Actually, the Cassette itself referes to it as PageLocation, however you can use this feature beyond page location, so I named it targetted bundling.