May 19, 2015

Building a Better Gulpfile

By Drew Barontini

What is Gulp?

Gulp is a streaming build system that helps to automate and enhance your workflow.

Why would you use it?

You would use Gulp, if you need to:

  • Watch and compile languages like Sass and CoffeeScript
  • Minify your CSS or JavaScript
  • Concatenate files
  • Run your JavaScript through a linter
  • Run your CSS through CSS testing tools
  • Build an SVG icon system

And there is a multitude of other things you can do with a build tool like Gulp.

But what about Grunt?

While Grunt most certainly has its merits, I am a fan of Gulp because of its simple API. I prefer my tasks to have an interface that lends itself to modularization, which makes it easier to port from project to project.

Noise

Whenever I can, I try to apply conventions and standards. For Gulp, I created Noise, which is a set of common tasks that I need when using Gulp. Additionally, it has a very customizable Gulpfile.js file that can be used in various projects.

The Gulpfile.js file is the main file that Gulp uses, and it’s what loads our plugins, runs our tasks, and does all the work.

So how does Noise organize the Gulpfile?

gulp-load-plugins

Loads in any Gulp plugins and attaches them to the global scope, or an object of your choice. - GitHub Page

The first method of cleaning up the Gulpfile is with gulp-load-plugins. This plugin will load all of the Gulp plugins specified in our package.json file. Let’s look at how we do this.

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

With the plugins variable, we now have access to any of our Gulp plugins via the plugin.name interface. For example:

plugins.coffee(); // Our 'gulp-coffee' plugin
plugins.sass(); // Our 'gulp-sass' plugin

One caveat here is that gulp-load-plugins looks for a specific naming pattern in the Gulp plugin, as specified in the package.json file. By default, gulp-* and gulp.* are the patterns. If a plugin doesn’t follow the naming convention, another pattern can be added to look for.

To illustrate the benefits of this approach, here’s how we would load our plugins without gulp-load-plugins:

var gulp = require('gulp');
var coffee = require('gulp-coffee');
var sass = require('gulp-sass');
// ...

This works fine when we only have a few plugins, but as we add more and more, the necessity of this plugin grows. Also, with the plugin. interface for calling the individual plugins, we make our code more expressive and explicit.

Options Object

The next method I use within the Gulpfile is creating an options object to hold all of the configuration options used across the tasks.

var options = {
  build: {},
  coffee: {},
  css: {},
  // ...
};

For example, options.build looks like this:

var options = {
  build: {
    tasks: ['minify:css', 'minify:js'],
    destination: 'build/',
  },
  // ...
};

With these configuration options in place, we can use them to clean up our tasks, like so:

// -------------------------------------
//   Task: Build
// -------------------------------------

gulp.task('build', function() {
  options.build.tasks.forEach(function(task) {
    gulp.start(task);
  });
});

With something as simple as the options object, we can add and change the configuration options to suit our setup. Moreover, we can easily condense repetitive tasks with loops, as you can see above with our forEach loop.

Documentation

Ah, documentation. We all love writing it, don’t we? Okay, maybe not, but it’s still an invaluable part of the process. Some code can be considered self-documenting, but it’s always important to provide proper documentation. It’s vital not only to another developer, but also the future you. Let’s be honest; we frequently forget why we do certain things.

At the top of the file, we can see the tasks available to us.

// *************************************
//
//   Gulpfile
//
// *************************************
//
// Available tasks:
//   `gulp`
//   `gulp build`
//   `gulp compile:coffee`
//   `gulp compile:sass`
//   `gulp icons`
//   `gulp lint:coffee`
//   `gulp minify:css`
//   `gulp minify:js`
//   `gulp test:css`
//   `gulp test:js`
//
// *************************************

After the available tasks are listed, there is documentation for all of the Gulp plugins that are included. There is also a description of what the plugin does.

// -------------------------------------
//   Modules
// -------------------------------------
//
// gulp              : The streaming build system
// gulp-autoprefixer : Prefix CSS
// gulp-coffee       : Compile CoffeeScript files
// gulp-coffeelint   : Lint your CoffeeScript
// gulp-concat       : Concatenate files
// gulp-csscss       : CSS redundancy analyzer
// gulp-jshint       : JavaScript code quality tool
// gulp-load-plugins : Automatically load Gulp plugins
// gulp-minify-css   : Minify CSS
// gulp-parker       : Stylesheet analysis tool
// gulp-plumber      : Prevent pipe breaking from errors
// gulp-rename       : Rename files
// gulp-sass         : Compile Sass
// gulp-svgmin       : Minify SVG files
// gulp-svgstore     : Combine SVG files into one
// gulp-uglify       : Minify JavaScript with UglifyJS
// gulp-util         : Utility functions
// gulp-watch        : Watch stream
// run-sequence      : Run a series of dependent Gulp tasks in order
//
// -------------------------------------

And for the sake of formatting, each task is prefixed with a comment block.

// -------------------------------------
//   Task: Build
// -------------------------------------

// ...

I’m an organization nut, so I go a little crazy with comments and formatting, but even with small amounts of formatting and documentation you can significantly increase your code’s readability.

Prefixing Tasks

The final method for cleaning up the Gulpfile is by prefixing tasks.

// -------------------------------------
//   Task: Compile: Coffee
// -------------------------------------

gulp.task('compile:coffee', function() {
  // ...
});

// -------------------------------------
//   Task: Compile: Sass
// -------------------------------------

gulp.task('compile:sass', function() {
  // ...
});

Since we are running compilation in multiple tasks, the compile prefix is added to each task. That way, it’s easier to keep common functionality grouped together.

The same technique is used for the minify and test tasks, adding those as prefixes.

With those prefixes in place, the interface is more explicit when running the commands in the shell.

$ gulp minify:css
$ gulp minify:js
$ gulp test:css
$ gulp test:js

That’s All, Folks

That’s a quick look at some ways that we can clean up our Gulpfile to be more modular, customizable, and readable.

© 2019 Drew Barontini — Building products under Drewbio, LLC