Prevent Errors from Crashing Gulp Watch

Gulp is an amazing tool. Here are some tips to keep it running smoothly.

Developers are a lazy bunch. Or at least I assume we are. Because of this reason we tend to build tools that make our work faster. From highly customizable editors to task runners.

Today's article is on gulp. If you don't know what gulp is, we have a good article and course that serves as a good intro to gulp.

With gulp, we can build tasks that automatically compile SASS, start a laravel server, live reload the browser, transpile es6 to es5, etc.

But, just as with life, sh&^ happens. A silly little punctuation (; I've got my eyes on you) can easily mess up our work. Thankfully, there's a few languages out there like Javascript which is very forgiving. Nonetheless, mistakes can happen.

Since we have a "gulp watcher" that watches our project and runs defined tasks when we make any change, an error can easily break our pipeline.

Creating and Watching a Gulp Task

Watching in Gulp refers to triggering a task when a change is made to a project's source.

So, before we watch a task, let's create a task that we will use as our example throughout this tutorial. The task we will create is a SCSS compilation task.

We can create a new working directory, name it whatever you want. We can now create our gulpfile.js in our working directory. Then we add our build task. Before we define our task, we need to install our dependencies.

For this article, here is a list of our dependencies.

{
  "private": true,
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-notify": "^2.2.0",
    "gulp-plumber": "^1.1.0",
    "gulp-sass": "^2.3.2",
    "gulp-util": "^3.0.7"
  }
}

Now that we have our dependency list, we can run npm install or if you have the new yarn package manager based on npm, you can run yarn install.

In the gulpfile, we can then define our gulp task.

const gulp = require('gulp');
const sass = require('gulp-sass');

gulp.task('compile-scss', function () {
    gulp.src('scss/main.scss')
        .pipe(sass())
        .pipe(gulp.dest('css/'));
});

So from the command line, we can run gulp compile-scss and our sass file should be compiled.

Watching a Task

Now that we have a task defined, let's trigger the file whenever we make a change to the project's source.

gulp.task('watch', function () {
    gulp.watch('scss/**/*.scss', ['compile-scss']);
});

From the terminal, we can run gulp watch and whenever a file ending with .scss extension in any folder within the scss directory gets changed, compile-scss task is run.

Prevent Errors from Breaking Tasks

We've got our task and watcher up and running, but if an error occurs in our SCSS file, the gulp watcher gets terminated. We then have to go back to the terminal and type gulp watch again. This gets very annoying really fast. A silly little ; can break our watcher.

To avoid breakage like this, we can one of three things:

  1. Swallow the Error.
  2. Gulp Util.
  3. Gulp Plumber.

Swallow the Error

One a way to go about dealing with errors is to "swallow the error". The error(s) will be taken in by the application to prevent the task from breaking. Basically, errors will not be reported and the task will keep running.

Since gulp sends a lot of events, we can hook into the error event of the task we don't want to fail.

gulp.task('compile-scss', function () {
    gulp.src('scss/main.scss')
        .pipe(sass())
        .on('error', function (err) {
            console.log(err.toString());

            this.emit('end');
        })
        .pipe(gulp.dest('css/'));
});

As you can see above, from the on listener on the task. The on event listener takes in two parameters: the event and a function to be triggered when the event gets called. The function that gets called takes in the error object. We then log the stringified version of the error to the terminal.

It is absolutely important to this.emit('end'), if this event is not triggered, the next pipe in this task pipeline never gets called, and the buffer will be left open.

Gulp Util

This method involves using the gulp-util plugin.

The gulp-util plugin provides a lot of helpful methods, one of them is log. With this method, we can log the error to the terminal. To use this, we attach an error event listener to the pipe.

var gutil = require('gulp-util');

gulp.task('compile-scss', function () {
    gulp.src('scss/main.scss')
        .pipe(sass())
        .on('error', gutil.log)
        .pipe(gulp.dest('css/'));
});

But this method also requires us to go through each pipe in the pipeline and attach .on('error', gutil.log) listener to all tasks. Something like this.

gulp.task('compile-scss', function () {
    gulp.src('scss/main.scss')
        .pipe(sass())
        .on('error', gutil.log)
        .pipe(autoprefixer())
        .on('error', gutil.log)
        .pipe(gulp.dest('css/'));
});

Gulp Plumber

Out of all three methods, this is my favorite. With gulp-plumber, we don't need to go to each pipe and add a listener, we can just add a global listener to the task and have a meaningful error displayed.

var plumber = require('gulp-plumber');

gulp.task('compile-scss', function () {
    gulp.src('scss/main.scss')
        .pipe(plumber())
        .pipe(sass())
        .pipe(autoprefixer())
        .pipe(cssnano())
        .pipe(gulp.dest('css/'));
});

We can have multiple pipes in this task and still only ever need to call plumber once.

Alerting the User to Errors

Now that we can see the errors without breaking out of watch, we need to find a way to get some kind of notification when an error occurs. There are several ways to do this, but I will cover only one method.

The method I will cover in this article: will play a beeping sound when an error occurs, and also show a system notifcation that looks like this.

This notification looks different according to your operating system.

To get this feature to work, we need to extend the gulp-plumber plugin. So in our gulp task, we update our call to plumber.

gulp.task('scss', function () {
    gulp.src('scss/main.scss')
        .pipe(plumber({ errorHandler: function() {
            // do stuff here
        }}))
        .pipe(sass())
        .pipe(gulp.dest('css'));
});

Notice, we pass an object that has an errorHandler property that takes a closure to plumber. We can then call our notify plugin in that closure.

var notify = require('gulp-notify');

gulp.task('scss', function () {
    gulp.src('scss/main.scss')
        .pipe(plumber({ errorHandler: function(err) {
            notify.onError({
                title: "Gulp error in " + err.plugin,
                message:  err.toString()
            })(err);
        }}))
        .pipe(sass())
        .pipe(gulp.dest('css'));
});

We call the notify plugin and pass it an object that has a title and message property. Now, when an error occurs, a notification is triggered. To play a beeping sound, we can use gulp-util for that.

var notify = require('gulp-notify');

gulp.task('scss', function () {
    gulp.src('scss/main.scss')
        .pipe(plumber({ errorHandler: function(err) {
            notify.onError({
                title: "Gulp error in " + err.plugin,
                message:  err.toString()
            })(err);

            // play a sound once
            gutil.beep();
        }}))
        .pipe(sass())
        .pipe(gulp.dest('css'));
});

Now, when an error occurs, we get both sound and system notification and then you can check your terminal for more information.

Conclusion

The configuration in this article should be suitable for most users, but if you have any suggestions/improvements, please let us know in the comments.

Samuel Oloruntoba

Self-proclaimed full-stack web developer and a quasi-academic. I work mostly on the backend (PHP and Node) with a recent enthusiasm for frontend development (React, SVG, HTML5 Canvas).