防止错误导致gulp watch崩溃/崩溃

184

我正在运行 gulp 3.6.2,并有以下任务,这是从在线示例设置的

gulp.task('watch', ['default'], function () {
  gulp.watch([
    'views/**/*.html',        
    'public/**/*.js',
    'public/**/*.css'        
  ], function (event) {
    return gulp.src(event.path)
      .pipe(refresh(lrserver));
  });

  gulp.watch(['./app/**/*.coffee'],['scripts']);
  gulp.watch('./app/**/*.scss',['scss']);
});

每当我的 CoffeeScript 出现错误时,gulp watch 就会停止——显然不是我想要的结果。

如其他地方所建议的那样,我尝试了这个

gulp.watch(['./app/**/*.coffee'],['scripts']).on('error', swallowError);
gulp.watch('./app/**/*.scss',['scss']).on('error', swallowError);
function swallowError (error) { error.end(); }

但它似乎不起作用。

我做错了什么?


响应@Aperçu的答案,我修改了我的swallowError方法,并尝试了以下内容:

gulp.task('scripts', function () {
  gulp.src('./app/script/*.coffee')
    .pipe(coffee({ bare: true }))
    .pipe(gulp.dest('./public/js'))
    .on('error', swallowError);
});

重新启动后,在我的coffee文件中创建了一个语法错误。出现了相同的问题:

[gulp] Finished 'scripts' after 306 μs

stream.js:94
      throw er; // Unhandled stream error in pipe.
            ^
Error: W:\bariokart\app\script\trishell.coffee:5:1: error: unexpected *
*
^
  at Stream.modifyFile (W:\bariokart\node_modules\gulp-coffee\index.js:37:33)
  at Stream.stream.write (W:\bariokart\node_modules\gulp-coffee\node_modules\event-stream\node_modules\through\index.js:26:11)
  at Stream.ondata (stream.js:51:26)
  at Stream.EventEmitter.emit (events.js:95:17)
  at queueData (W:\bariokart\node_modules\gulp\node_modules\vinyl-fs\node_modules\map-stream\index.js:43:21)
  at next (W:\bariokart\node_modules\gulp\node_modules\vinyl-fs\node_modules\map-stream\index.js:71:7)
  at W:\bariokart\node_modules\gulp\node_modules\vinyl-fs\node_modules\map-stream\index.js:85:7
  at W:\bariokart\node_modules\gulp\node_modules\vinyl-fs\lib\src\bufferFile.js:8:5
  at fs.js:266:14
  at W:\bariokart\node_modules\gulp\node_modules\vinyl-fs\node_modules\graceful-fs\graceful-fs.js:104:5
  at Object.oncomplete (fs.js:107:15)

3
请查看官方的 gulp 教程,网址为 https://github.com/gulpjs/gulp/tree/master/docs/recipes。其中有一个关于使用 stream-combiner2 组合流来处理错误的教程。这是官方给出的答案! - danday74
8个回答

263

你的 swallowError 函数应该长这样:

function swallowError (error) {

  // If you want details of the error in the console
  console.log(error.toString())

  this.emit('end')
}

我认为您需要将此函数绑定到正在失败的任务的error事件上,而不是watch任务,因为问题并不在那里,您应该在可能失败的每个任务(例如插件在缺少;或其他内容时中断)上设置此错误回调函数,以防止watch任务停止。

示例:

gulp.task('all', function () {
  gulp.src('./app/script/*.coffee')
    .pipe(coffee({ bare: true }))
    .on('error', swallowError)
    .pipe(gulp.dest('./public/js'))

  gulp.src('css/*.scss')
    .pipe(sass({ compass: true }))
    .on('error', swallowError)
    .pipe(cssmin())
    .pipe(gulp.dest('dist'))
})

或者,如果您不介意包含另一个模块,您可以使用gulp-utillog函数来避免在gulpfile中声明额外的函数:

.on('error', gutil.log)
但我可以建议看一下非常棒的gulp-plumber插件,它用于移除error事件的onerror处理程序,以避免流程中断。 它非常简单易用,可以防止您捕获所有可能失败的任务。
gulp.src('./app/script/*.coffee')
  .pipe(plumber())
  .pipe(coffee({ bare: true }))
  .pipe(gulp.dest('./public/js'))

更多信息请查看该插件创建者的这篇文章


2
你可能想要编辑你的答案,以反映最终正确的做法,这样未来发现这个问题的人就不必阅读评论链了。 - George Mauer
1
这一切都很好,但为什么我要编写自己的错误处理程序呢?我使用gulp或grunt或其他预处理工具的原因是它可以为我完成繁琐的工作。我的代码中可能会出现语法错误(这是不可避免的),但这不应该使整个系统停止运行。与另一个预处理器Prepros的GUI相比较-这个工具可以准确地告诉您错误的位置,并且在出现问题时不会挂起或退出。 - Kokodoko
如果你想要控制台的蜂鸣声,请使用:console.log("\x07"); - gkiely
1
很惊奇,尽管它是官方的Gulp配方,但在整个过程中没有人提到过steam-combiner2!Plumber也不错,但我总是更喜欢遵循Gulp配方,这些配方由Gulp维护并可以在https://github.com/gulpjs/gulp/tree/master/docs/recipes上查看。 - danday74
1
面对同样的难题,我决定使用一个包装器来包裹 gulp.watch(),并在观察函数的结果中添加 .on('error', function() { this.emit('end') }),因为我特别希望让观察任务具有抗挂起能力。这个方法非常有效,而且失败的单个任务仍然会打印它们自己的错误消息。请参见代码:https://github.com/specious/specious.github.io/commit/f8571d28 - webninja
显示剩余11条评论

20

上述示例对我无效。以下的方法有效:

var plumber = require('gulp-plumber');
var liveReload = require('gulp-livereload');
var gutil = require('gulp-util');
var plumber = require('gulp-plumber');
var compass = require('gulp-compass');
var rename = require('gulp-rename');
var minifycss = require('gulp-minify-css');
var notify = require('gulp-notify');

gulp.task('styles', function () {
    //only process main.scss which imports all other required styles - including vendor files.
    return gulp.src('./assets/scss/main.scss')
            .pipe(plumber(function (error) {
                gutil.log(error.message);
                this.emit('end');
            }))
            .pipe(compass({
                config_file: './config.rb',
                css: './css'
                , sass: './assets/scss'
            }))
            //minify files
            .pipe(rename({suffix: '.min'}))
            .pipe(minifycss())

            //output
            .pipe(gulp.dest('./css'))
            .pipe(notify({message: 'Styles task complete'}));
});

gulp.task('watch', function () {
    liveReload.listen();
    gulp.watch('assets/scss/**/*.scss', ['styles']);
});

这很好,但如果出现错误也通知一下会更好(目前只在终端记录错误)。 - ejntaylor

4

我喜欢使用gulp plumber,因为它可以为任务添加全局监听器,并显示有意义的消息。

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/'));
});

参考资料: https://scotch.io/tutorials/prevent-errors-from-crashing-gulp-watch

本文介绍了如何使用Gulp Watch避免在开发过程中由于错误而导致的崩溃。在实施Gulp Watch时,需要使用try-catch语句来捕获错误并输出错误信息。此外,还可以使用gulp-plumber插件来自动处理错误。通过这些方法,可以保证开发过程中的稳定性和可靠性。

4

只使用一种文件格式

(例如:仅限*.coffee文件)

如果您只想使用一种文件格式,则gulp-plumber是您的解决方案。

例如,对于咖啡脚本,它可以处理丰富的错误和警告:

gulp.task('scripts', function() {
  return gulp.src(['assets/scripts/**/*.coffee'])
    .pipe(plumber())
    .pipe(coffeelint())
    .pipe(coffeelint.reporter())
    .pipe(lintThreshold(10, 0, lintThresholdHandler))
    .pipe(coffee({
      bare: true
    }))
    .on('error', swallowError)
    .pipe(concat('application.js'))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/scripts'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

使用多种文件格式

(例如同时使用*.coffee和*.js)

但如果您想要使用多种文件格式(例如: *.js*.coffee),那么我将发布我的解决方案。

我将在此处发布一个自我解释的代码,并附有一些说明。

gulp.task('scripts', function() {
  // plumber don't fetch errors inside gulpif(.., coffee(...)) while in watch process
  return gulp.src(['assets/scripts/**/*.js', 'assets/scripts/**/*.coffee'])
    .pipe(plumber())
    .pipe(gulpif(/[.]coffee$/, coffeelint()))
    .pipe(coffeelint.reporter())
    .pipe(lintThreshold(10, 0, lintThresholdHandler))
    .pipe(gulpif(/[.]coffee$/, coffee({ // if some error occurs on this step, plumber won't catch it
      bare: true
    })))
    .on('error', swallowError)
    .pipe(concat('application.js'))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/scripts'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

我在使用gulp.watch(...时遇到了gulp-plumbergulp-if的问题。
相关问题请参见:https://github.com/floatdrop/gulp-plumber/issues/23 所以对我来说最好的选择是:
  • 将每个部分作为文件,然后合并它们。创建多个任务,可以分别处理每个部分的文件(类似于grunt),然后将它们合并。
  • 将每个部分作为流,然后合并流。使用merge-stream(从event-stream创建)将两个流合并成一个,并继续工作(我首先尝试了这种方法,对我来说效果很好,因此比前一种方法更快)。

将每个部分作为流,然后合并流

这是我的代码主要部分:
gulp.task('scripts', function() {
  coffeed = gulp.src(['assets/scripts/**/*.coffee'])
    .pipe(plumber())
    .pipe(coffeelint())
    .pipe(coffeelint.reporter())
    .pipe(lintThreshold(10, 0, lintThresholdHandler))
    .pipe(coffee({
      bare: true
    }))
    .on('error', swallowError);

  jsfiles = gulp.src(['assets/scripts/**/*.js']);

  return merge([jsfiles, coffeed])
    .pipe(concat('application.js'))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/scripts'))
    .pipe(notify({ message: 'Scripts task complete' }));
});

将文件分为若干部分,然后合并

如果要将一个大文件分为若干部分,则每个部分应该创建一个结果文件。例如:

gulp.task('scripts-coffee', function() {

  return gulp.src(['assets/scripts/**/*.coffee'])
    .pipe(plumber())
    .pipe(coffeelint())
    .pipe(coffeelint.reporter())
    .pipe(lintThreshold(10, 0, lintThresholdHandler))
    .pipe(coffee({
      bare: true
    }))
    .on('error', swallowError)
    .pipe(concat('application-coffee.js'))
    .pipe(gulp.dest('dist/scripts'));

});

gulp.task('scripts-js', function() {

  return gulp.src(['assets/scripts/**/*.js'])
    .pipe(concat('application-coffee.js'))
    .pipe(gulp.dest('dist/scripts'));

});

gulp.task('scripts', ['scripts-js', 'scripts-coffee'], function() {

  var re = gulp.src([
    'dist/scripts/application-js.js', 'dist/scripts/application-coffee.js'
  ])
    .pipe(concat('application.js'))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/scripts'))
    .pipe(notify({ message: 'Scripts task complete' }));

  del(['dist/scripts/application-js.js', 'dist/scripts/application-coffee.js']);

  return re;

});

附言:

这是使用的节点模块和函数:

// Load plugins
var gulp = require('gulp'),
    uglify = require('gulp-uglify'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    plumber = require('gulp-plumber'),
    merge = require('ordered-merge-stream'),
    replace = require('gulp-replace'),
    del = require('del'),
    gulpif = require('gulp-if'),
    gulputil = require('gulp-util'),
    coffee = require('gulp-coffee'),
    coffeelint = require('gulp-coffeelint),
    lintThreshold = require('gulp-coffeelint-threshold');

var lintThresholdHandler = function(numberOfWarnings, numberOfErrors) {
  var msg;
  gulputil.beep();
  msg = 'CoffeeLint failure; see above. Warning count: ';
  msg += numberOfWarnings;
  msg += '. Error count: ' + numberOfErrors + '.';
  gulputil.log(msg);
};
var swallowError = function(err) {
  gulputil.log(err.toString());
  this.emit('end');
};

我仍然看到这些评论的改进空间。比如速度比较,以及仅链接到 .js 文件(不包括压缩或第三方库,或来自 bower_components 的库)。但基本上通过 Google.com 可以轻松调整和解决这个嗅探问题。 - Roman M. Koss

2
我已经实现了以下hack作为对 https://github.com/gulpjs/gulp/issues/71的解决方法:
// Workaround for https://github.com/gulpjs/gulp/issues/71
var origSrc = gulp.src;
gulp.src = function () {
    return fixPipe(origSrc.apply(this, arguments));
};
function fixPipe(stream) {
    var origPipe = stream.pipe;
    stream.pipe = function (dest) {
        arguments[0] = dest.on('error', function (error) {
            var state = dest._readableState,
                pipesCount = state.pipesCount,
                pipes = state.pipes;
            if (pipesCount === 1) {
                pipes.emit('error', error);
            } else if (pipesCount > 1) {
                pipes.forEach(function (pipe) {
                    pipe.emit('error', error);
                });
            } else if (dest.listeners('error').length === 1) {
                throw error;
            }
        });
        return fixPipe(origPipe.apply(this, arguments));
    };
    return stream;
}

将其添加到您的 gulpfile.js 中,然后像这样使用:

gulp.src(src)
    // ...
    .pipe(uglify({compress: {}}))
    .pipe(gulp.dest('./dist'))
    .on('error', function (error) {
        console.error('' + error);
    });

在我看来,这是最自然的错误处理方式。如果没有任何错误处理程序,它将抛出一个错误。已经使用Node v0.11.13进行了测试。


2
一个简单的解决方案是将 gulp watch 放入一个 Bash(或 sh)shell 的无限循环中。 while true; do gulp; gulp watch; sleep 1; done 在编辑 JavaScript 时,将此命令的输出保留在屏幕上可见区域。当您的编辑导致错误时,Gulp 将崩溃、打印其堆栈跟踪、等待一秒钟,并继续监视源文件。然后您可以纠正语法错误,Gulp 将通过打印其正常输出或再次崩溃(然后恢复)来指示编辑是否成功。
这适用于 Linux 或 Mac 终端。如果您使用 Windows,请使用 Cygwin 或 Ubuntu Bash(Windows 10)。

这是什么语言?此外,相比建议的解决方案,它有什么好处吗? - George Mauer
它是用Bash/sh编写的。相比其他解决方案,它需要更少的代码,并且更容易记忆和实现。 - Jesse Hogan
你能否在回答中编辑一下关于它是Bash的事实以及你对此的其他想法?我不认为这比Plumber更容易,但这是一种有效(尽管不跨平台)的方法。我最初投了反对票,因为甚至不清楚它使用的是什么语言,除非你编辑答案,否则我无法改变我的投票。 - George Mauer
1
那么你更喜欢崩溃流并重新启动整个过程,这可能需要相当长的时间,具体取决于您在无限循环中所做的事情,而不是简单地捕获错误?对我来说有点令人不安。说到减少代码,将管道工添加到任务中只需要16个字符,而您的解决方案需要36个字符,而不包括“gulp watch”。 - Preview
感谢您的反馈,@GeorgeMauer,我已经进行了编辑,并写了关于它可以运行的环境/平台的内容。 - Jesse Hogan
不能与浏览器刷新一起工作,您必须在出现错误后手动刷新浏览器。这会很快变得混乱。 - Paul S

1

Typescript

这是我的做法。我使用 Typescript 并将处理 less 的函数分离出来(以避免与 this 关键字混淆)。这也适用于 Javascript

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

gulp.task('less', function() {
    // writing a function to avoid confusion of 'this'
    var l = less({});
    l.on('error', function(err) {
        // *****
        // Handle the error as you like
        // *****
        l.emit('end');
    });

    return gulp
        .src('path/to/.less')
        .pipe(l)
        .pipe(gulp.dest('path/to/css/output/dir'))
})

现在,当您观看.less文件时,如果出现错误,观看将不会停止,并且根据您的less任务处理新更改。
注意:我尝试过l.end(); 但是,它没有起作用。但是,l.emit('end'); 完全有效。
希望这可以帮助您。祝好运。

1
这对我有用 ->
var gulp = require('gulp');
var sass = require('gulp-sass');

gulp.task('sass', function(){
    setTimeout(function(){
        return gulp.src('sass/*.sass')
        .pipe(sass({indentedSyntax: true}))
        .on('error', console.error.bind(console))
        .pipe(gulp.dest('sass'));
    }, 300);
});



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

gulp.task('default', ['sass', 'watch'])

我刚刚添加了.on('error', console.error.bind(console))这一行,但我必须以root身份运行gulp命令。我在一个php应用程序上运行node gulp,因此在同一台服务器上有多个帐户,这就是为什么我遇到了gulp在语法错误上中断的问题,因为我没有以root身份运行gulp...也许plumber和其他答案在我以root身份运行时会对我有用。感谢Accio Code https://www.youtube.com/watch?v=iMR7hq4ABOw提供的答案。他说通过处理错误可以帮助您确定错误所在的行以及控制台中的错误,但也可以防止gulp在语法错误上中断。他说这是一种轻量级的修复方法,不确定它是否适用于您正在寻找的内容。快速修复,值得一试。希望这能帮助到某些人!

但是现在你的CI流水线不再看到错误。 - Paul S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接