Small Sips of Gulp.js: 4 Steps to Reduce Complexity

Over the past couple months, I’ve been writing a lot of JavaScript and taken a much deeper dive into the node.js world, more specifically into Gulp.js as a build system for my client side work. One thing I’ve noticed when looking at other gulpfiles is that they have a tendency to be very verbose and not very DRY.

As the line count of my own gulpfile began to push beyond the 300 mark, I started to look for patterns and ways to DRY up the code. Here are four things I’ve found useful:

1. Eliminate Unnecessary Plugins The first obvious thing to me was the nasty mess of 25 lines of require statements at the top of the file. My first step was to evaluate each plugin to determine if we actually needed it.

Why load a plugin when using child_process.exec() will accomplish the same task? Do we really need a separate file concatenator when all we are using it for is to concatenate our JavaScript? And Uglifier will handle that for us? Eliminating unnecessary plugins saved a few lines.

2. Sidestep (Most) Require Lines

The next step actually required installing a new plugin, but saved me all but three of the require lines. gulp-load-plugins is a fantastic little plugin that will take the liberty to load all plugins in node_modules whose name begins with gulp-. You only need to instantiate the plugin and the result is a POJO that you can stuff into a variable and pass around.

var $ = require('gulp-load-plugins')();

Inside of a gulp task you would call your plugin as normal, only now you access it through the object you created.

gulp.task('sass', ['clean:css'], function() {
  return gulp.src('path/to/sass')
    .pipe($.rubySass())
    .pipe(gulp.dest('dist/css')
});

This cleanly reduced 20 lines of plugin require to one line.

3. Tap the Power of Node.js Modules

Step two helped reduce the complexity in our gulpfile, but there is still the issue of the sheer file size. I find any file I have to scroll in to be too long. By the time I get to the bottom, I’ve forgotten what’s at the top. And in the case of Gulp, I was losing track of which tasks were and weren’t in the system.

For this refactoring, I turned to the node.js module system. I’m not a node.js expert by any means, but the documentation available on the module system is decent, and there are plenty of tutorials on using it. My recommendation for breaking up your Gulp file is to start small. Pull out one task at a time and ensure that everything still runs as intended. During this step in my own project, I discovered several redundant or similar tasks and was able to make them more reusuable with a few small changes.

Taking our Sass task from above and moving it into its own module could look something like this:

module.exports = function(gulp, $) {
  gulp.task('sass', ['clean:css'], function() {
    return gulp.src('path/to/sass')
      .pipe($.rubySass())
      .pipe(gulp.dest('dist/css'))
  });
}

Extracting all of our tasks out into individual modules leaves us with a gulpfile of nothing but require statements and small, single responsibility modules that are clearly defined.

4. Weed Out Duplications

While the refactoring we just did was extremely helpful in improving the readability of our build system, I found it introduced a lot of duplication. We had several command line arguments and different internal paths that were being defined in many of our tasks.

To reduce this duplication, the power of POJOs and the node.js module system came into play again. We collected all of our options at the top of our gulpfile then passed them into the module functions of each task that required them. This step gave us something similar to this:

var args = require('yargs').argv;
var opts = {
  sync: args.sync,
  p: args.p,
  n: args.n
}

Our Sass task could then use our opts object.

module.exports = function(gulp, opts, $) {
  gulp.task('sass', ['clean:css'], function() {
    return gulp.src('path/to/sass')
      .pipe($.rubySass())
      .pipe(opts.p ? gulp.dest('dist/css') : gulp.dest('.tmp/css'))
  });
}

All of our refactoring would leave our gulpfile.js like this:

var args = require('yargs').argv,
    gulp = require('gulp'),
    $    = require('gulp-load-plugins')(),
    opts = {
      sync: args.sync,
      p: args.p,
      n: args.n
    }

require('./tasks/sass')(gulp, opts, $);
require('./tasks/scripts')(gulp, opts, $);
require('./tasks/default')(gulp);

In just a few short steps, we have reduced the overall complexity and readability of our gulpfile.js. We’ve extracted our tasks into small reusable modules, each designed with a single responsibility. And reduced repeated code by extracting out a simple POJO to hold our build system state.

Related Posts

Like what you're seeing? Let's keep in touch.

Subscribe to Our Newsletter