Prevent Errors from breaking Gulp watch

gulp-plumber, custom error handler, gulp-prettyerror

As an intermediate javascript developer, you may using gulp these days – a great and straightforward streaming build system with a lot of advantages compared to grunt. For example, i’ve switched from a bunch of custom, ANT based scripts to gulp for the next EnlighterJS major version and it saves a lot of time!

Especially the watch tasks, which can automatically run partial build tasks when updating files. But this can cause serious trouble during the development process, in case there are some errors in your code – the task will break and you have to restart it manually.

Running a watch task#

In case a subtask failed, the whole watch task will stop without a proper error handler set. Well, you known this issue..

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
File /home/andi/Development/Javascript/EnlighterJS3/Source/Language/Xml.js was changed, running tasks...
[09:36:24] Starting 'jsx'...
[09:36:24] Finished 'jsx' after 8.59 ms
[09:36:24] Starting 'browser-js'...
events.js:141
throw er; // Unhandled 'error' event
^
SyntaxError: /home/andi/Development/Javascript/EnlighterJS3/.tmp/EnlighterJS.browser.js: Unexpected token (1070:12)
1068 | type: 'x1',
1069 | filter:
> 1070 | }
| ^
1071 | ];
1072 | }
1073 | };
at Parser.pp.raise (babel-core/node_modules/babylon/index.js:1413:13)
at Parser.pp.unexpected (babel-core/node_modules/babylon/index.js:2895:8)
at Parser.pp.parseExprAtom (babel-core/node_modules/babylon/index.js:746:12)
at Parser.pp.parseExprSubscripts (babel-core/node_modules/babylon/index.js:501:19)
at Parser.pp.parseMaybeUnary (babel-core/node_modules/babylon/index.js:481:19)
at Parser.pp.parseExprOps (babel-core/node_modules/babylon/index.js:412:19)
at Parser.pp.parseMaybeConditional (babel-core/node_modules/babylon/index.js:394:19)
at Parser.pp.parseMaybeAssign (babel-core/node_modules/babylon/index.js:357:19)
at Parser.pp.parseObjPropValue (babel-core/node_modules/babylon/index.js:1013:99)
at Parser.pp.parseObj (babel-core/node_modules/babylon/index.js:986:10)
File /home/andi/Development/Javascript/EnlighterJS3/Source/Language/Xml.js was changed, running tasks... [09:36:24] Starting 'jsx'... [09:36:24] Finished 'jsx' after 8.59 ms [09:36:24] Starting 'browser-js'... events.js:141 throw er; // Unhandled 'error' event ^ SyntaxError: /home/andi/Development/Javascript/EnlighterJS3/.tmp/EnlighterJS.browser.js: Unexpected token (1070:12) 1068 | type: 'x1', 1069 | filter: > 1070 | } | ^ 1071 | ]; 1072 | } 1073 | }; at Parser.pp.raise (babel-core/node_modules/babylon/index.js:1413:13) at Parser.pp.unexpected (babel-core/node_modules/babylon/index.js:2895:8) at Parser.pp.parseExprAtom (babel-core/node_modules/babylon/index.js:746:12) at Parser.pp.parseExprSubscripts (babel-core/node_modules/babylon/index.js:501:19) at Parser.pp.parseMaybeUnary (babel-core/node_modules/babylon/index.js:481:19) at Parser.pp.parseExprOps (babel-core/node_modules/babylon/index.js:412:19) at Parser.pp.parseMaybeConditional (babel-core/node_modules/babylon/index.js:394:19) at Parser.pp.parseMaybeAssign (babel-core/node_modules/babylon/index.js:357:19) at Parser.pp.parseObjPropValue (babel-core/node_modules/babylon/index.js:1013:99) at Parser.pp.parseObj (babel-core/node_modules/babylon/index.js:986:10)
File /home/andi/Development/Javascript/EnlighterJS3/Source/Language/Xml.js was changed, running tasks...
[09:36:24] Starting 'jsx'...
[09:36:24] Finished 'jsx' after 8.59 ms
[09:36:24] Starting 'browser-js'...

events.js:141
      throw er; // Unhandled 'error' event
      ^
SyntaxError: /home/andi/Development/Javascript/EnlighterJS3/.tmp/EnlighterJS.browser.js: Unexpected token (1070:12)
  1068 |                 type: 'x1',
  1069 |                 filter:
> 1070 |             }
       |             ^
  1071 |         ];
  1072 |     }
  1073 | };
    at Parser.pp.raise (babel-core/node_modules/babylon/index.js:1413:13)
    at Parser.pp.unexpected (babel-core/node_modules/babylon/index.js:2895:8)
    at Parser.pp.parseExprAtom (babel-core/node_modules/babylon/index.js:746:12)
    at Parser.pp.parseExprSubscripts (babel-core/node_modules/babylon/index.js:501:19)
    at Parser.pp.parseMaybeUnary (babel-core/node_modules/babylon/index.js:481:19)
    at Parser.pp.parseExprOps (babel-core/node_modules/babylon/index.js:412:19)
    at Parser.pp.parseMaybeConditional (babel-core/node_modules/babylon/index.js:394:19)
    at Parser.pp.parseMaybeAssign (babel-core/node_modules/babylon/index.js:357:19)
    at Parser.pp.parseObjPropValue (babel-core/node_modules/babylon/index.js:1013:99)
    at Parser.pp.parseObj (babel-core/node_modules/babylon/index.js:986:10)

Plumber as Helper#

Generally, you have to add a separate onError callback to all piped task. It produces a lot of coding overhead. Plumber can do this job for you and also takes care of the streams –  Briefly it replaces pipe method and removes standard onerror handler on error event.

Example – Catch all errors and stop pipe processing#

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var plumber = require('gulp-plumber');
gulp.src('./src/*.scss')
.pipe(plumber(function(error){
console.log("Error happend!", error.message);
this.emit('end');
}))
.pipe(sass())
.pipe(uglify())
.pipe(plumber.stop())
.pipe(gulp.dest('./dist'));
var plumber = require('gulp-plumber'); gulp.src('./src/*.scss') .pipe(plumber(function(error){ console.log("Error happend!", error.message); this.emit('end'); })) .pipe(sass()) .pipe(uglify()) .pipe(plumber.stop()) .pipe(gulp.dest('./dist'));
var plumber = require('gulp-plumber');

gulp.src('./src/*.scss')
    .pipe(plumber(function(error){
        console.log("Error happend!", error.message);
        this.emit('end');
    }))
    .pipe(sass())
    .pipe(uglify())
    .pipe(plumber.stop())
    .pipe(gulp.dest('./dist'));

Putting it all together#

Well, the output doesn’t look very nice. To obtain the gulp output appearance (timeline, colors), we can use the

log()
log() method of the gulp-util package. Additionally, the plumber() call is wrapper into to helper function to use it in multiple tasks.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var gulp = require("gulp");
var gutil = require('gulp-util');
var gplumber = require('gulp-plumber');
// our custom error handler
var errorHandler = function(){
// default appearance
return gplumber(function(error){
// add indentation
var msg = error.codeFrame.replace(/\n/g, '\n ');
// output styling
gutil.log('|- ' + gutil.colors.bgRed.bold('Build Error in ' + error.plugin));
gutil.log('|- ' + gutil.colors.bgRed.bold(error.message));
gutil.log('|- ' + gutil.colors.bgRed('>>>'));
gutil.log('|\n ' + msg + '\n |');
gutil.log('|- ' + gutil.colors.bgRed('<<<'));
});
};
// Out Tasks
gulp.task('jsx', function () {
return gulp.src(['Source/Views/*.jsx'])
.pipe(errorHandler())
.pipe(...)
;
};
gulp.task('js', function () {
return gulp.src(['Source/**.js'])
.pipe(errorHandler())
.pipe(...)
;
};
var gulp = require("gulp"); var gutil = require('gulp-util'); var gplumber = require('gulp-plumber'); // our custom error handler var errorHandler = function(){ // default appearance return gplumber(function(error){ // add indentation var msg = error.codeFrame.replace(/\n/g, '\n '); // output styling gutil.log('|- ' + gutil.colors.bgRed.bold('Build Error in ' + error.plugin)); gutil.log('|- ' + gutil.colors.bgRed.bold(error.message)); gutil.log('|- ' + gutil.colors.bgRed('>>>')); gutil.log('|\n ' + msg + '\n |'); gutil.log('|- ' + gutil.colors.bgRed('<<<')); }); }; // Out Tasks gulp.task('jsx', function () { return gulp.src(['Source/Views/*.jsx']) .pipe(errorHandler()) .pipe(...) ; }; gulp.task('js', function () { return gulp.src(['Source/**.js']) .pipe(errorHandler()) .pipe(...) ; };
var gulp = require("gulp");
var gutil = require('gulp-util');
var gplumber = require('gulp-plumber');
// our custom error handler
var errorHandler = function(){
    // default appearance
    return gplumber(function(error){
        // add indentation
        var msg = error.codeFrame.replace(/\n/g, '\n    ');

        // output styling
        gutil.log('|- ' + gutil.colors.bgRed.bold('Build Error in ' + error.plugin));
        gutil.log('|- ' + gutil.colors.bgRed.bold(error.message));
        gutil.log('|- ' + gutil.colors.bgRed('>>>'));
        gutil.log('|\n    ' + msg + '\n           |');
        gutil.log('|- ' + gutil.colors.bgRed('<<<'));
    });
};

// Out Tasks
gulp.task('jsx', function () {
    return gulp.src(['Source/Views/*.jsx'])
        .pipe(errorHandler())
        .pipe(...)
    ;
};
gulp.task('js', function () {
    return gulp.src(['Source/**.js'])
        .pipe(errorHandler())
        .pipe(...)
    ;
};

Finally, the Quick Way#

To quickly enable this functionality, i’ve create the gulp-prettyerror package, which wraps the code above into a single, easy to use function:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var prettyError = require('gulp-prettyerror');
// default release build
gulp.task('browser-js', ['jsx'], function (){
return gulp.src(['Source/Lib/**/*.js', 'Source/Browser/**/*.js', 'Source/Engine/**/*.js', '.tmp/Views.js'].concat(languageSources))
.pipe(prettyError())
// create sourcemaps for development
.pipe(sourcemaps.init())
// concat all files
.pipe(concat('EnlighterJS.browser.js'));
});
var prettyError = require('gulp-prettyerror'); // default release build gulp.task('browser-js', ['jsx'], function (){ return gulp.src(['Source/Lib/**/*.js', 'Source/Browser/**/*.js', 'Source/Engine/**/*.js', '.tmp/Views.js'].concat(languageSources)) .pipe(prettyError()) // create sourcemaps for development .pipe(sourcemaps.init()) // concat all files .pipe(concat('EnlighterJS.browser.js')); });
var prettyError = require('gulp-prettyerror');

// default release build
gulp.task('browser-js', ['jsx'], function (){
    return gulp.src(['Source/Lib/**/*.js', 'Source/Browser/**/*.js', 'Source/Engine/**/*.js', '.tmp/Views.js'].concat(languageSources))
        .pipe(prettyError())

        // create sourcemaps for development
        .pipe(sourcemaps.init())

        // concat all files
        .pipe(concat('EnlighterJS.browser.js'));
});

The Result#

screenshot1