Во всех современных системах сборки фронтенда есть режим watch
, при котором запускается специальный демон для автоматической пересборки файлов сразу после их сохранения. Также он есть и gulp.js, но с некоторыми особенностями, делающими работу с ним немного сложней. Работа gulp.js основана на обработке файлов как потоков данных (streams). И ошибки в потоках не всегда можно перехватить, а когда ошибка случается, то будет выброшено неперехваченное исключение и процесс завершится.
Чтобы этого не происходило, и watcher мог игнорировать отдельные ошибки и запускаться снова и снова при сохранении файлов, в gulp нужно сделать несколько дополнительных настроек, о которых и расскажется в этой статье.
Что происходит?
Для начала разберемся что происходит и почему же watcher иногда падает. При написании плагина к gulp рекомендуется испускать событие error
при возникновении ошибок во время работы плагина. Но nodejs-потоки, на которых основана система сборки, не позволяют ошибкам оставаться незамеченными. В случае, если на событие error
никто не подписался, выбросится исключение, чтобы сообщение точно достигло пользователя. В результате, при работе с gulp разработчики часто видят такие ошибки
events.js:72 throw er; // Unhandled 'error' event
При применении Continious-Integration (CI), когда на каждый коммит запускаются автоматические проверки, это может быть и полезным (сборка провалилась, билд не собрался, ответственные получат письма). Но вечнопадающий watcher в локальной разработке, который нужно постоянно перезапускать – это большая неприятность.
Что делать?
В интернете можно найти вопросы этой теме как на stackoverflow, так и на тостере. В ответах на вопросы предлагают несколько популярных решений.
Можно подписаться на событие error
:
gulp.task('less', function() { return gulp.src('less/*.less') .pipe(less().on('error', gutil.log)) .pipe(gulp.dest('app/css')); });
Можно подключить плагин gulp-plumber, который не только подпишется на error
для одного плагина, но и автоматически сделает это и для всех последующих плагинов, подключенных через pipe:
gulp.task('less', function() { return gulp.src('less/*.less') .pipe(plumber()) .pipe(less()) .pipe(gulp.dest('app/css')); });
Почему так не надо делать
Несмотря на то, что проблема выглядит как решенная, это не так. Теперь ошибки сборки перехватываются, мы получаем в консоль сообщения об этом, запущенный демон-watcher не падает, но у нас теперь есть новая проблема.
Допустим у нас есть CI-сервер, где идет сборка наших скриптов для выкладки. Чтобы наша сборка работала вместе с watch, мы применили одно из решений выше. Но теперь оказывается, что при ошибках в сборке наш билд все равно отмечается как успешный. Команда gulp всегда завершается с кодом 0. Получается, для CI-сборки нам не нужно проглатывать ошибки. Можно добавлять свой обработчик ошибок только для watch режима, но это усложнит описание сборки и повысит шансы допустить ошибку. К счастью, есть решение, как настроить сборку одинаково, но при этом поддержать работу и в режиме билда и в режиме watch.
Решение
На самом деле gulp пытается слушать ошибки в потоках файлов. Но из-за того, что обычно последним идет вызов gulp.dest()
, записывающий результат на диск, а ошибки случаются в промежуточных плагинах, то ошибка не достигает конца цепочки, поэтому gulp о ней не узнает. Например рассмотрим такую цепочку:
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'); // в консоли выведется только "error 1"
В отличие от, например, promise, ошибки в потоках не распространяются дальше по цепочке, их нужно перехватывать в каждом потоке отдельно. По этому поводу есть pull-request в io.js, но в нынешних версиях передать ошибку в конец цепочки не получится. Поэтому gulp не может перехватить ошибки промежуточных потоках и нам это нужно делать самостоятельно.
Зато gulp в качестве описания task принимает не только функцию, возвращающую поток, но и обычную callback-style функцию, как и многие API в node.js. В такой функции мы сами будем решать, когда задача завершилось с ошибкой, а когда успешно:
gulp.task('less', function(done) { gulp.src('less/*.less') .pipe(less().on('error', function(error) { // у нас ошибка done(error); })) .pipe(gulp.dest('app/css')) .on('end', function() { // у нас все закончилось успешно done(); }); });
Такое описание сборки выглядит немного больше, потому что мы теперь делаем часть работы за gulp, но теперь мы ее делаем правильно. А если написать себе вспомогательную функцию, вроде такой, то разница будет практически незаметна:
gulp.task('less', wrapPipe(function(success, error) { return gulp.src('less/*.less') .pipe(less().on('error', error)) .pipe(gulp.dest('app/css')); }));
Зато мы получим правильные сообщения в консоли как во время разработки и пересборке при сохранении, так и на CI-сервере во время билда.
ссылка на оригинал статьи http://habrahabr.ru/post/259225/
Добавить комментарий