Bundling with Gulp in TeamCity

Like most, our front end developers write CSS in LESS or SASS and then use Gulp to compile the result. This is great but up until recently both the compiled and source style files would end up in our source control repository.

While this isn’t a major issue it was more of an annoyance factor when doing a merge that the compiled file would need to be merged as well as the source files. Any conflicts in bundled/minified files also can become problematic to solve. As well as this it also just seems wrong to have both files in a repo, effectively duplicating the file. After all we wouldn’t put compiled dll’s into a repo with their source.

Our solution was to get the build server to start running the gulp tasks to produce the bundled files.

Step 1 – Install Node on the build server

To start we need NodeJS installed on the build server. This allows extensions to be installed via NPM (Node Package Manager), it’s a similar thing to NuGet,

nodejs-install-4

Step 2 – Install the TeamCity plugin for NodeJS

To add built steps for Node and Gulp we need to install a plugin to make them available. Lucking there is one that does such a thing here https://github.com/jonnyzzz/TeamCity.Node

The actual build of the plugin you can download from Jetbrains team city here https://teamcity.jetbrains.com/viewType.html?buildTypeId=bt434. Just login as guest and then download the latest zip from the artifacts of the last build.

To install the plug you need to copy the zip to Team City’s plugin folder. For me this was C:\ProgramData\JetBrains\TeamCity\plugins, if your having trouble finding your’s just go to Administration > Global Settings in Team City and it will tell you the data directory. The plugin folder will be in there.

Restart the TeamCity server and the plugin should now show under Administration > Plugins List

NodeJS Plugin

Step 3 – Add a NPM Setup build step

NPM Build Step

The NPM step with a command of install will pick up dependencies and get the files.

Step 4 – Add a Gulp build step

Gulp Build Step

In your gulp step add the path to the gulp file and the tasks in your gulp file that need to be run. I’m using a gulp file that our front end devs had already created for the solution that contained as task for bundling css and another for bundling js.

Step 5 – Including bundled files in a MSBuild

As the bundled files are no longer included in our Visual Studio solution it also means that they arn’t included in the set of files which will be included in a publish when MSBuild runs.

To overcome this update the .csproj file with a Target with BeforeTargets set to BeforeBuild and list your bundled files as content. In my example I’m included the whole Content\bundles folder

<Target Name="BundlesBeforeBuild" BeforeTargets="BeforeBuild">
    <ItemGroup>
      <Content Include="Content\bundles\**" />
    </ItemGroup>
  </Target>
Advertisements

Sitecore Alias as Redirect

One feature of Sitecore that I have always disliked is Alias’s. On each page of a site, content editors have the ability to click an alias button on the presentation tab and add alternative urls for the page.

Alias Toolbar

Once added these will appear in the Aliases folder under system.

Alias

However all this accomplishes is multiple URLs existing for one page which is a big SEO no no.

Content editors like to do this in order to create simple URLs for things like landing pages. e.g. himynameistim.com/Sitecore but search engines hate it as they see multiple pages with the exact same content. As a result the value of each page gets lowered and appears lower in search engine results. What Content editors really want is to set up a 301 redirect so that they can have the simple URL but redirect users to the actual page on the site.

Aliases as Redirects

One solution is to updated the aliases functionality to cause a redirect to it’s linked item rather than resolve the page.

To do this we need to create a pipeline processor that inherits from AliasResolver.

using Sitecore;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;
using System.Net;
using System.Web;
using AliasResolver = Sitecore.Pipelines.HttpRequest.AliasResolver;

namespace HiMyNameIsTim.Pipelines
{
    public class AliasAsRedirectResolver : AliasResolver
    {
		public override void Process(HttpRequestArgs args)
		{
			if (!Settings.AliasesActive)
			{
				return; // if aliases aren't active, we really shouldn't confuse whoever turned them off
			}

			var database = Context.Database;

			if (database == null)
			{
				return; // similarly, if we don't have a database, we probably shouldn't try to do anything
			}

			if (!Context.Database.Aliases.Exists(args.LocalPath))
			{
				return; // alias doesn't exist
			}

			var targetID = Context.Database.Aliases.GetTargetID(args.LocalPath);

			// sanity checks for the item
			if (targetID.IsNull)
			{
				Tracer.Error("An alias for \"" + args.LocalPath + "\" exists, but points to a non-existing item.");
				return;
			}
			var item = args.GetItem(targetID);

			if (database.Aliases.Exists(args.LocalPath) && item != null)
			{
				if (Context.Item == null)
				{
					Context.Item = item;
					Tracer.Info(string.Concat("Using alias for \"", args.LocalPath, "\" which points to \"", item.ID, "\""));
				}

				HttpContext.Current.Response.RedirectLocation = item.Paths.FullPath.ToLower()
					.Replace(Context.Site.StartPath.ToLower(), string.Empty);
				HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.MovedPermanently;
				HttpContext.Current.Response.StatusDescription = "301 Moved Permanently";
				HttpContext.Current.Response.End();
			}
		}
    }
}

And patch in in place of the regular Alias Resolver.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="HiMyNameIsTim.Core.Pipelines.AliasAsRedirectResolver, LabSitecore.Core" 
                   patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.AliasResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

The above code is adapted from a solution given by Jordan Robinson but with a bug fixed to stop every valid URL without an alias writing an error to the log file.