In all modern frontend assembly systems, there is a
watch
mode, in which a special daemon is launched to automatically reassemble files immediately after they are saved. He also has gulp.js, but with some features that make working with it a bit more complicated. The work of gulp.js is based on processing files as streams of data (streams). And errors in the streams can not always be intercepted, and when an error occurs, an uncaught exception will be thrown and the process will end.
To prevent this from happening, and watcher could ignore individual errors and run again and again while saving files, several additional settings need to be made in gulp, which will be discussed in this article.
What's happening?
First, let's look at what is happening and why the watcher sometimes falls. When writing a plugin for gulp, it is recommended to emit an
error
event when errors occur while the plugin is running. But the nodejs streams on which the build system is based do not allow errors to go unnoticed. In case no one has subscribed to the
error
event, an exception will be thrown so that the message reaches the user exactly. As a result, when working with gulp, developers often see such errors
events.js:72 throw er;
')
When using Continious-Integration (CI), when automatic checks are run on each commit, this can be useful (the build failed, the build was not collected, the responsible ones will receive letters). But the ever-falling watcher in local development, which needs to be constantly restarted, is a big deal.
What to do?
On the Internet, you can find questions on this topic both
on stackoverflow and
on the toaster . In the answers to the questions offer several popular solutions.
You can subscribe to the
error
event:
gulp.task('less', function() { return gulp.src('less/*.less') .pipe(less().on('error', gutil.log)) .pipe(gulp.dest('app/css')); });
You can connect the
gulp-plumber plugin, which not only subscribes to
error
for one plugin, but also automatically does this for all subsequent plug-ins connected via pipe:
gulp.task('less', function() { return gulp.src('less/*.less') .pipe(plumber()) .pipe(less()) .pipe(gulp.dest('app/css')); });
Why not do this
Although the problem looks solved, it is not. Now the build errors are intercepted, we get messages to the console about it, the running daemon does not crash, but now we have a new problem.
Suppose we have a CI server where our scripts are being built for display. In order for our assembly to work with watch, we applied one of the solutions above. But now it turns out that in case of errors in the build, our build is still marked as successful. The gulp command always ends with code 0. It turns out that for the CI assembly we don’t need to swallow errors. You can add your error handler only for watch mode, but this will complicate the description of the assembly and increase the chances of making a mistake. Fortunately, there is a solution for how to set up the assembly in the same way, but at the same time support the work in both build mode and watch mode.
Decision
In fact, gulp is trying to listen for errors in file streams. But due to the fact that usually the last call is
gulp.dest()
, writing the result to disk, and errors occur in intermediate plugins, the error does not reach the end of the chain, so the gulp will not know about it. For example, consider the following chain:
stream1.on('error', function() { console.log('error 1') }) stream2.on('error', function() { console.log('error 2') }) stream3.on('error', function() { console.log('error 3') }) stream1 .pipe(stream2) .pipe(stream3); stream1.emit('error');
Unlike, for example, promise, errors in the streams do not spread further along the chain, they need to be intercepted in each stream separately. On this occasion, there is a
pull-request in io.js , but in the current versions it is not possible to pass an error to the end of the chain. Therefore, gulp cannot catch errors in intermediate threads and we need to do this on our own.
But gulp as a description task takes not only the function that returns the stream, but also the usual callback-style function, like many APIs in node.js. In such a function, we ourselves will decide when the task has completed with an error, and when it is successful:
gulp.task('less', function(done) { gulp.src('less/*.less') .pipe(less().on('error', function(error) {
Such a description of the assembly looks a bit more, because we are now doing some of the work for the gulp, but now we are doing it right. And if you write yourself a helper function,
like this , the difference will be almost imperceptible:
gulp.task('less', wrapPipe(function(success, error) { return gulp.src('less/*.less') .pipe(less().on('error', error)) .pipe(gulp.dest('app/css')); }));
But we will get the correct messages in the console both during development and reassembly while saving, and on the CI server during the build.