【初心者】Gulpの監視タスク中にエラー発生してもタスク継続 & 通知 メモ【備忘録】

# はじめに
投稿時点で、筆者は知識ゼロの状態から勉強を初めて2ヶ月程度の実力です。
そのため、理解不足や説明不足、誤った内容や呼び方をしている可能性があります。
万が一参考にする場合は上記の点を考慮した上でご一読ください。
説明文の内容はChatGPT先生からの引用も多いです。


# 目的

こちらは、前回からの続きになります。
前回：[【初心者】Gulpで構文チェック（gulp-htmlhint、stylelint、ESLint）メモ【備忘録】](https://qiita.com/miyaayim_study/items/8af5f662338b2e3236aa)

Gulpの監視タスク中に、Sassコンパイルのエラーなどで監視タスクが中断されてしまう問題を解消するのが目的

今回の完成品は[こちらのGitHub](https://github.com/miyaayim-study/template_project_react/tree/main/project_react)にあります。
（記事にはありませんが、タスクファイル分割、ファイルパス変数化等も含まれています。）

# 監視タスク実行中にエラー発生しても監視作業を中断せずに継続する方法

## 1.gulp-plumberを使用

`gulp-plumber`とはChatPGT先生より説明
>gulp-plumberは、Gulpタスクランナーを使用しているときに、パイプラインの中断を防止するために使用されるGulpプラグインの1つです。
gulp-plumberは、Gulpのパイプラインの途中でエラーが発生した場合に、タスクランナーの実行を停止するのではなく、エラーをキャッチしてハンドルすることができます。これにより、開発者はエラーが発生した際にプログラムが停止するのを防ぎ、タスクの連鎖を維持することができます。
例えば、gulp-plumberを使用しない場合、Sassコンパイルのタスクを実行するときに、1つのSassファイルにエラーがあると、Gulpはエラーを吐き出してタスクランナーを停止します。しかし、gulp-plumberを使用することで、エラーが発生してもタスクランナーを停止せず、エラーメッセージを表示することができます。

`gulp-plumber`をインストールします。
```console
npm i -D gulp-plumber
```

`gulpfile.mjs`に`gulp-plumber`を記述します。
```javascript:gulpfile.mjs
import { dir } from "../config.mjs";
import gulp from 'gulp';
import plumber from "gulp-plumber"; // 追加

// sass 関連インポート
import gulpSass from "gulp-sass"
import sassCompiler from "sass";
const sass = gulpSass(sassCompiler);

// postcss 関連インポート
import postcss from 'gulp-postcss';
import postcssReporter from 'postcss-reporter';
import autoprefixer from 'autoprefixer';
import stylelint from 'stylelint';

const compileSass = (done) => {

  gulp.src(dir.src.stylesheets + 'style.scss')
    .pipe(plumber()) // 追加
    .pipe(
      sass({
        outputStyle: "expanded"
      })

    .pipe(postcss([autoprefixer()]))
    .pipe(postcss([
      stylelint({
        configFile: ".stylelintrc.js",
        fix: true,
      }),
      postcssReporter({clearMessages: true})
    ]))
    
    .pipe(gulp.dest(dir.dest.stylesheets))
    done();
};

export default compileSass;
```

2行追加しました。
では監視タスク実行中にわざとエラーを出してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/ba747008-b14d-aef9-a7f9-dc28bf9b78cf.png)
エラーは出てきましたが監視タスクは中断されません、成功です。
しかし同じエラー内容が復数メッセージとして表示されています。
理由はまだ理解できていませんが、コードを下記に変更すると、エラー表示が1回だけになってくれました。
```javascript:gulpfile.mjs
// 変更前
.pipe(plumber()) 

// 変更後
.pipe(
  plumber({
    errorHandler: (error) => {
    console.log(error.messageFormatted);
    },
  })
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/396e5d08-bd7f-d558-f0ed-ed2835165032.png)


## 2.on('error', ...)を使用
こちらでも同様のことが可能です。
記述してみます。
```javascript:gulpfile.mjs
import { dir } from "../config.mjs";
import gulp from 'gulp';

// sass 関連インポート
import gulpSass from "gulp-sass"
import sassCompiler from "sass";
const sass = gulpSass(sassCompiler);

// postcss 関連インポート
import postcss from 'gulp-postcss';
import postcssReporter from 'postcss-reporter';
import autoprefixer from 'autoprefixer';
import stylelint from 'stylelint';

const compileSass = (done) => {

  gulp.src(dir.src.stylesheets + 'style.scss')

    .pipe(
      sass({
        outputStyle: "expanded"
      })

      .on('error', (err) => { // 追加
        console.log(err.message); // 追加
      }) // 追加

    .pipe(postcss([autoprefixer()]))
    .pipe(postcss([
      stylelint({
        configFile: ".stylelintrc.js",
        fix: true,
      }),
      postcssReporter({clearMessages: true})
    ]))
    
    .pipe(gulp.dest(dir.dest.stylesheets))
    done();
};

export default compileSass;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/67f2a435-258d-1a6e-0baa-3d223ed92530.png)
こちらも、エラー検出時に監視タスクが終了せずにいけました

こういった記述もできます。
```javascript:gulpfile.mjs
.on('error', (err) => {
  console.log(err.message);
})

// ↓ 記述を短くした、`sass`部分はタスクに合わせて変更

.on("error", sass.logError)
```

そして、先程は`on('error', ...)`を`.pipe()`内に追加しましたが、`.pipe()`外に記述している記事もありました。
```javascript:gulpfile.mjs
// .pipe()内
.pipe(
  sass()
  .on('error', (err) => {
    console.log(err.message);
  })
)

// .pipe()外
.pipe(sass())
.on('error', (err) => {
  console.log(err.message);
})


```
違いをChatGPT先生に聞いてみました。
>これらの2つのコードの主な違いは、エラーハンドリングのタイミングにあります。
1つ目のコードは、sass()の出力がGulpストリーム内でパイプラインに渡されるときに、エラーが発生した場合にエラーハンドラーが呼び出されます。エラーが発生すると、エラーハンドラーが呼び出され、エラーメッセージがコンソールに出力されます。このコードでは、エラーハンドラーはsass()ストリーム内で実行されます。
2つ目のコードは、sass()ストリームの出力がすでにパイプラインに渡された後に、エラーハンドラーが呼び出されます。エラーが発生すると、エラーハンドラーが呼び出され、エラーメッセージがコンソールに出力されます。このコードでは、エラーハンドラーはsass()ストリームの出力に対して直接実行されます。
つまり、1つ目のコードは、sass()ストリームの内部でエラーハンドリングを行い、2つ目のコードは、sass()ストリームの出力に対して直接エラーハンドリングを行うという点で異なります。
on('error', ...)をsass()ストリーム内で実行すると、エラーが発生した場合にストリームが停止され、エラーメッセージがコンソールに出力されます。これに対して、on('error', ...)をsass()ストリームの出力に対して実行すると、エラーが発生してもストリームは停止されず、エラーメッセージがコンソールに出力されますが、パイプラインは継続します。

うーん……なんとなく言いたいことはわかるが、ハッキリとイメージできていない…。
なので実際にやってみましょう。
`.pipe()`内
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/df1c9097-a61d-a2f7-19e5-a990cc0659bf.png)
`.pipe()`外
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/bd8fb9aa-c611-70f5-ee80-890a75134338.png)
メッセージ的にも動作的にも全く同じでした。
ではこちらはどのように使い分けるのでしょうか？
ChatGPT先生お願いします。
>on('error', ...)を.pipe()の外に書くと、タスク自体のエラーをキャッチして、その後のタスクを実行しないようにします。これは、例えばビルド時にSCSSのコンパイルエラーがあった場合に、その後のタスクも実行せずにビルドを終了させたい場合に使用します。
一方、.pipe()内にon('error', ...)を書くと、そのタスク内で発生するエラーをキャッチして処理を続行します。例えば、ビルド時にSCSSのコンパイルエラーがあった場合でも、その後のタスクを実行したい場合に使用します。また、エラーの詳細な情報を出力したい場合にも使えます。

うーん先程と似たような回答ですね。
ここらへんは経験積んでから出直します。

ところで、この`on('error', ...)`ですが、自分はJavaScriptの知識が浅くて理解できていないため、一つずつChatGPT先生に聞いてみました。

`.on("error", sass.logError)`のonとはなんですか？
>`.on()` は、ストリームのイベントをキャッチして処理するためのメソッドです。引数としてイベント名と、実行したいコールバック関数を指定します。イベントが発生すると、指定したコールバック関数が実行されます。
この例では、Sassコンパイル中にエラーが発生した場合に発生する `error` イベントをキャッチしています。エラーが発生すると、`sass.logError` が呼ばれてエラーの内容がログに出力されます。これにより、コンパイルエラーが発生した場合でもGulpが強制終了することを防ぎ、実行を継続することができます。

`error`イベントとはなんですか？
>JavaScriptにおけるイベント(event)とは、DOM(Document Object Model)などのオブジェクトで発生する特定のアクションを指します。これらのアクションには、クリックやキーボードの入力などがあります。error イベントは、ある種のエラーが発生したときに自動的に発生するイベントです。たとえば、ファイルの読み込み中にエラーが発生した場合や、ネットワーク通信中にエラーが発生した場合などがあります。この error イベントをハンドリングすることで、エラーが発生しても処理が継続できるようにすることができます。

`sass.logError`とはなんですか？

`console.log(err.message);`
これのerr.messageはなんですか？
>この `err.message` は、エラーオブジェクト `err` の中の `message` プロパティを指しています。 `message` プロパティは、エラーに関する説明的な文字列を保持するために使用されます。例えば、Sassコンパイルでエラーが発生した場合、エラーオブジェクトにはそのエラーに関する情報が含まれており、そのエラーの説明的なテキストが `message` プロパティに保存されます。



## 使い分け
じゃあ`gulp-plumbe`と`on('error', ...)`どっちを使うの？ってなったのでChatGPT先生に聞きました。
>gulp-plumberは、エラーが発生した場合にGulpのタスクが強制的に停止するのを防止するためのプラグインです。一方、on('error', ...)は、Gulpストリーム内でエラーが発生した場合に、カスタムのエラーハンドラーを実行するための方法です。
つまり、on('error', ...)はGulpストリーム内でエラーを処理することができますが、Gulpタスク全体に対するエラーハンドリングは提供しません。一方、gulp-plumberは、Gulpタスク全体でエラーハンドリングを提供するためのプラグインです。
したがって、gulp-plumberはGulpのタスクがエラーで停止するのを防ぐために必要な場合があります。一方、on('error', ...)は、特定のGulpストリームでエラー処理が必要な場合に使用されます。on('error', ...)を使用すると、エラーが発生したときにストリームが停止せず、エラー処理後にGulpストリームが継続できるようになります。
したがって、Gulpタスク内でエラー処理が必要な場合は、on('error', ...)を使用することができます。ただし、Gulpタスク全体でエラーハンドリングを提供するためには、gulp-plumberプラグインを使用する必要があります。

おそらくですが、1つの作業だけを実行するような場合、`.pipe()`内で使う場合は、`.on('error', ...)`を`.pipe()`内で使う。
1つの作業（`.pipe()`）が復数あるタスク内において、どの作業がエラー検出しても全体のタスクを中断させないようにするときは`gulp-plumbe`をタスク内で使う。
という使い分けでしょうか。
大は小を兼ねるので、`gulp-plumbe`を使えば問題ないのかもしれません。
このエラーだけは必ずタスクを中断させるっていうのがタスク内のどこかにある場合は`gulp-plumbe`は適していないということなのかな？

### 各種パターンまとめ
```javascript:gulpfile.mjs
//gulp-plumber---------------
// これだと同じエラーメッセージが重複した
.pipe(plumber())
.pipe(sass())

// これならエラーメッセージが重複しなかった
.pipe(
  plumber({
    errorHandler: (error) => {
    console.log(error.messageFormatted);
    },
  })
)
.pipe(sass())


//.on('error', ...)---------------
// .pipe()内
.pipe(
  sass()
  .on('error', (err) => {
    console.log(err.message);
  })
)

//// コード短縮
.pipe(
  sass()
  .on("error", sass.logError)
)

// .pipe()外
.pipe(sass())
.on('error', (err) => {
  console.log(err.message);
})

//// コード短縮
.pipe(sass())
.on("error", sass.logError)
```


### 備忘録（webpackタスクにつける場合）

`gulp-plumber`の場合
```javascript:gulpfile.mjs
// 変更前
const bundleWebpack = (done) => {
  webpackStream(webpackConfig, webpack)
    .pipe(gulp.dest(dir.dest.javascripts));
    done();
};

// 変更後
const bundleWebpack = (done) => {
  plumber({
    errorHandler: (error) => {
      console.log(error.messageFormatted);
    },
  })
    .pipe(webpackStream(webpackConfig, webpack))
    .pipe(gulp.dest(dir.dest.javascripts));
    done();
};

```

`.on('error', ...)`の場合

```javascript:gulpfile.mjs
// 変更前
const bundleWebpack = (done) => {
  webpackStream(webpackConfig, webpack)
    .pipe(gulp.dest(dir.dest.javascripts));
    done();
};

// 変更後
const bundleWebpack = (done) =>
  webpackStream(webpackConfig, webpack)
    .on('error', (err) => {
      console.log(err.message);
    })  
    .pipe(gulp.dest(dir.dest.javascripts));
    done();
};
```




## gulp-notify
`gulp-notify`を使えば、エラー通知をデスクトップ通知で知らせてくれます。
まずはインストールします。
```console
npm i -D gulp-notify
```

`gulpfile.mjs`に`gulp-notify`を記述します。

```javascript:gulpfile.mjs
import { dir } from "../config.mjs";
import gulp from 'gulp';
import plumber from "gulp-plumber";
import notify from "gulp-notify"; // 追加

// sass 関連インポート
import gulpSass from "gulp-sass"
import sassCompiler from "sass";
const sass = gulpSass(sassCompiler);

// postcss 関連インポート
import postcss from 'gulp-postcss';
import postcssReporter from 'postcss-reporter';
import autoprefixer from 'autoprefixer';
import stylelint from 'stylelint';

const compileSass = (done) => {

  gulp.src(dir.src.stylesheets + 'style.scss')
    .pipe(plumber(notify.onError('Error: <%= error.message %>'))) // 追加
    .pipe(
      sass({
        outputStyle: "expanded"
      })

    .pipe(postcss([autoprefixer()]))
    .pipe(postcss([
      stylelint({
        configFile: ".stylelintrc.js",
        fix: true,
      }),
      postcssReporter({clearMessages: true})
    ]))
    
    .pipe(gulp.dest(dir.dest.stylesheets))
    done();
};

export default compileSass;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/6a3bb152-4997-1bdc-97b7-e6a3f8de6c9d.png)

エラーメッセージ内容が赤文字になったのと、デスクトップ通知が出るようになりました。
デスクトップ通知は要らない人もいると思いますが、エラーメッセージ内容が赤文字になるのはわかりやすくていいですね。
ちなみに、自分のwebpackタスクにつけてみたのですがwebpackタスクでは通知ができませんでしたし、単独で実行した際にコンソールをキャンセルしないと終了してくなりなりました。

### 各種パターンまとめ
```javascript:gulpfile.mjs
//gulp-plumber---------------
// plumberのerrorHandleあり
.pipe(plumber({
  errorHandler: notify.onError("Error: <%= error.message %>")
}))
.pipe(sass())

// notifyはplumberのerrorHandler無しでもOK 
.pipe(plumber(notify.onError('Error: <%= error.message %>')))
.pipe(sass())

//.on('error', ...)---------------
// .pipe()内
.pipe(
  sass()
  .on('error', notify.onError('Error: <%= error.message %>'))
)

// .pipe()外
.pipe(sass())
.on('error', notify.onError('Error: <%= error.message %>'))
```

### 通知のカスタム
`gulp-notify`でのデスクトップ通知内容をカスタムできるそうです。
下記のように記述します。
```javascript:gulpfile.mjs
.pipe(plumber({
  errorHandler: notify.onError({
    title: 'Sassコンパイルエラー',
    message: 'Error: <%= error.message %>'
  })
}))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310435/c1591aae-9967-932d-54ef-d6dfc9d3de0d.png)
これはわかりやすいですね。





# 参考サイト
[これからはじめるGulp #6：gulp-plumberとgulp-notifyを使ったデスクトップ通知](https://dev.classmethod.jp/articles/gulp-solo-adv-cal-06/)
[gulp-sass, gulp-plumberを使うときの注意](https://qiita.com/Quramy/items/5491d8da29d1fe047464)
[gulp-plumberを使ってもSassのエラーでGulpが止まってしまう場合の対処方法](https://devsakaso.com/gulp-when-gulp-plumber-doesnot-work-and-sass-stops/)
[gulp-plumberとgulp-notifyでgulpエラー時にデスクトップで通知がくるようにする](https://webrandum.net/gulp-notify/)
[Sassから完璧なCSSファイルを出力するために通したいGulpのタスク【WordPress編】](https://haniwaman.com/gulp-sass/#index_id2)
[`gulp-notify` を真面目に使ってデスクトップにエラーを通知する](https://labor.ewigleere.net/2020/07/24/gulp-notify/)