Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docs] Getting Started - Error Management #359

Open
yocontra opened this issue Mar 20, 2014 · 20 comments
Open

[Docs] Getting Started - Error Management #359

yocontra opened this issue Mar 20, 2014 · 20 comments
Assignees

Comments

@yocontra
Copy link
Member

We need a huge section for this since it is by far our hugest problem for new users.

Here are cases we need to make sure we cover:

  1. Fail task and stop stream when an error occurs
    • This should be the default behavior
  2. Fail task after stream finished when an error occurs
    • testing or linting
  3. Log when an error occurs but stop nothing
    • for people who like partial builds
@yocontra yocontra added this to the gulp 4 milestone Mar 20, 2014
@yocontra yocontra self-assigned this Mar 20, 2014
@ScottWeinstein
Copy link

And additional "requirement" - errors, either from the stream or from the task should not have gulp-internals in the stack trace.This causes lots of confusion from users of the build.

@yocontra
Copy link
Member Author

@ScottWeinstein this is fixed in the new task system via nextTick-ing stuff

@phated
Copy link
Member

phated commented Aug 26, 2014

Thoughts on a command line flag for partial builds?

@yocontra
Copy link
Member Author

@phated Why a CLI flag?

@phated
Copy link
Member

phated commented Aug 26, 2014

partial builds are the edge case, as far as I can tell. This would switch series/parallel from using bach.series/parallel to settleSeries/settleParallel internally.

@itajaja
Copy link

itajaja commented May 21, 2015

👍 really. I cannot use gulp in my CI because I am afraid I am not catching all the errors, I would like to know a bullet proof approach for catching all the errors inside task or a composition of tasks with parallel/series

@yocontra
Copy link
Member Author

@phated Are there any docs anywhere for error management in bach/gulp 4?

@phated
Copy link
Member

phated commented May 22, 2015

@contra https://github.com/phated/async-done#completion-and-error-resolution but it hasn't been thoroughly tested with gulp pipelines and all the errors that can happen there. Also, --continue flag is available to continue running on failure. See #871

@Bnaya
Copy link

Bnaya commented Dec 20, 2015

Hey @contra

Another case i would like to see (it its makes sense)
it's like case 3. but it filters the failed files from the stream.

My use case:
I have partial build with linter before the build step on the same pipe.
i would like that if file have linter error to log error and not continue down the pipe to the build step without breaking the entire stream.

Thanks for all of the effort!

@yocontra
Copy link
Member Author

@Bnaya You mean like the behavior using gulp-plumber?

@Bnaya
Copy link

Bnaya commented Dec 20, 2015

I'm working with gulp4 and i understand that plumber is not needed (?)
Also i read plumber's docs and i can see any reference that it will filter the files with errors from the stream. i might have missed something

This is what i want to achieve, without the need to explicitly check for file.eslint.errorCount
my builder.build will break if file with syntax error will get to it so i need to filter them.

gulp.src(changedFileRef.path)
.pipe(gulpEslint())
.pipe(gulpMap(function (file) {
    if (file.eslint.errorCount === 0) {
        return builder.build(toRelative(file.path));
    } else {
        return Promise.resolve();
    }
})

And also if there a more elegant way to send the data to the builder.build without gulp-map. i couldn't find something else

Thanks!

@iliakan
Copy link

iliakan commented Jan 8, 2016

gulp-plumber/stream-combiner not needed in gulp4? sounds strange.. indeed?

@indolering
Copy link

I just came on to beg that Gulp 4 report errors by default, am I correct in interpreting this ticket as handling that?

@phated phated changed the title Document error management Getting Started - Error Management documentation Jul 29, 2018
@phated phated modified the milestones: gulp 4.1, gulp 4 - latest tag Jul 29, 2018
@phated phated self-assigned this Jul 29, 2018
@phated phated changed the title Getting Started - Error Management documentation [Docs] Getting Started - Error Management Oct 25, 2018
@phated
Copy link
Member

phated commented Oct 25, 2018

Updated the title here because it will belong under the "Getting Started" documentation and it'll be titled "Error Management"

@brendanfalkowski
Copy link

brendanfalkowski commented Feb 23, 2019

I've had some trouble finding examples of migrating error handling from Gulp 3 to 4. Not sure if I'm alone, but to me the developer experience of error handling is my greatest frustration with Gulp over the years. I've spent dozens of hours trying to make three things work across tasks:

  1. Errors don't end a running watch task.
  2. Errors fire an OS notification with small hints at the issue (task, file, line number).
  3. Errors are logged to the console with detail on the issue and good UX (formatting, color).

Example frustration: I got stuck when the Gulp docs and a package creator suggested different things were the best practice: sindresorhus/gulp-imagemin#285

I'd really like to help people avoid the "oh, my whole routine works except in that one package" scenario.

My attempts

I'm not a JS architect, but I'd like to share some code failures and successes to get the ball rolling. My testing workflow was:

  1. Run gulp watch
  2. Save a proper CSS (or JS) file, which will compile.
  3. Add an obvious typo to that file, and save.
  4. Observe how it fails by logging straight to console, using my error handler, and/or preserving the watcher.

Failed

  • Works when the task compiles
  • Fails when the task doesn't compile, which ends the watcher.
function css (cb) {
    var task = config.task.css;

    gulp
    .src(task.src, { sourcemaps: true })
    .pipe(sass(task.sassOptions))
    .pipe(autoprefixer(task.autoprefixerOptions))
    .pipe(gulp.dest(task.dest, { sourcemaps: task.mapDest }))
    .pipe(gulpif(!isSilent, notify(task.notifyOptions)));

    cb();
};

Failed

  • Works when the task compiles.
  • Fails when the task doesn't compile, which ends the watcher and doesn't run the error handler.
function css () {
    var task = config.task.css;

    return pump([
        gulp.src(task.src, { sourcemaps: true }),
        sass(task.sassOptions),
        autoprefixer(task.autoprefixerOptions),
        gulp.dest(task.dest, { sourcemaps: task.mapDest }),
        gulpif(!isSilent, notify(task.notifyOptions))
    ], errorHandler);
};

Works

  • Works when the task compiles.
  • Works when the task doesn't compile, and the watcher stays running.
function css (cb) {
    var task = config.task.css;

    pump([
        gulp.src(task.src, { sourcemaps: true }),
        sass(task.sassOptions),
        autoprefixer(task.autoprefixerOptions),
        gulp.dest(task.dest, { sourcemaps: task.mapDest }),
        gulpif(!isSilent, notify(task.notifyOptions))
    ], errorHandler);

    cb();
};

Failed

The same approach that worked for my CSS task failed my JS task. I'm not sure why.

  • Works when the task compiles.
  • Works when the task doesn't compile, but the watcher only detects the first error. It stays watching but can't detect a fixed issue without restarting.
function jsAppPost (cb) {
    var task = config.task.jsAppPost;

    pump([
        gulp.src(task.src, { sourcemaps: true }),
        uglify(task.uglifyOptions),
        concat(task.file),
        gulp.dest(task.dest, { sourcemaps: true }),
        gulpif(!isSilent, notify(task.notifyOptions))
    ], errorHandler);

    cb();
}

My error handler

For reference, here's my error handler:

var beeper = require('beeper');
var color  = require('ansi-colors');
var notify = require('gulp-notify');

module.exports = function (error) {
    if (typeof error !== 'undefined') {
        // [log] Uncomment to show the full error object
        //console.log(error);

        // ----------------------------------------------
        // Normalize error responses

        var report = ['\n'];
        var notifyMessage = '';

        if (error.plugin == 'gulp-eslint') {
            report.push(color.red('Plugin: ') + error.plugin     + '\n');
            report.push(color.red('File:   ') + error.fileName   + '\n');
            report.push(color.red('Line:   ') + error.lineNumber + '\n');
            report.push(color.red('Note:   ') + error.message    + '\n');

            notifyMessage = 'JS linter found errors.';
        }

        if (error.plugin === 'gulp-sass') {
            report.push(color.red('Plugin: ') + error.plugin          + '\n');
            report.push(color.red('File:   ') + error.relativePath    + '\n');
            report.push(color.red('Line:   ') + error.line            + '\n');
            report.push(color.red('Column: ') + error.column          + '\n');
            report.push(color.red('Note:   ') + error.messageOriginal + '\n');

            notifyMessage = error.relativePath + '\n' + error.line + ' : ' + error.column;
        }

        if (error.plugin == 'gulp-stylelint') {
            notifyMessage = 'CSS linter found errors.';
        }

        if (error.plugin === 'gulp-uglify') {
            report.push(color.red('Plugin: ') + error.plugin         + '\n');
            report.push(color.red('Path:   ') + error.fileName       + '\n');
            report.push(color.red('File:   ') + error.cause.filename + '\n');
            report.push(color.red('Line:   ') + error.cause.line     + '\n');
            report.push(color.red('Column: ') + error.cause.col      + '\n');
            report.push(color.red('Note:   ') + error.cause.message  + '\n');

            notifyMessage = error.cause.filename + '\n' + error.cause.line + ' : ' + error.cause.col;
        }

        // ----------------------------------------------
        // Show error in console

        console.error(report.join(''));

        // ----------------------------------------------
        // Fire Mac/Windows notification for error

        notify({
            title:   'Failed Gulp — See Console',
            message: notifyMessage,
            sound:   'Sosumi' // Sound for Mac. See: https://github.com/mikaelbr/node-notifier#all-notification-options-with-their-defaults
        }).write(error);

        beeper(); // Fallback to system sound (for Windows).
    }
};

Next steps

I'd like to help create the docs for error handling, and hope these examples get some feedback. I'm sure there's a working recipe and I just haven't found the right blog yet.

@kimamula
Copy link

Now that Node.js v8 has reached its EOL, every pump() in gulp docs should probably be replaced with stream.pipeline().
https://nodejs.org/en/docs/guides/backpressuring-in-streams/#the-problem-with-data-handling

@battk
Copy link

battk commented Jun 15, 2024

Is there any new known workaround for gulp 5 to prevent errors thrown during gulp watch from stopping the stream?

@donpedro
Copy link

donpedro commented Aug 26, 2024

Is there any new known workaround for gulp 5 to prevent errors thrown during gulp watch from stopping the stream?

Is the --continue flag what you're looking for, @battk? Here is the original discussion on it. I'm not using gulp 5 yet, but it appears to still be available in the latest gulp-cli

@battk
Copy link

battk commented Aug 26, 2024

No difference, the problem doesnt actually affect gulp 4. I would guess its the same issue as in #2812. Its a little concerning that error handling has been a problem for a decade.

@ehmicky
Copy link

ehmicky commented Oct 29, 2024

There is some update about this problem, described here.

Even though user documentation recommends using .pipe(), and this is what most users do, it seems to me that using pipeline() as suggested above is the right way to make sure errors are properly handled.

import {pipeline} from 'node:stream/promises'
import gulp from 'gulp'

export default () => pipeline(
  gulp.src(...),
  stream,
  secondStream,
  thirdStream,
)

On Gulp v5, one might also be able to keep using a fluent interface by using a combination of:

  • .pipe(), when the previous stream was created by gulp
  • .compose(), otherwise

That's because streams created by gulp are using streamx, which lacks .compose().

It seems to work, but I am not 100% sure whether this always does.

import {pipeline} from 'node:stream/promises'
import gulp from 'gulp'

export default () => gulp.src(...)
  .pipe(stream)
  .compose(secondStream)
  .compose(thirdStream)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests