Integrating Roots With Gulp

This article was contributed by Michael Kornblum. Thanks Michael!

For the past few years I have used Gulp as my main build system of choice. The reason I love using Gulp is that its well supported and has a vast ecosystem of plug ins that lets me access many of the tools I use on a day to day basis. Another reason I love using Gulp is that if a specific tool is not available, I can write a few lines of JavaScript, wrap them in a gulp task and the code will act as if its another plug in.

Recently however, I discovered Roots and have found it to be a joy to use. Formally, Roots can be considered a static site generator, much like Jekyll or Metalsmith. But Roots comes with impressive set of features that makes it more of a build system in its own right. The only issue I have is that Roots is still a fairly young project, and therefore lacks some of the tools that I've grown accustomed to in my present workflow. Since I don't have the programming chops to create a Roots extension, I've set about integrating Roots in my present Gulp build process. The result is an interesting hybrid that combines Roots out of the box functionality with some of the tools that I've come to depend on from the Gulp ecosystem.

The Gulp File

Below is the gulpfile I created for this experiment. One thing I noticed is that Roots is so feature rich that many of the tasks I usually write in a standard gulp file are not needed. The only plug-ins that I used in this file are gulp-imagemin, gulp-svg-symbols and browser-sync. The other tasks in this file are pure JavaScript taken directly from the Roots API and other sources.

Required Modules

  var gulp = require('gulp'),
  $ = require('gulp-load-plugins')(),
  Roots = require('roots'),
  path = require('path'),
  coffeeScript = require('coffee-script/register'),
  var browserSync = require('browser-sync').create(),
  var del = require('del'),
  var runSequence = require('run-sequence');

Those who have used Gulp or Node.js for any extent of time will find the above lines of code to be very familiar. Below is a rundown of each module and what it does.

Roots tasks

gulp.task('roots:init', function(){
  return Roots.new({
    path: path.join(__dirname, 'roots')
  }).done(function() {
    console.log("roots is ready");
  }, function(err){
    console.error("oh no! " + err);
  });
});

The roots:init task is used to create an instance of Roots within our project directory. Essentially, its code taken from the Roots API which was wrapped in a gulp task. The only difference between this gulp function and the original API documentation is that the progress method has been removed for a cleaner console output.

gulp.task('roots:compile', function(){
  return require('child_process').exec('roots compile', {cwd: './roots'}).on('exit', function(){
    browserSync.init({
      server : {
        baseDir : './roots/public/'
      }
    })
  });
});

The roots:compile task is a simple child process which is used to start the roots compile command. One special thing to note here is that I used a callback function to start the browswer-sync server, once compilation is compilation is complete.

gulp.task('roots:recompile', function(){
  return require('child_process').exec('roots compile', {cwd: './roots'}).on('exit', function(){browserSync.reload()});
});

Like roots:compile, roots:recompile executes the same child process, but uses a callback function on exit that reloads the browser-sync server.

gulp.task('roots:deploy', function(){
  var project = new Roots(path.join(__dirname, 'roots'));
  return project.deploy({to: 'gh-pages'})
    .done(function(){
      console.log('finished!');
    }, function(err){
      console.log('uh oh... ' + err);
  });
});

Like roots:init, roots:deploy is taken directly from the Roots API. Once again the .progress method has been removed from the task for a cleaner console output.

Standard Gulp Tasks

gulp.task('images', function(){
  return gulp.src('./images/*')
    .pipe($.imagemin({
      optimizationLevel: 3,
      progressive: true,
      interlaced: true
    }))
    .pipe(gulp.dest('./roots/assets/img'));
});

Those who have used Gulp for any extent of time will find the images task to be very familiar. It takes files from the images folder, optimizes them and pipes them into the roots/assets/img directory.

gulp.task('vectors', function(){
  return gulp.src('./vectors/*.svg')
    pipe($.symbols({
      templates: ['default-svg']
    }))
    .pipe(gulp.dest('roots/views'));
});

The vectors task takes svg files from the vectors directory, combines them into an svg-symbols file and sends it to the roots/views folder. From there the file can be included from a jade template. For more information, see this article from CSS-Tricks.

Utility Tasks

gulp.task('clean', function(cb){
  del(['./roots/public/**/*'], cb);
});

The clean task is used to clear out the roots/public directory. It is commonly run before roots:compile to assure that unneeded files from prior compilations don't make their way into current builds.

gulp.task('watch', function(){
  gulp.watch([
    'roots/views/**/*',
    'roots/assets/**/*',
    'roots/app.coffee',
    'roots/app.production.coffee',
    '!roots/public/**/*'],
    ['roots:recompile']);
  gulp.watch('images/*', ['images']);
  gulp.watch('svg/*.svg', ['vectors']);
});

The watch command is the nerve center of the gulp file. It is used to instruct gulp on how to react to files changes within your project.

The Default Task

gulp.task('default', function(){
  runSequence('clean', 'images', 'vectors', 'roots:compile', 'watch');
});

Last, but not least we come to the default task. Here the run-sequence module is used to make sure that all of our resources are built before the project is served by browser-sync server and the file watcher kicks in.

In Conclusion

Of all the build systems I've used with Gulp, Roots provided me with the best experience. Roots eliminated a lot of the boiler plate code that I usually include in my gulp files. at the same time the Roots API made its integration with Gulp easy.

To see this code in action, visit my github repository or drop by the gitter chatroom. I'd love to hear from you.