Sunday, June 12, 2011

Combine and minify JavaScript / CSS in Sitecore CMS

For a long time I tried to find a solid JavaScript / CSS combining and minifying solution, which is  easy to use, extensible and works with Sitecore without need to change sources, etc.
It’s not that hard to create own custom tool, but for many reasons it makes more sense to use actively developed and well-tested project.


And it looks like I’ve finally found it. With Combres (open source .NET library which enables minification, compression, combination, and caching of JavaScript and CSS resources) you can combine / minify both JS and CSS. You can even choose minification engine from: YUI Compressor for .NET, Microsoft Ajax Minifier and Google Closure Compiler. Also, Combres does the following stuff:
  • Adds Expires or Cache-Control header. Combres automatically emits Expires and Cache-Control response headers.
  • Gzip-s components. Combres will detect Gzip and/or Deflate support in the users' browser and apply the appropriate compression algorithm on each resource set's combined content before sending it to the browser. 
  • Configures ETags. Combres emits ETags for each resource set's combined content.
Another thing important for developers – you can simply turn off combining by changing configuration file, so you can debug JS without any problems. Read more about all Combres features here.

In this blog post I’ll describe installing Combres and configuring it to work with Sitecore website (AppPool - Integrated Mode and .NET Framework 3.5, minor adjustments will be needed if you use 4.0).
Configuring is a bit tricky, but I tried to describe all steps as clear as possible. This guide is based on official quick-start from here.

Download and install NuGet Package Manager extension for VS.NET 2010
  1. Backup your Web.config file (this is important!)
  2. Open your Sitecore project in VS.NET 2010
  3. Open the Package Manager Console (Tools > Library Package Manager > Package Manager Console)
  4. In the command window enter Install-Package Combres
The package manager may fail with “Unable to find assembly references that are compatible with the target framework '.NETFramework,Version=v3.5'.“ message, in this case you should change Target Framework property of the project to 4.0, install the package again and change it back to 3.5.

After the installation you’ll get few additional references in the project, App_Start folder, config files in  of App_Data folder, and updated Web.config.

Update Web.config
Combres installer cannot update Sitecore config properly. I suggest restoring the original one and performing the following steps:
  • Add configuration section declaration:
    <section name="combres" type="Combres.ConfigSectionSetting, Combres, Version=2.2, Culture=neutral, PublicKeyToken=1ca6b37997dd7536"/>
    
  • Add logger declaration (if needed):
    <logger name="Combres">
        <level value="DEBUG"/>
    </logger>
    
  • Add Combres configuration section (inside "configuration"):
    <combres definitionurl="~/App_Data/combres.xml" logprovider="Combres.Loggers.Log4NetLogger">
    
  • Add: “/combres.axd” to IgnoreUrlPrefixes
  • Add the following to the "pages" section:
    <namespaces>
        <add namespace="Combres"/>
    </namespaces>
    
  • Enable UrlRouting – add the following to the "modules" section of Web.config:
    "modules" section:
    <add name="RoutingModule" type="System.Web.Routing.UrlRoutingModule, 
                   System.Web.Routing, 
                   Version=3.5.0.0, Culture=neutral, 
                   PublicKeyToken=31bf3856ad364e35"/>
    "handlers" section:
    <add name="UrlRoutingHandler" path="UrlRouting.axd" precondition="integratedMode" type="System.Web.HttpForbiddenHandler, 
                   System.Web, Version=2.0.0.0, Culture=neutral, 
                   PublicKeyToken=b03f5f7f11d50a3a" verb="*"/>
    

Register Route (for ASP.NET 3.5 users)
  • Delete the generated file /AppStart/Combres.cs
  • Remove the reference to the assembly WebActivator
  • Open global.asax code-behind file 
  • Import Combres namespace, i.e. using Combres;
  • Add this to the first line of either RegisterRoutes() or Application_Start():  
    RouteTable.Routes.AddCombresRoute("Combres");
    
  • Reference “System.Web.Routing” library and set “Copy local” to true;
  • Rebuild your solution

Modify Combres.xml 
  • Open /App_Data/Combres.xml (this should be auto-created when you install the Combres NuGet package)
  • Add/remove resource sets, resources etc. as per your need. Here's the sample file:
    <resourceSet name="siteCss" type="css">
          <resource path="~/Css/default.css" />
        </resourceSet>
        <resourceSet name="siteJs" type="js">
          <resource path="~/Scripts/jquery-1.4.1.min.js" />
          <resource path="~/Scripts/jquery-1.4.1-vsdoc.js" />
        </resourceSet>
    
Add references to Combres resource sets in layout 
  • Open any page (for example, Sample layout.aspx) which need to reference JS/CSS resource sets defined in Combres.xml
  • Add the following to the "head" section:
    <%= WebExtensions.CombresLink("siteCss") %>
    <%= WebExtensions.CombresLink("siteJs") %>
    
If you have any questions, or encountered problems during the installation - do not hesitate to leave the comments. Also, it would be great if you can share your feedback or suggestions.

15 comments:

  1. Hi Alex,
    Thanks for shareing. We've been using YUIcompressor for a while now, but we are doing the minifying during our project build using MSBuild scripts. This reduces complexity of the installation (less logic, less components, less upgrade-incompatibilities).

    But Combres obviously does more than minifying js/css files.
    - Gzip Compression: we use the built in compresson of IIS 7.5
    - Expire/Cache-control: we use the built in cache-control of IIS 7.5
    - ETags: again, we use IIS 7.5 for that.

    Any Pros/Cons on using the Compres Feature Set instead?

    ReplyDelete
  2. Hi,

    I think it does not make much sense for you to change MSBuild / IIS configurations to Combres.
    Combres also works great for developers, as it can be turned off by one setting and then immediately turned on to test combined / minified resources, etc.

    -Alex

    ReplyDelete
  3. Hi Alex,

    Thanks for the snippet

    We are using Combres in our project, we kept the JS and CSS files in file system to take advantage of Combres. Is there any recommendation from you on file system Vs media library?

    Also, we have agencies building sites in the platform and they will be adding new CSS and JS files. What is your suggestion on modifying the xml file?

    Thanks
    RP

    ReplyDelete
  4. I can only suggest using Media Library for such assets as CSS / JS if you plan changing them frequently, without releasing the website (example - small promo sites, event sites, etc.)
    In all other cases using File System is much more convenient for developers.

    ReplyDelete
  5. On our project the Sitecore log4net (apparently version 1.2.10) and the Combres log4net (version 1.2.11) are clashing. Has anyone else run into this issue?

    ReplyDelete
  6. You just need to build Combres from sources, referencing the version of log4net used by Sitecore.

    ReplyDelete
  7. Le sigh... Your advice was definitely helpful, although the need to do it a bit of a pain in the neck. Thank you, Alexander!

    ReplyDelete
  8. Hi, i keep getting a 404 error in sitecore using combres.
    I'm sure almost everything is setting up perfectly but there must still be something wrong. The combined css file is generated but http://domain.com/combres.axd/siteHeaderJs/-1139431538/ still gives a 404 (Not Found)

    Can someone please help me with this?

    ReplyDelete
  9. Make sure that UrlRouting is setup and actually works. Do you see the module in IIS?

    Also it worth checking that Sitecore handles IgnoreUrlPrefixes properly, as well as the code in Global.asax

    ReplyDelete
  10. i see the module urlrewrite in IIS and that's working properly. I also added /combres.axd in the web.config and "RouteTable.Routes.AddCombresRoute("Combres");" to the application_start event in global asax. i need to find a way how to debug what's going wrong. It looks like the ignoreUrlPrefix is not working or something else prevents my url routing.

    ReplyDelete
  11. There should be a UrlRoutingModule-4.0 module, not urlrewrite

    ReplyDelete
  12. i see UrlRoutingModule-4.0 in the modules section so i think it is installed properly( i also have some mvc application and they work without any problem)
    I also installed routedebugge via nuget and when i go to my generated css file : http://example.com/combres.axd/siteHeaderCss/1289323714/ i see at the bottom of the page that the combres route matches the current request. Is there a way to debug further?

    ReplyDelete
  13. I created on the same IIS 7 an empty website with the same combres settings and dll’s and there it works like a charm.
    So I think sitecore is preventing the combres.axd from processing the request, because I still get a sitecore 404 page.

    ReplyDelete
  14. Can you try it @ Clean Sitecore Website? Maybe, there is some customization at your site?

    ReplyDelete
  15. it is solved!! I had two IgnoreUrlPrefixes settings.. one in web.config and one in sitecoresettings.config in the include folder. It was the latter that is used by sitecore and not the web.config (i don't know why i have this setting in that file because it isn't used)

    Thanks all for your help, while looking for the 404 settings i've found the real problem!

    ReplyDelete