Rinse and Repeat with Sitecore

Rinse and Repeat with Sitecore

The concept of rinse and repeat is try to keep development very iterative and automate the development process locally. Gulp and Grunt have been huge for task runners for frontend developers when it comes to preprocessors for LESS or SASS. Or, adding a linter for JavaScript development. It helps us get feedback quickly while we are developing. For Sitecore development, Team Development for Sitecore (TDS) has been a great asset by simply Build Solution in Visual Studio and TDS will deploy your site to a local sandbox.

If the developer does not have TDS, then you need an alternative or perhaps you need a lighter process that only updates assets like JavaScript and StyleSheet files and Views.

In this post, there are a few solutions that can help with development:

  • Gulp
  • MSBuild Extension Pack
  • Browser Sync

This post assumes you have some experience with Node.js and NPM.

Gulp

Outside your C# project directory, you will want to start a package.json file. Below is an example one that I created:

{
  "name": "blog-post-rinserepeat",
  "description": "install task runners for project and use MSBuild for build",
  "version": "0.0.1",
  "devDependencies": {
    "gulp": "^3.9.0",
    "gulp-cli": "^1.2.0",
    "gulp-copy": "^0.0.2",
    "gulp-shell": "^0.5.2"
  }
}

Once we have development dependencies defined, we need to run a couple commands to get setup:

npm install gulp-cli -g
npm install gulp

Next, we need to create a gulpfile.js in the same location for managing the different tasks:

var gulp = require('gulp');
gulp.task('default', []);

When we run gulp in command line we will run the default task which is nothing at this point.

We should have a structure as follows:

  • gulpfile.js
  • package.json
  • MyWebProject
    • MyWebProject.csproj
  • MyWebProject.sln

With Gulp, the packages we will need are:

  • gulp
  • gulp-cli
  • gulp-copy
  • gulp-shell

Gulp and Gulp CLI is typical, but I will explain why Gulp Copy and Gulp Shell will be used.

Gulp Copy

There are a couple ways you could go about copying files around. The two ways I will handle that is first with Gulp Copy and the second with MSBuild Extensions Pack particularly RoboCopy. Gulp Copy is useful if I'm managing dependencies to a JavaScript framework like AngularJS.

If I wanted to manage my JavaScript frameworks with NPM, then I can simply add the dependency to the package.json file:

{
  "name": "blog-post-rinserepeat",
  "description": "install task runners for project and use MSBuild for build",
  "version": "0.0.1",
  "devDependencies": {
    "gulp": "^3.9.0",
    "gulp-cli": "^1.2.0",
    "gulp-copy": "^0.0.2",
    "gulp-shell": "^0.5.2"
  },
    "dependencies": {
      "angular": "1.5.3"
  }
}

Then finally, npm install to get the dependency downloaded into node_modules. However, I don't like having to reference or copy over dependencies from node_modules manually so I can add a task to copy what I need:

var gulp = require('gulp');
gulp.task('copy', function(){
    gulp.src('./node_modules/angular/*.js')
        .pipe(gulp.dest('./MyWebProject/js/'));
});
gulp.task('default', ['copy']);

Now when we run:

gulp

The AngularJS files now copy over to ./MyWebProject/js. This helps with any upgrades in the future and should be low maintenance to manage it.

Gulp Shell

Gulp Shell is useful because I will use that to execute any command I need on command line such as msbuild. To get setup with running MSBuild from Gulp we will need to add another task for build on gulpfile.js:

var gulp = require('gulp'),
    shell = require('gulp-shell');
gulp.task('build', ['restore'], shell.task([
    'echo "Building project..."',
    'msbuild MyWebProject.csproj /p:Configuration=Debug'
  ],{ 'cwd' : 'MyWebProject'})
);

gulp.task('restore', function(){
  shell.task([
    'echo "Restore NuGet Packages..."',
    'nuget restore'
  ])
});

gulp.task('copy', function(){
    gulp.src('./node_modules/angular/*.js')
        .pipe(gulp.dest('./MyWebProject/js/'));
});
gulp.task('default', ['copy', 'build']);

Notice, I'm running msbuild MyWebProject.csproj /p:Configuration=Debug. I also have a dependency to another task I created restore. I may have NuGet packages that are needed prior to build so I can also run nuget restore prior to running msbuild. If you need to change which directory msbuild runs under you can update the option cwd which is changing the working directory.

You can run gulp since we have added the task as a sub task to the default task or we can run gulp build. You should see a Build succeeded in the output as follows:
Build succeeded

Watcher

Now, all we need is a watcher to build our project automatically when we change a file in the project. Gulp comes with a watcher. We can add that as follows:

var gulp = require('gulp'),
    shell = require('gulp-shell');
gulp.task('build', ['restore'], shell.task([
    'echo "Building project..."',
    'msbuild MyWebProject.csproj /p:Configuration=Debug'
  ],{ 'cwd' : 'MyWebProject'})
);

gulp.task('restore', function(){
  shell.task([
    'echo "Restore NuGet Packages..."',
    'nuget restore'
  ])
});

gulp.task('watch', function() {
  var watcher = gulp.watch('MyWebProject/**/*',['build'])
});

gulp.task('copy', function(){
    gulp.src('./node_modules/angular/*.js')
        .pipe(gulp.dest('./MyWebProject/js/'));
});
gulp.task('default', ['copy', 'watch']);

Here we moved the build task as a dependency to the watch task and replaced it with watch on the default task. When we make changes in Visual Studio, we can get a build on the project automatically. This is not huge because we all know we can use a keyboard shortcut with Visual Studio to build the solution. But, it is a good illustration on using a watcher to run msbuild for us.

Yet, this might prove to only enhance development. For example, a Build Solution will cause the entire w3wp process to restart and for Sitecore there is some wait time. If I wanted to limit this to my Views and other static files, this might expedite verifying changes with Sitecore development. We can change which files to watch and deploy.

We can build on this and add a couple more tasks such as deploying the project, starting IIS Express for the site and use Browser Sync to refresh the browser automatically.

Deploying

Let's add a deployment task with Gulp. To do that, we are going to utilize msbuild and MSBuild Extension Pack.

First, we need to create a tools folder and under that MSBuildExtensionPack. Next, copy the contents of the 4.0.12.0\Binaries (at the time of downloading this) from the zip file to MSBuildExtensionPack.

Second, we will create a simple site.build file under a tools folder:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
        <PropertyGroup>
        <WorkingFolder>$(MSBuildProjectDirectory)</WorkingFolder>
        <Configuration>Debug</Configuration>
        <SolutionFile>..\MyWebProject.csproj</SolutionFile>
        <DeployPath>..\public</DeployPath>
        </PropertyGroup>
        
        <Import Project="MSBuildExtensionPack\MSBuild.ExtensionPack.tasks"/>
        
        <Target Name="BuildAll" DependsOnTargets="Deploy" />

        <Target Name="Deploy">
                <Message Text="=== DEPLOY LATEST BUILD to $(DeployPath) ===" />
                <MSBuild.ExtensionPack.FileSystem.RoboCopy Source="../MyWebProject/bin" Destination="$(DeployPath)/bin" Files="*.*" Options="/MIR">
                        <Output TaskParameter="ExitCode" PropertyName="Exit" />
                        <Output TaskParameter="ReturnCode" PropertyName="Return" />
                </MSBuild.ExtensionPack.FileSystem.RoboCopy>
                <Message Text="ExitCode = $(Exit)"/>
                <Message Text="ReturnCode = $(Return)"/>
                <MSBuild.ExtensionPack.FileSystem.RoboCopy Source="../MyWebProject/Views" Destination="$(DeployPath)/Views" Files="*.*" Options="/MIR">
                        <Output TaskParameter="ExitCode" PropertyName="Exit" />
                        <Output TaskParameter="ReturnCode" PropertyName="Return" />
                </MSBuild.ExtensionPack.FileSystem.RoboCopy>
                <Message Text="ExitCode = $(Exit)"/>
                <Message Text="ReturnCode = $(Return)"/>     
                <MSBuild.ExtensionPack.FileSystem.RoboCopy Source="../MyWebProject/js" Destination="$(DeployPath)/js" Files="*.*" Options="/MIR">
                        <Output TaskParameter="ExitCode" PropertyName="Exit" />
                        <Output TaskParameter="ReturnCode" PropertyName="Return" />
                </MSBuild.ExtensionPack.FileSystem.RoboCopy>
                <Message Text="ExitCode = $(Exit)"/>
                <Message Text="ReturnCode = $(Return)"/>   
                <MSBuild.ExtensionPack.FileSystem.RoboCopy Source="../MyWebProject/" Destination="$(DeployPath)/" Files="Web.config"  Options="">
                        <Output TaskParameter="ExitCode" PropertyName="Exit" />
                        <Output TaskParameter="ReturnCode" PropertyName="Return" />
                </MSBuild.ExtensionPack.FileSystem.RoboCopy>
                <Message Text="ExitCode = $(Exit)"/>
                <Message Text="ReturnCode = $(Return)"/>                                                  
        </Target>
</Project>

This site.build file will use RoboCopy from MSBuild ExtensionPack to copy files from the build to the folder public we'll create alongside of the package.json and gulpfile.js files. Looking at this build file, we are copying the following directories, their contents and one file:

  • bin
  • Views
  • js
  • Web.config

Finally, we just need to add a gulp task to initiate the deployment to gulpfile.js:

gulp.task('deploy', shell.task([
     'echo "Deploying site...', 
     'msbuild site.build' 
   ], { 'cwd' : 'tools'})
);

Also, update the watch task to start it after the build:

gulp.task('watch', function() {
  var watcher = gulp.watch('MyWebProject/**/*',['build','deploy'])
});
Starting Server

Starting the server will only need to happen when we initially run the gulp command and not with the watcher. I use IIS Express because it is lightwight and allows us to only need to run the site when we are developing. To keep this task contained, we can start the site with a bat file:

set PATH=%PATH%;C:\Program Files\IIS Express\"
msbuild .\tools\site.build
iisexpress /path:%CD%\public

I create a start_website.bat alongside of the gulpfile.js.

Next, we can update the gulpfile.js to start the server:

gulp.task('server', shell.task([
    'echo "Start Server..."',
    'start start_website.bat'
]));

Notice we have a start command before the start_website.bat. This allows us to spin off another command window and continue with our other gulp tasks.

And, we will update the default task:

gulp.task('default', ['copy', 'server', 'watch']);
Browser Sync

Browser Sync takes a little more investment to get setup. There is some setup to get up and running with Browser Sync.

Setup

To get Browser Sync setup, you need to first update your package.json file to add the dependency:

{
  "name": "blog-post-rinserepeat",
  "description": "install task runners for project and use MSBuild for build",
  "version": "0.0.1",
  "devDependencies": {
    "browser-sync": "^2.12.3",  
    "gulp": "^3.9.0",
    "gulp-cli": "^1.2.0",
    "gulp-copy": "^0.0.2",
    "gulp-shell": "^0.5.2"
  }
}

Next, run npm install to get Browser Sync installed.

Finally, update the gulpfile.js to add a reference to the Browser Sync library:

var gulp = require('gulp'),
    shell = require('gulp-shell'),
    browserSync = require('browser-sync').create();
Proxy

Browser Sync can act as a server and serve your local static files however we need it to work with IIS Express. So, we will set it up as a proxy server.

To do this, we need to setup an initialization task in gulp:

gulp.task('init-browser-sync', function()
{
   browserSync.init({
        proxy:  "localhost:8080"        
    }); 
});

Next, we need to update the default task to initialize Browser Sync:

gulp.task('default', ['init-browser-sync', 'copy', 'server', 'watch']);

Now when we run gulp, we should see the browser open up with the address http://localhost:3000 and will proxy requests over to http://localhost:8080. Since IIS Express is starting in a separate command window, you should see the request come in for the server.

Reload

The last part we need is to tell Browser Sync to reload the page after the watch detects that we have changed a file, build the project and deploy it.

To do this, we just need to add another gulp task:

gulp.task('sync', [], function() {
    gulp.watch(["public/**/*"], {debounceDelay: 2000}).on('change', browserSync.reload);
});

And, update the watch task to start the sync task:

gulp.task('watch', function() {
  var watcher = gulp.watch('MyWebProject/**/*',['build', 'deploy', 'sync'])
});

Notice, we are watching changes to the public folder. One issue I ran into was that the browser was reloading the page twice. To remove this problem, we use the debounceDelay option. This gives the deployment enough time before Browser Sync reloads the window.

Trying it out

I have just started using these techniques with building out examples of using Sitecore and AngularJS. In my previous blog post, I started a series on getting started with AngularJS in a Sitecore solution. You can see this in action at https://github.com/edames/sitecore-angularjs/tree/part-1