Blog

Bundling and Minification with Sitecore

There's various ways to add bundling and minification to a site, but one of the easiest is to use Microsoft's support from the Microsoft.AspNet.Web.Optimization package. This implementation has some great features including:

  • Automatically create the bundles and minify them as files are changed
  • Create unique urls to each version of a bundle to force browser refreshing of the file
  • Debug mode which outputs links to the raw files rather than the bundled minified version

The functionality is also fully compatible with Sitecore. To use it in your Sitecore solution follow these steps:

1. Add Microsoft.AspNet.Web.Optimization to your project from NuGet. This is the package from Microsoft that contains the functionality to do bundling and minification

 

2. Create your CSS and Javascript bundles. Where you put the logic for this is up to you but it will need to run when the application starts. Outside of Sitecore my main development is on bespoke ASP.NET MVC and Web API projects so I like to organise startup scripts into an App_Start folder and reference it from the Application_Start event in the global ascx file.

If you are running a Sitecore instance with multiple sites or if you do not have direct access to the production config files, it may be better to keep the logic separate and use a pipeline to create the bundles.

My bundle definitions would look something like this

1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Web;
5using System.Web.Optimization;
6
7public class BundleConfig
8{
9 public static void RegisterBundles(BundleCollection bundles)
10 {
11 bundles.Add(new ScriptBundle("~/bundles/scripts").Include(
12 "~/Scripts/jquery-{version}.js",
13 "~/Scripts/bootstrap.js"));
14
15 bundles.Add(new StyleBundle("~/bundles/css").Include(
16 "~/Content/bootstrap.css",
17 "~/css/site.css"));
18 }
19}

And in my Global.asax.cs file I would have some logic like this to call the other function

1protected void Application_Start(object sender, EventArgs e)
2{
3 BundleConfig.RegisterBundles(System.Web.Optimization.BundleTable.Bundles);
4}

3. Replace your Javascript/CSS references in your layout or rendering files with a call to render the bundle

Webforms

1<%: System.Web.Optimization.Scripts.Render("~/bundles/scripts") %>
2<%: System.Web.Optimization.Styles.Render("~/bundles/css") %>

MVC

1@Scripts.Render("~/bundles/scripts")
2@Styles.Render("~/bundles/css")

4. In your web.config file there are a couple of changes needed to get things to work.

First you need to set an ignore url prefix to stop Sitecore trying to resolve the URL to the bundle. In this example mine is called bundles (note: you should add the prefix the what is already in your config file. e.g. |/bundles)

1<!-- IGNORE URLS
2 Set IgnoreUrlPrefixes to a '|' separated list of url prefixes that should not be
3 regarded and processed as friendly urls (ie. forms etc.)
4-->
5<setting name="IgnoreUrlPrefixes" value="/sitecore/default.aspx|/trace.axd|/webresource.axd|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.DialogHandler.aspx|/sitecore/shell/applications/content manager/telerik.web.ui.dialoghandler.aspx|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.SpellCheckHandler.axd|/Telerik.Web.UI.WebResource.axd|/sitecore/admin/upgrade/|/layouts/testing|/bundles" />
6

Next a dependant assembly binding needs to be set up for Web Grease. Without it you will see an error about the Web Grease version number

1<dependentAssembly>
2 <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
3 <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
4</dependentAssembly>

If you have done this and are still getting a version number error check the assembly binding tag. Older versions of Sitecore have the applies to property set which may be only applying the bindings to .Net 2 and you may be using .Net 4.

1<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1" appliesTo="v2.0.50727">

5. If you are using IIS7 you will also need to make a change in your web.config file

1<system.webServer>
2 <modules runAllManagedModulesForAllRequests="true">
3 </modules>
4</system.webServer>

6. Publish your changes into your Sitecore site. Depending if you have compilation mode set to debug or not you will now either have a bundle reference for all your JavaScript and CSS or the individual file references.

Bundling and Minification error with IIS7

.NETs standard tools for bundling and minification are a great asset to the platform solving a lot of problems with only a few lines of code.

However if using IIS 7.0 you may run into a strange issue where the path to your bundles gets created but a 404 is returned whenever there accessed. The functionality is obviously installed and working otherwise the URL wouldn't be created, but a 404 clearly isn't what you want.

The solution lies in your web.config file by setting runAllManagedModulesForAllRequests to true

1<system.webServer>
2 <modules runAllManagedModulesForAllRequests="true">
3 </modules>
4</system.webServer>

Sitecore MVC Page Editor: Combining a General Link with an Image

Sitecore's Page Editor provides content authors a simple way to edit content, visually seeing the changes as they make them. To make fields on a page editable though the Page Editor simply use the field renderer function to put a field's content on the page. When the page is viewed in page editor mode the function will output all the necessary html and JavaScript to make the page editor functionality work.

1@Html.Sitecore().Field("Title", Model.Item)

But what if your page needs to render the contents of one field inside another? e.g. You could have a link on an image or a link surrounding a combination of other fields.

In an xslt rendering you could simply nest one tag within another:

1<sc:link field="Image Link">
2 <sc:image field="Image" />
3</sc:link>

When you click the item in the Page Editor the toolbar would then combine the icons for the General Link field and the Image field so that you can edit the image and update the link. But with a ViewRendering in MVC, the Razer syntax used for views doesn't support nesting items in this way.

Thankfully Sitecore have considered this and rather than using the Field function they have included a BeginField and EndField function. Between the two you can place your nested fields:

1@Html.Sitecore().BeginField("Image Link", Model.Item)
2@Html.Sitecore().Field("Image", Model.Item)
3@Html.Sitecore().EndField()

Unfortunately it doesn't quite work right. The page will render correctly and the toolbar will show both options, however editing anything results in the content disappearing until you refresh the page. Thankfully there is a work around to get it to work:

1@Html.Sitecore().BeginField("Image Link", Model.Item, new { haschildren = true })
2@Html.Sitecore().Field("Image", Model.Item)
3@Html.Sitecore().EndField()

Sitemap XML for Sitecore

The simplest description of a Sitemap is a file that list all the URL’s on a site for a Search Engine to crawl. Traditionally web crawlers would follow links on pages within a site and rely on them to discover new pages and index them. Sitemaps add a layer of intelligence to this by providing a list of URL’s up front and also adding additional metadata such as when the page was last updates, how often it changes and how important the page is.

For more information on Sitemaps have a look at sitemaps.org

Introducing Sitemap XML for Sitecore

Out of the box Sitecore doesn’t provide any built in functionality for auto generating a sitemap, but a good module on the Sitecore Marketplace is Sitemap XML (https://marketplace.sitecore.net/en/Modules/Sitemap_XML.aspx). This module will generate a schema compliant sitemap file in the root of your site on each publish of the site.

There are a couple of issue you might experience when trying to use it:

Publishing Error

Once you’ve installed the module and attempted a full site publish you will likely get an error like this…

1Job started: Publish to 'web'|Items created: 0|Items deleted: 0|Items updated: 78|Items skipped: 15877|#Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---&gt; System.NullReferenceException: Object reference not set to an instance of an object.
2 at Sitecore.Modules.SitemapXML.SitemapManager.BuildSiteMap(String sitename, String sitemapUrlNew)
3 at Sitecore.Modules.SitemapXML.SitemapManager..ctor()
4 at Sitecore.Modules.SitemapXML.SitemapHandler.RefreshSitemap(Object sender, EventArgs args)
5 at Sitecore.Events.Event.EventSubscribers.RaiseEvent(String eventName, Object[] parameters, EventResult result)
6 at Sitecore.Events.Event.RaiseEvent(String eventName, Object[] parameters)
7 at Sitecore.Publishing.Publisher.NotifyEnd()
8 at Sitecore.Publishing.Publisher.Publish()
9 --- End of inner exception stack trace ---
10 at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
11 at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
12 at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
13 at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
14 at (Object , Object[] )
15 at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
16 at Sitecore.Jobs.Job.ThreadEntry(Object state)

Going by the comments on the Sitecore marketplace this happens to quite a lot of people and unfortunately the limited documentation for the module doesn't explain the cause.

When you install the module it will create a config file called SitemapXML.config within App_Config\Include. By default that config will have the following 2 lines:

1<site name="website_1" filename="sitemap1.xml" serverUrl="www.site1domain.com" />
2<site name="website_2" filename="sitemap2.xml" serverUrl="www.site2domain.com"/>

These websites don't exist which is what causes the error. So to fix simply change the name of your website and the URL can be left blank. e.g.

1<site name="website" filename="sitemap.xml" serverUrl="" />

Multiple Server Setup

If you're Sitecore installation is using separate servers for content management and content delivery you'll notice that your only generating sitemaps on your content management server.

To set up your content delivery server to start generating the files update the event node in the config file from

1<event name="publish:end">

to

1<event name="publish:end:remote">

and also make sure you have both the SitemapXML.dll file and config file on your content deliver server.