Sitecore QA Best Practices

Code a la Mode banner

4 practices specific to testing Sitecore that all Quality Assurance Specialists should follow to ensure correctness and head off bugs early.

Don’t test Content Editor or Experience Editor using an admin account

When a new instance of Sitecore is set up, by default there is only one “admin” account. Creating additional users and roles may be held off until later in the project, but developing and testing using only the “admin” account may conceal bugs. Admins have access to items and fields that normal Content Authors do not. Authors may be restricted to specific languages, but Admins are not restricted. Authors have to lock pages for editing, but Admins bypass locks and workflow. Authors have different publishing access than admins. There are enough differences in the authoring experiences to merit testing using both types of accounts.
Author account missing permissions

Test custom functionality using at least 2 different languages

When a new instance of Sitecore is set up, by default there is only one language available, “en”. Typically, developers build and test components using only “en”, and additional languages are added only when they are needed by content entry folks. Developing in only the default language can hide bugs. The issue is that when items and fields are fetched from the database, a developer can explicitly specify which language version to fetch, or he can leave it up to Sitecore to use the current context language. When rendering a page on the front end, Sitecore sets the context language equal to the language of the page being viewed, so in the ideal scenario all the items and fields that we fetch in code are in the same language as our page. However, sometimes Sitecore doesn’t have a page language to go by, like during an index crawl. In this case, Sitecore defaults to “en” as the context language. So if you are crawling the French version of a page, and the code fetches data from the database without explicitly specifying a language, you may get “en” content indexed in your French page.
Language selection in Sitecore

Test that renderings fail gracefully if missing datasources

Not all renderings use datasources, but those that do need to be coded to account for the possibility that the datasource might be missing. How can a datasource be missing:

  • The rendering was added through Presentation Details, which doesn’t prompt for the creation of a datasource item, and the user forgot to add it
  • The datasource item was deleted by accident
  • The datasource item exists, but it was not copied to web database because it has validation errors or it’s not in approved workflow state
    If the datasource is missing, the ideal behavior is to show a useful error message in editing mode, and to suppress the rendering entirely in front-end mode. We certainly need to avoid a single rendering taking down an entire page. To test graceful failure, use Experience Editor to add a rendering with datasource to a page, save, and then delete the datasource item from the Sitecore tree in Content Editor. Check the page in editing, preview, and front-end modes.

Run accessibility compliance scans early and often

In scans like SEO and performance, different elements contribute to a single page-level score, so it makes sense to hold off running these tests until the end of a project, when development is nearly complete and pages are near their final state. However, accessibility compliance errors are assigned at the level of individual DOM elements, so it’s ok to run an accessibility scan even when a page is only partially complete.
In my experience, remediation of accessibility compliance errors can be pretty involved. For example, if the HTML of a component needs to be changed, then CSS and JavaScript may need to be refactored too. So it’s a good idea to start scans early in the project.

Tool to use: https://wave.webaim.org/
Wave tool example

How to Change Render Behavior During Crawl-Time (When Using Coveo)

Code a la Mode banner

You know how it’s super easy to change how a rendering is rendered based on Sitecore.Context.PageMode? If you’ve ever wished there was a similar check available for PageMode.IsIndexCrawlerCrawling, then read on.
User VS Index Crawler

Why does crawler context matter

  1. If you have renderings that show alerts to front-end users (for example, cookie consent or legal disclaimers), it makes sense not to render these at crawl time since their HTML shouldn’t show up in search results.
  2. If you have dynamic renderings that AJAX their content, you may want to provide a static version for the index.
  3. If you have renderings that inject user tracking code, it makes sense to keep this code out of the indexed version of the page.

Let’s code!

Wrap the default HtmlContentInBodyWithRequestsProcessor

By default, Coveo uses the HtmlContentInBodyWithRequestsProcessor class to index a page’s content. This class makes an HTTP request to the page’s URI and indexes the HTML that is returned. Unfortunately, HtmlContentInBodyWithRequestsProcessor is mostly non-virtual and private methods, so it’s difficult to customize. But we can get around this by creating a wrapper.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using System;
using System.IO;
using Coveo.Framework.Configuration;
using Coveo.Framework.Http;
using Coveo.Framework.Processor;
using Coveo.SearchProvider.Pipelines;
using Coveo.SearchProvider.Processors;

namespace Website.Search.Processors
{
/// <summary>
/// Wraps <see cref="HtmlContentInBodyWithRequestsProcessor"/> to add query string
/// parameter to Http Request that denotes the request as coming from index crawler.
/// </summary>
public class CustomHtmlContentInBodyWithRequestsProcessor
: IProcessor<CoveoPostItemProcessingPipelineArgs>
{
private readonly object _lock = new object();

private HtmlContentInBodyWithRequestsProcessor _baseProcessor;

/// <inheritdoc/>
public void Process(CoveoPostItemProcessingPipelineArgs args)
{
if (_baseProcessor == null)
{
lock (_lock)
{
if (_baseProcessor == null)
{
_baseProcessor = new HtmlContentInBodyWithRequestsProcessor(new CustomItemContentFetcher(new HttpRequestBuilder(), args.IndexConfiguration));
}
}
}

_baseProcessor.Process(args);
}

private class CustomItemContentFetcher : IItemContentFetcher
{
private readonly HtmlItemContentFetcher _baseFetcher;

public CustomItemContentFetcher(IHttpRequestBuilder requestBuilder, CoveoIndexConfiguration indexConfiguration)
{
_baseFetcher = new HtmlItemContentFetcher(requestBuilder, indexConfiguration);
}

public Stream FetchItemContent(string url) => _baseFetcher.FetchItemContent(FixUrl(url));

public IHttpWebResponse FetchHttpWebResponse(string url) => _baseFetcher.FetchHttpWebResponse(FixUrl(url));

/// <summary>
/// Adds a "isIndexCrawl" query string parameter to the item's default URI
/// </summary>
private string FixUrl(string url)
{
var uriBuilder = new UriBuilder(url);
var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
query["isIndexCrawl"] = "1";
uriBuilder.Query = query.ToString();
return uriBuilder.ToString();
}
}
}
}

Configuration patch

Replace default processor with custom processor in configuration

1
2
3
4
5
6
7
8
9
10
11
12
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<coveoPostItemProcessingPipeline>
<processor type="Coveo.SearchProvider.Processors.HtmlContentInBodyWithRequestsProcessor, Coveo.SearchProviderBase">
<patch:delete />
</processor>
<processor type="Website.Search.Processors.CustomHtmlContentInBodyWithRequestsProcessor, Website" />
</coveoPostItemProcessingPipeline>
</pipelines>
</sitecore>
</configuration>

Set up renderings

Now we just need to make our renderings aware of whether the special query string parameter is present. Add this IsCrawl property to your Rendering Model class. You can test for this property in your View and change rendering output accordingly.

1
public bool IsIndexCrawler => Sitecore.Context.Request.QueryString["isIndexCrawl"] == "1";

Bon Appétit!

Help Content Authors Differentiate Between Page-Specific and Shared Content

Code a la Mode banner

This blog post covers how to inject extra metadata text into Experience Editor. One of the great uses of this metadata text it to help content authors see a visual difference between renderings that use local datasources and renderings that use shared datasources.

Introduction - How Shared Content Folder Work

It’s common practice for Sitecore projects to organize content items (rendering datasources) into different Content folders that denote whether the content is page-specific or shared. For example, the project I’m currently working on has a structure like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sitecore/
└── Content/
├── Program Name/
| ├── Asia/
| | ├── Region Shared Content/
| | └── Page Name/
| | └── Page Name/
| | └── Content/
| ├── Europe/
| | ├── Region Shared Content/
| | └── Page Name/
| | └── Page Name/
| | └── Content/
| └── North America/
| ├── Region Shared Content/
| └── Page Name/
| └── Page Name/
| └── Content/
└── Global Shared Content/

As you can see, we have “Content” folders under each page, which hold the content items specific to that page (an example of what would go here is a datasource for a Page Heading component). We have “Region Shared Content” folders under each region item, which hold content items that are shared across a single region (an example of what would go here is a datasource for a Header or Footer component). And finally, we have “Global Shared Content”, which holds content items shared across all pages in all regions (an example of what would go here is a datasource for a Cookie Dislaimer Alert component).

Unfortunately, when the Content Authors are editing a rendering datasource from Experience Editor, they don’t know which Content folder this datasource lives in unless they open up rendering Properties dialog and inspect the path of the datasource item, which is not convenient.

The solution to this problem is actually very easy, and it will take us longer to setup vanilla Sitecore to show the demo than to write the demo itself ☺

Let’s code!

Set up Sitecore

  • Install a vanilla instance of Sitecore (I’m using verion 8.2 update 6 right now, but the specific version doesn’t really matter for this demo)

  • Create an MVC Layout with a single placeholder called “main”
    Main Layout

Contents of MainLayout.cshtml

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div>@Html.Sitecore().Placeholder("main")</div>
</body>
</html>

  • Create a new page template called “Content Page” which uses the custom MVC Layout
    Content Page template

  • Create two new templates for content folders called “Page-Specific Content” and “Shared Content”. Make them inherit from /Templates/Common/Folder and give them distinct icons
    Content Folder templates

  • Since each instance of Content Page will need to have a Page-Specific Content folder as a child, create a branch template for this and add this branch template to the insert options of the Home page
    Content Page Branch Template

  • Insert an instance of “Shared Content” as a child of Home, and insert a couple of Content Pages. Your content tree should look like this
    Content Tree Structure

  • Create a new template for a datasource item
    Simple Datasource Item

  • Create a new View Renderings called “View rendering” and set it to reference our custom datasource. Note the value in the “Datasource Location” field; more on this later.
    Custom View Rendering
    Contents of SimpleViewRendering.cshtml

    1
    2
    3
    <div>@Html.Sitecore().Field("Text Field")</div>
    <div>@Html.Sitecore().Field("Image Field")</div>
    <div>@Html.Sitecore().Field("Link Field")</div>
  • Create a placeholder settings item that permits our View Renderings to be inserted into the “main” placeholder

Set up local and shared content

  • The value we specified for “Datasource Location” of the rendering definition item is special because it lets the Content Authors chose whether their datasource should be local or shared at the time when they are inserting new components into the page.

    1
    query:./*[@@templateid='{AA7140CB-54A5-4D69-8EE7-E370638960CF}']|query:/sitecore/content/Home/*[@@templateid='{F320B812-0603-4172-A7DF-027313B6CAA4}']
  • Open up one of your Content Page instances in Experience Editor and insert a View Rendering component. You should see a dialog like this
    Creating datasources in different Content folders

  • Insert two instances of View Rendering into the page. Make one use a datasource in the local folder and make the other use a datasource in the shared folder. Your Sitecore tree should look like this:
    Sitecore tree with local and shared datasource items

The solution - Visually differentiate between local and shared datasources

  • The box that shows up around a rendering in Experience Editor is called a “chrome”, and we can inject extra data into chromes by adding a custom getChromeData pipeline.

Contents of custom config file in `Website/App_Config/Include/‘

1
2
3
4
5
6
7
8
9
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<getChromeData>
<processor patch:after="processor[@type='Sitecore.Pipelines.GetCromeData.GetPlaceholderChromeData, Sitecore.Kernel']" type="Website.Pipelines.InjectExperienceEditorMetaData" />
</getChromeData>
</pipelines>
</sitecore>
</configuration>

Contents of the backend class referenced by the config file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using Sitecore.Data;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.GetChromeData;

namespace Website.Pipelines
{
public class InjectExperienceEditorMetaData : GetChromeDataProcessor
{
private readonly ID _sharedContentTemplateId = ID.Parse("{..id-of-your-shared-folder-template..}");

public override void Process(GetChromeDataArgs args)
{
Assert.ArgumentNotNull(args, "args");

// Potential values of args.ChromeType include: EditFrame, Field, Placeholder, Rendering
if (args.ChromeType.Equals("Rendering", StringComparison.InvariantCultureIgnoreCase))
{
var query = $"./ancestor::*[@@tid=\"{_sharedContentTemplateId}\"]";
var contentFolder = args.Item.Axes.SelectSingleItem(query);
if (contentFolder != null)
{
args.ChromeData.DisplayName += " (SHARED)";
}

// add extra checks if you have more levels of Shared Content folders
}
}
}
}

What’s happening here is we are looking for chromes of Renderings, then we use a query to figure out which Content folder the current Rendering Context item lives in, and then we append extra text to the chrome DisplayName if the Rendering Context item is in a Shared content folder.

Notes

  • We are injecting extra data when args.ChromeType == "Rendering", but there are other values that args.ChromeType can have. For example: EditFrame, Field, Placeholder, Rendering. This means you can utilize the getChromeData pipeline for solving a broad range of use cases.
  • Also note that you can adapt this code to support additional level of “Shared” content, such as my example from the introduction.
  • You can improve the code snippet by using text from the Sitecore Dictionary rather than hardcoding the text “(SHARED)”

View the result

  • Refresh the Experience Editor, and select each instance of the View Rendering components. Observe that when you select the local component, the chrome displays “View rendering”. When you select the shared component, the chrome displays “View rendering (SHARED)”.

Local datasource

Shared datasource

Bon Appétit!

A Guide to Automating Sitecore Development Tasks With Gulp

Code a la Mode banner

This post is part of a series

  1. Node.js for (.NET) Dummies
  2. A quick-start guide to using Node to bundle JavaScript modules for Sitecore
  3. A guide to automating Sitecore development tasks with Gulp

This tutorial is aimed at Sitecore/.NET developers who are new to the node universe. In the previous post we set up a Sitecore project with 2 renderings that utilized ES6 JavaScript modules. We compiled and bundled the JavaScript modules and their 3rd party dependencies using npm and rollup. Today we will improve our setup by adding automation. We will utilize the following frameworks:

The files created in this demo can be cloned from https://bitbucket.org/anastasiya29/gulp-yarn-demo

Why yarn?

Yarn is a package/dependency manager, and it is a replacement to using npm. Think of it like NuGet for your JavaScript, except that it’s waaay faster. (How fast? Crazy fast. “Yarn parallelizes operations to maximize resource utilization so install times are faster than ever” -yarnpkg.com) Plus their site is covered in cats. What more do you need?

Why gulp?

Gulp is a task runner build tool. Basically, with gulp you write simple js functions, which define different tasks. Tasks can be something like “compile my fancy shmancy react files to plain old js” or “concatenate and minify all my js files” or “copy my js files from my project directory to my Sitecore directory”. Think of it like MSBuild for your JavaScript.

Let’s code!

Prerequisites

We will re-use the View renderings and JavaScript modules created in the last post. The demo assumes the project is set up in a sibling directory to the Sitecore /Website folder. My project directory contains a src folder for JavaScript modules, package.json for managing node dependencies, and rollup.config.js for the bundling configuration. The node-modules folder will be auto-generated by running $ yarn install and the dist folder for the distribution-ready bundle will be auto-generated by our gulp task.

1
2
3
4
5
6
7
8
9
10
11
12
Data/
Website/
gulp-yarn-demo/
├── dist/ (generated)
├── node_modules/ (generated)
├── src/
│ ├── main.js
│ └── modules/
│ ├── UserGreeting.js
│ └── WebsiteVisit.js
├── package.json
└── rollup.config.js

Set up yarn

  • Use npm to install yarn globally

    1
    $ npm install --global yarn
  • npm and yarn are both package managers, and you can’t use both within the same project. If you’ve already been using npm to manage dependencies in your project, then reset by deleting the entire “node_modules/“ folder.

Install node dependencies

  • Ensure that your package.json specifies the following dependencies and then run $ yarn install
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    {
    "name": "gulp-yarn-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "",
    "license": "ISC",
    "devDependencies": {
    "del": "^3.0.0",
    "gulp": "^3.9.1",
    "gulp-rename": "^1.2.2",
    "gulp-sourcemaps": "^2.6.3",
    "rollup-plugin-commonjs": "^8.2.6",
    "rollup-plugin-inject": "^2.0.0",
    "rollup-plugin-node-resolve": "^3.0.0",
    "rollup-stream": "^1.24.1",
    "vinyl-buffer": "^1.0.1",
    "vinyl-source-stream": "^2.0.0"
    },
    "dependencies": {
    "jquery": "^3.2.1",
    "js-cookie": "^2.2.0",
    "moment": "^2.20.1"
    }
    }

This adds a new file (yarn.lock) to your file system. Yarn will manage this file.

The jquery, js-cookie, and moment dependencies are used by our Sitecore components. The rollup-plugin-commonjs, rollup-plugin-inject, and rollup-plugin-node-resolve dependencies are used during rollup compilation. These were covered in the previous post. Lets go over the new dev dependencies that were added

  • gulp (self-explanatory)
  • gulp-rename: a gulp plugin for renaming files, used during bundling process
  • gulp-sourcemaps: a gulp plugin for generates sourcemaps for transpiled or minified code, used during bundling process
  • rollup-stream: a rollup plugin that makes rollup compatible with gulp. This is necessary since rollup does not natively output a stream which we want to use as a starting point in a gulp task
  • vinyl-buffer & vinyl-source-stream: adapters that allow piping streams with other gulp vinyl
  • del: a gulp pluging that deletes files, used during MSBuild’s ‘Clean’ step

Set up gulp

  • Gulp is now installed as a development-time dependency, but we’re not quite done. Gulp needs a special file where tasks are defined. Add a file named gulpfile.js to your working directory with the following contents.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const gulp         = require('gulp');
const rollup = require('rollup-stream');
const rollupConfig = require('./rollup.config');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const sourcemaps = require('gulp-sourcemaps');
const rename = require('gulp-rename');
const del = require("del");

const paths = {
distributionJS: './dist',
sourceJS: './src',
website: './../Website'
};
const bundleName = 'bundle.js';

gulp.task('rollup', () => { // define task named 'rollup'
return rollup(rollupConfig) // fire up rollup with existing config file
.pipe(source(rollupConfig.input)) // pipe in entry-point JS file
.pipe(buffer()) // buffer the output because many gulp plugins don't support streams
.pipe(sourcemaps.init({loadMaps: true})) // fire up sourcemaps
.pipe(rename(bundleName)) // rename output to 'bundle.js'
.pipe(sourcemaps.write('.')) // write the sourcemap for 'bundle.js' to 'bundle.js.map'
.pipe(gulp.dest(paths.distributionJS)); // spit 'bundle.js' and sourcemap out in the 'dist' folder
});

Use gulp to run rollup

The rollup.config.js that we created last time needs minor tweaks to be compatible with gulp’s rollup.

Contents of rollup.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const resolve  = require('rollup-plugin-node-resolve');
const commonjs = require('rollup-plugin-commonjs');
const inject = require('rollup-plugin-inject');

module.exports = {
input: './src/main.js',
format: 'iife',
sourcemap: true,
plugins: [
resolve({
customResolveOptions: {
module: true,
moduleDirectory: 'node_modules',
browser: true,
jsnext: true
}
}),
commonjs({
include: 'node_modules/**',
}),
inject({
include: '**/*.js',
exclude: 'node_modules/**',
$: 'jquery',
Cookies: 'js-cookie',
moment: 'moment'
})
]
};

Congratulations, you’ve created your first gulp task! Run it by running $ gulp rollup from command line; verify that this adds a ‘dist’ directory to your project containing bundle.js and bundle.js.map. The bundle should contain our custom modules transpiled for browser compatibility, as well their 3rd party dependencies.

Publish rollup bundle to Sitecore instance

Our JavaScript bundle in being compiled into ‘gulp-yarn-demo/dist/‘, but MainLayout.cshtml is expecting the file in ‘Website/js’. Lets create a task to handle this publishing

1
2
3
4
5
6
7
8
gulp.task('publish-js', ['rollup'], () => {                 // define task named 'publish-js' that depends on task 'rollup'
return new Promise(resolve => { // returning a promise allows this task to run asynchronously
let bundle = paths.distributionJS + '/' + bundleName; //
gulp.src([ bundle, bundle + '.map' ]) // grab the bundle and sourcemap files
.pipe(gulp.dest(paths.website + '/js')) // and copy them into the Sitecore instance
.on("finish", resolve);
});
});

Make gulp watch JavaScript files for changes

Now that we have a task that publishes our bundle to the Sitecore instance, we can utilize one of the most useful features of gulp: the watch command. This command monitors files for changes, and runs specified code when changes are detected. For our case, we can make it run the publish-js task when changes are detected. This is superior to the typical process of copying JavaScript files into Sitecore as part of a project build step because it avoids an AppPool recycle (and the resulting Sitecore warmup time).

1
2
3
gulp.task('watch-js', ['publish-js'], () => {                 // define task named 'watch-js' that depends on task 'publish-js'
gulp.watch(paths.sourceJS + '/**/*.js', ['publish-js']); // watch all JS files in 'src' folder for changes; run 'publish-js' if changes are detected
});

Similar watch operations can be implemented for Views and Sass files.

Run gulp from Visual Studio

Visual Studio versions 2015 and higher ship with a Task Runner Explorer. Open it from View -> Other Windows -> Task Runner Explorer. As long as your gulpfile.js is in the VS project root, VS will recognize it. This lets you run gulp tasks using a Visual Studio GUI.
Visual Studio Task Runner Explorer

We can attach gulp tasks to MSBuild by adding a comment like this to the top of gulpfile.js. This will trigger a gulp task named ‘before-build’ when you perform a project Build. Similarly, a gulp task names ‘clean’ will be called when you perform a project Clean.

1
/// <binding BeforeBuild='before-build' Clean='clean' />

Add the relevant tasks to gulp

1
2
3
4
5
6
7
8
gulp.task('before-build', ['publish-js']);   // define task named 'before-build' that depends on task 'publish-js'

gulp.task('clean', () => { // define task names 'clean'
del([ // which deletes
paths.distributionJS + '/*.js', // all .js
paths.distributionJS + '/*.map' // and all .map files from dist/
]);
});

Up Next

In the next post we will dive deeper into the module loading function of main.js that’s mapping Sitecore renderings to JavaScript modules.

Bon Appétit!

A Quick-Start Guide to Using Node to Bundle JavaScript Modules for Sitecore

Code a la Mode banner

This post is part of a series

  1. Node.js for (.NET) Dummies
  2. A quick-start guide to using Node to bundle JavaScript modules for Sitecore
  3. A guide to automating Sitecore development tasks with Gulp

This tutorial is aimed at Sitecore/.NET developers who are new to the node universe. We will build Sitecore renderings that use JavaScript modules, and learn how to get started with the following frameworks:

Why npm?

Npm is a package/dependency manager. Think of it like NuGet for your JavaScript. There are other package managers available which use parallelism to speed up package downloads, but npm ships with Node and it’s good enough to get us started.

Why rollup?

Rollup is a module bundler that resolves dependencies between modules by using the ES6 module export syntax from the latest JavaScript spec. You may have heard of amd, cmd, and umd - these are all specifications for how modules in JavaScript should be defined. They’re great, but I like the idea of working with the syntax that’s been approved into the language.
Additionally, I love that rollup uses tree shaking, which is the process of removing unused code from our final bundle. Tree shaking is helpful when using 3rd party libraries that offer many more functions than we actual use.

Let’s code!

Prerequisites

Install node and npm globally on your system using the official installer - https://nodejs.org/en/download/
You can check if your system already has node and npm installed by running $ node -v and $ npm -v

Optional) Install Visual Studio Code. This is an awesome, lightweight, FREE code editor that works in both, Windows and Mac OS. It has intelliSence, debugging, and built-in git integration.

Note
If you don’t feel like following along in the creation of files, the file structure created in this demo can be cloned from https://bitbucket.org/anastasiya29/sitecore-node-demo

Set up Sitecore

  • Install a vanilla instance of Sitecore (I’m using verion 8.2 update 6 right now, but the specific version doesn’t really matter for this demo)
  • Create an MVC Layout with a single placeholder called “main”
    Main Layout
  • Create two new View Renderings called “User Greeting” and “Website Visit”
    User Greeting Rendering Website Visit Rendering
  • Create a placeholder settings item that permits our View Renderings to be inserted into the “main” placeholder
    Placeholder Settings
  • Create a new template called “Node Page” which uses the custom MVC Layout
    Node Page template
  • Insert an instance of “Node Page” into the content tree under “Home”
  • Use Experience Editor or Presentation Details to insert the two custom renderings into the page’s “main” placeholder
    Node Page content item

Contents of MainLayout.cshtml

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div>@Html.Sitecore().Placeholder("main")</div>
<script type="text/javascript" src="/js/bundle.min.js"></script>
</body>
</html>

Contents of UserGreeting.cshtml

1
2
3
4
5
<div data-module="UserGreeting"
data-locale="@Sitecore.Context.Language.Name"
data-datetime-format="LLLL" @* In a real project, this value should come from custom site settings *@
data-user="@Sitecore.Context.User.Identity.Name">
</div>

Contents of WebsiteVisit.cshtml

1
<div data-module="WebsiteVisit"></div>

Set up npm project

Select a filesystem location outside of the /Website folder to serve as the root of our node project. For this demo, I made a “src” folder as a sibling to my “Website” folder. (From now on, when I say “root level” I am referring to this directory) Initialize a new npm repository inside this directory by running

1
$ npm init

This step will ask you to name your npm project and define an entry point file. I stuck with the default index.js, but the name is up to you. A new file package.json is automatically created, which houses your custom configuration.

If using Visual Studio Code, use Open Folder to open it to your npm project root. You can create all your new files from this interface.

Create the index.js file at root level.

Set up rollup

Use npm to add rollup to your local project

1
2
3
4
$ npm install --save-dev rollup 
$ npm install --save-dev rollup-plugin-node-resolve
$ npm install --save-dev rollup-plugin-commonjs
$ npm install --save-dev rollup-plugin-inject

Using the --save-dev flag specified that this is compile-time dependency, and that we do not intend to use rollup in production. Note that the 3 plugins that we added are used for resolving dependencies of 3rd party libraries from our node_modules folder. These plugins are necessary because not all 3rd party libraries are written in ES6 syntax, and this handles the conversion for us. The “inject” plugin is for convenience - with it we don’t need to explicitly “import” any global libraries like jQuery; they are imported automatically if our code uses them.

If you peek at your package.json right now, you’ll see that new lines have been added for the rollup dependencies. This is how npm keeps track of what you have installed. If you peek inside you node_modules folder, you will see that you have dozens of modules in here now. That’s because the modules you installed have their own dependencies, and this is all managed for us automagically by npm. If your node modules ever get screwed up, you can freely delete the entire “node_modules” folder and run $ npm install, which will cause npm to re-install everything as it is defined in package.json.

Create a rollup.config.js file at root level with the following content

1
2
3
4
5
6
7
8
export default {
input: 'index.js', // This is our entry file
output: { //
file: 'Website/js/bundle.min.js', // This is where our bundle will be generated
format: 'iife', // This is the format that denotes we are compiling for browsers
sourcemap: 'inline' // Source maps are for in-browser debugging, they map code in the bundle to the original files
}
};

Your file structure should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
Data/
Website/
├── Views/
│ └── Layouts/
│ └── MainLayout.cshtml
│ └── Sitecore/
│ ├── UserGreeting.cshtml
│ └── WebsiteVisit.cshtml
src/
├── index.js
├──node_modules/
├──package.json
└──rollup.config.js

Test that everything is configured correctly by running

1
$ ./node_modules/.bin/rollup -c

This should generate a “Website/js/bundle.min.js” file

Set up external dependencies

Lets add a few external libraries to make our example more interesting.

1
2
3
$ npm install jquery
$ npm install moment
$ npm install js-cookie

We need to let rollup know about our external dependencies, so update rollup.config.js to look like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import inject from 'rollup-plugin-inject';

export default {
input: 'index.js',
output: {
file: 'Website/js/bundle.min.js',
format: 'iife',
sourcemap: 'inline'
},
plugins: [
resolve({
customResolveOptions: {
module: true,
moduleDirectory: 'node_modules',
browser: true,
jsnext: true
}
}),
commonjs({
include: 'node_modules/**',
}),
inject({
include: '**/*.js',
exclude: 'node_modules/**',
$: 'jquery',
Cookies: 'js-cookie',
moment: 'moment'
})
]
};

Set up our custom JavaSript modules

Now we are ready to write the modules that will be used by our View Renderings. Create a “modules” folder at root level and add these 2 files to it.

UserGreeting.js - This modules takes parameters passed in from the Sitecore View and uses them to print a message about the current user and datetime.

1
2
3
4
5
6
7
8
9
10
11
12
13
export default class UserGreeting {
constructor(domElement, { locale, user, datetimeFormat }) {
this.render(domElement, locale, user, datetimeFormat);
}

render(domElement, locale, user, format) {
const mom = moment();
mom.locale(locale);

const content = `Hello, ${user}! The current time is ${mom.format(format)}`;
domElement.innerHTML = content;
}
}

WebsiteVisit.js - This module uses a cookie to keep track of how many times the current user has visited the site.

1
2
3
4
5
6
7
8
9
10
11
12
13
const key = "websitevisit";

export default class WebsiteVisit {
constructor(domElement) {
const visits = parseInt(Cookies.get(key) || 0, 10) + 1;
this.render(domElement, visits);
Cookies.set(key, visits);
}

render(domElement, visits) {
domElement.innerHTML = `Thank you for visiting our site! This is visit #${visits}`;
}
}

Your file structure should now look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Data/
Website/
├── js/
│ └── bundle.min.js
├── Views/
│ └── Layouts/
│ └── MainLayout.cshtml
│ └── Sitecore/
│ ├── UserGreeting.cshtml
│ └── WebsiteVisit.cshtml
src/
├── index.js
├── modules/
│ ├── UserGreeting.js
│ └── WebsiteVisit.js
├──node_modules/
│ ├── jquery
│ ├── moment
│ └── rollup
├──package.json
└──rollup.config.js

Finally, update the entry file index.js to import our modules and map them to the Sitecore Views.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import UserGreeting from './modules/UserGreeting.js';
import WebsiteVisit from './modules/WebsiteVisit.js';

const modules = {
UserGreeting: UserGreeting,
WebsiteVisit: WebsiteVisit
};

$("[data-module]").each(function () {
const $moduleElement = $(this),
data = $moduleElement.data(),
moduleName = data.module;

if (typeof modules[moduleName] === 'function') {
var module = new modules[moduleName](this, data);
}
});

Note
It’s best practice to keep business logic out of the entry-point file; this file should be used to pull in the different modules used by your project.

Wrap Up

Compile rollup again…

1
$ ./node_modules/.bin/rollup -c

…and checkout the bundle.min.js that was produced. It contains our external dependencies and our custom modules, so the Sitecore Layout file only needs to reference this single file. Having a single file is great because you can minify it and serve the whole thing through a CDN to get really optimized page load times. (And yes, rollup has a minification plugin, but we’re not covering that in this demo).

Publish the “Node Page” from earlier and open it in frontend mode.
Node Page front-end

Go to “inspect element” and checkout the list of JavaScript files loaded on the page. It’s a small handful of files, just our custom stuff and the external libraries that we used. Compare that to the dozens of modules in your node_modules folder. None of that server-side bulk is carried over to the front-end bundle. That’s pretty cool!
Front-end sources

Up Next

In the next post I will add a task manager to our stack to automate tasks like minimizing for production and pushing code changes into the local Sitecore instance.

Bon Appétit!

Additional Resources

  • Additional configuration options for rollup are explained here
  • If you want to try out other module loaders/bundler to see which one you like best, comparable alternatives to rollup are
    • browserify
    • steal
    • system
      (You’ve probably heard of webpack too - webpack can certainly handle bundling, and much much more. It requires more configuration, so it’s overkill for simple bundling.)

Node.js for (.NET) Dummies

Code a la Mode banner

This post is part of a series

  1. Node.js for (.NET) Dummies
  2. A quick-start guide to using Node to bundle JavaScript modules for Sitecore
  3. A guide to automating Sitecore development tasks with Gulp

This post is for my fellow .NET/Sitecore developers who find themselves trying to avoid JavaScript like the plague. For the record, I do not think my peers are dummies. On the contrary, Sitecore is incredibly complex, so I think anyone who can jump into this under-documented framework, weave through constant version changes, and still produce a beautiful, usable product is a very skilled individual. Yet I’ve worked with enough Sitecore power-houses to know that JavaScript scares even the best Sitecore devs, and most often we end up with a single monolitic JavaScript file that contains scattared code covering the entire site, and is a nightmare to maintain. If this sounds familiar to you, then read on. If not, then consider yourself lucky and go give your boss a high five.

Why is JavaScript so scary?

I think there is a very simple answer to this question - it’s scary because it’s unfamiliar. Sitecore is built upon backend processing. And while we were busy in the backend during the past few year, building custom pipelines, processors, and integrations, JavaScript evolved from a Charmander to a Charizard. Now it’s no longer this little thing that we can hack through because the syntax is “close enough” to C#. It has new features - arrow functions, destructuring, exports - stuff that looks totally foreign. And with the rise of Node, it seems like you need to learn 5 different libraries before you can even start working on a module in this “ecosystem”. No one has time for that - clients pay us to implement Sitecore, not to implement JavaScript.

Why should we care about JavaScript?

We should care about JavaScript because we need it to build the fancy, dynamic components that clients want. Most clients are not willing to pay for a team of Sitecore devs and a separate team of front-end devs - we are generally expected to do it all. And even if clients were willing to add front-end devs to the team - Sitecore’s backend processing regularly spills into frontend architecture (personalization, analytics, globalization, security), so there is no clear boundary where a Sitecore dev could stop coding and hand a component over to a frontend dev.

Plus, JavaScript is fun! ☺

Why should we care about Node?

  1. Node allows us to embrace js modularity - the organization of js into neat modules which are autonomous, extendable, and testable. The notion of Modules has been added to the latest spec, so this is trully how js code should be organized. Please kill that monolithic file that has all the front-end code for the entire site and create a js library that’s as beautifully architected as your .NET application, and as easy to traverse as your Sitecore tree.
  2. Adding node allows us to easily utilize popular frameworks like React and Angular in our Sitecore components.
  3. Node makes it easy to add transpilation to our build process, so we can get fancy with TypeScript or ECMAScript 6 code.
  4. By setting up a node stack, we create a way to manage front-end code that’s independent from both, Sitecore and Visual Studio. This makes js development MUCH faster. It also frees you from having to develop in a Windows enrivonment.
  5. Even if you hate JavaScript, consider this - Node-based task runners can be used to significantly improve your .NET/Sitecore development process. For example, you can set a task runner to “watch” JavaScript, CSS, and View files for changes and automatically copy updates to your /Website directory, thus minimizing how often the project needs to be built.

So what can we do to get better?

The best way to learn is to break up existing Javascript into modules - each module should be a separate file that encapsulates the functionality of a single Sitecore component. Then create a node project with the single goal of bundling and minifying those modules (and their 3rd party dependencies like jQuery) into a single file that can be referenced by the Main Layout. This bundling is the base use-case for connecting Node with Sitecore.

There is a very large number of libraries to chose from when determining your Node stack. In fact, the number of choices feels overwhelming to a newbie (this is another reason people are afraid to get their feat wet). My advice is this - don’t get hung up on chosing the perfect set of tools, and start with the minimum toolset necessary to accomplish your immediate needs. You can always add on to the stack later. Remember that all the competing libraries (gulp vs grunt, npm vs yarn, webpack vs Babel) are server side tools; this means it’s ok to experiment and make mistakes. The worst that can happen is you make a selection that’s not optimal in terms of configuration effort and build time, but it will still get you the same end-result and the frontend won’t blow up.

That’s it for my pep-talk. In the next post I will demonstrate how easy it is to achieve what I described above.

In the meantime, I offer this diagram, which draws parallels between the JavaScript world and the .NET world that we’re used to, as a high-level overview of the popular libraries in the Node stack. Granted we are comparing apples to oranges so this is a bit crude, but this kind of thinking really helped me when I was learning.

Diagram showing items in the node stack along with the analogous parallel in .NET

Bon Appétit!

Up Next

A quick-start guide for creating a new Node project and bundling/minifying JavaScript modules for Sitecore.

Additional Resources

I highly recommend reading these two books to anyone who wants to elevate his or her JavaScript extertise.

  • Secrets of the JavaScript Ninja by John Resig - this is an older book that was written before the latest spec was released, but it is by far the best presentation of raw JavaScript - what’s special about it and how to use it. I would not love JavaScript as much as I do had I not read this book a few years ago.

  • You Don’t Know JS: ES6 & Beyond by Kyle Simpson - this is a great teacher of all the fancy new stuff in the latest spec. Understading how the new features work does not come naturally from simply looking at the code. So everyone just has to buckle down and study these. Then force yourself to use them in your code to make sure the lessons stick.

Using Powershell to Find Items That Are Missing Language Versions

Code a la Mode banner

The Missing Language Version Problem

Sitecore allows us to delete all language versions from an item, so it’s possible for items to end up in an invalid state.
Sitecore language versions menu - no versions available

How Can Items Have No Language Versions?

One way this can happen is if you programatically cleaned up all instances of a specific language. For example, if you used Powershell to delete all instances of “en” in favor of using “en-US”.

Common Powershell Commands Can’t Help Us

When there are items in the Sitecore tree that don’t have any language versions, we cannot filter out these items using common Powershell commands like Get-ChildItems. This is because Powershell implicitly uses “en” as the language when the -Language parameter is not specified. So if you try to find items which have no language versions by omiting the -Language parameter, you will end up with a list of “en” items instead. If you try using an empty string for the -Language parameter, it will return all versions of items that have language versions, which is the opposite of what we want.

The Powershell Solution

The solution is to get a bit clever, and search for items that have a collective count of all language versions equal to 0. First, I used $item.Versions.GetType() to get the .Net class name of the object I’m working with. After decompiling the class, I discovered it has a method GetVersions(bool includeAllLanguages) that returns an array of items. This is the perfect method to leverage since I can test for an empty count to determine if it’s one of the items that’s missing language versions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# point this to the root of your website
$folder = "/sitecore/content"

# you can combine the Where-Object clause with other tests
# like Name or Template if you are looking for something specific
$items = Get-ChildItem -Path $folder -recurse | `
Where-Object { $_.Versions.GetVersions($true).Count -eq 0 } | `
%{
[PSCustomObject]@{
"ID" = $_.ID
"ItemName" = $_.Name
"Item" = $_
"ItemPath" = $_.Paths.Path
}
}

# this will print out any results that it finds
$items

Bon Appétit!