gulpをv3系からv4系に移行する流れ

gulpをv3系からv4へ実際に移行してみた流れをまとめました。
執筆時点でのgulpバージョンは**v4.0.2**になります。

# 移行前の状態
こちらが移行する前（v3）の状態の設定ファイルです。

```javascript:gulpfile.js
/*--------------------------------------------------------
  modules
--------------------------------------------------------*/

let gulp = require('gulp'),
  autoprefixer = require('gulp-autoprefixer'),
  cached = require('gulp-cached'),
  notify = require('gulp-notify'),
  plumber = require('gulp-plumber'),
  sass = require('gulp-sass'),
  watch = require('gulp-watch'),
  encode = require('gulp-convert-encoding'),
  uglify = require('gulp-uglify'),
  pleeease = require('gulp-pleeease'),
  replace = require('gulp-replace'),
  fs = require('fs');

//------------------------------------------------------------------------------
// PC:sass / scss / css
//------------------------------------------------------------------------------

gulp.task('pc:sass', () =>
  gulp
    .src(`${__dirname}/pc/**/*.scss`)
    .pipe(
      plumber({
        errorHandler: notify.onError('<%= error.message %>')
      })
    )
    .pipe(
      sass({
        outputStyle: 'expanded'
      }).on('error', sass.logError)
    )
    .pipe(
      autoprefixer({
        browsers: [
          'last 2 versions',
          'ie >= 9',
          'ChromeAndroid >= 6',
          'Android >= 6',
          'iOS >= 9'
        ]
      })
    )
    .pipe(replace('UTF-8', 'Shift_JIS'))
    .pipe(replace('utf-8', 'Shift_JIS'))
    .pipe(encode({ to: 'Shift_JIS' }))
    .pipe(gulp.dest(`${__dirname}/path/to/css/`))
);

//------------------------------------------------------------------------------
// SP:sass / scss / css
//------------------------------------------------------------------------------

gulp.task('sp:sass', () =>
  gulp
    .src(`${__dirname}/sp/**/*.scss`)
    .pipe(
      plumber({
        errorHandler: notify.onError('<%= error.message %>')
      })
    )
    .pipe(
      sass({
        outputStyle: 'expanded'
      }).on('error', sass.logError)
    )
    .pipe(
      autoprefixer({
        browsers: [
          'last 2 versions',
          'ie >= 9',
          'ChromeAndroid >= 6',
          'Android >= 6',
          'iOS >= 9'
        ]
      })
    )
    .pipe(replace('UTF-8', 'Shift_JIS'))
    .pipe(replace('utf-8', 'Shift_JIS'))
    .pipe(encode({ to: 'Shift_JIS' }))
    .pipe(gulp.dest(`${__dirname}/path/to/css/`))
);

//------------------------------------------------------------------------------
// PC:watch
//------------------------------------------------------------------------------
gulp.task('pc:watch', () => {
  watch(`${__dirname}/pc/**/*.scss`, () => gulp.start('pc:sass'));
});

//------------------------------------------------------------------------------
// SP:watch
//------------------------------------------------------------------------------
gulp.task('sp:watch', () => {
  watch(`${__dirname}/sp/**/*.scss`, () => gulp.start('sp:sass'));
});

gulp.task('pc', ['pc:watch']);
gulp.task('sp', ['sp:watch']);
gulp.task('default', ['pc', 'sp']);
```

~~使ってないプラグインなんかも読み込まれていてますがその辺は後でまとめて整理するとして~~
こいつをv4の書き方に直していきます。

# 移行スタート

## まずはgulpのバージョンアップ
今入っているgulpのバージョンが**v3.9.1**だったので、まずはこれを最新のバージョンにアップデートします。
（パッケージのバージョンチェック・アップデートに[npm-check-updates](https://www.npmjs.com/package/npm-check-updates)を使ってます。）

```sh
ncu gulp -u
npm i
```

v4にバージョンアップして試しにそのままgulpを実行すると、当然ですがエラーで阻まれます。

## 出てくるエラーを片端から潰していく

### :warning: Task function must be specified

v4からタスクを呼び出す時の書き方が変わった事によるエラーです。
今回の場合だとこの辺りが原因になります。

```js:gulpfile.js
gulp.task('pc', ['pc:watch']);
gulp.task('sp', ['sp:watch']);
gulp.task('default', ['pc', 'sp']);
```

`pc:watch`、`sp:watch`、`pc`、`sp` というタスクを呼び出して実行しようとしてますが、呼び出し方がv4の書き方に則ってないよって感じですね。
v4からの書き方はこうなります。

```js:gulpfile.js
gulp.task('pc', gulp.task('pc:watch'));
gulp.task('sp', gulp.task('sp:watch'));
gulp.task('default', gulp.parallel('pc', 'sp'));
```

複数のタスクを同時に実行する場合は以下のどちらかを使いましょう。
`gulp.series`：直列処理
`gulp.parallel`：並列処理

今回使用したgulpfileだと、ここを修正しただけでとりあえずタスクが実行されるようになりました。

### :warning: gulp.start is not a function

`default`、`pc`、`sp` のタスクは無事に実行されるようになりましたがscssファイルを保存するとエラーで止まってしまいます。
v4から`gulp.start`が使えなくなっているのが原因です。コードとしてはこの辺り。

```js:gulpfile.js
//------------------------------------------------------------------------------
// PC:watch
//------------------------------------------------------------------------------
gulp.task('pc:watch', () => {
  watch(`${__dirname}/pc/**/*.scss`, () => gulp.start('pc:sass'));
});

//------------------------------------------------------------------------------
// SP:watch
//------------------------------------------------------------------------------
gulp.task('sp:watch', () => {
  watch(`${__dirname}/sp/**/*.scss`, () => gulp.start('sp:sass'));
});
```

そもそもwatch機能を使うために`gulp-watch`を使用していますが、`gulp.watch`で良いのでそこも含めて書き直しましょう。

```js:gulpfile.js
//------------------------------------------------------------------------------
// PC:watch
//------------------------------------------------------------------------------
gulp.task('pc:watch', () => {
  gulp.watch(`${__dirname}/pc/**/*.scss`, gulp.task('pc:sass'));
});

//------------------------------------------------------------------------------
// SP:watch
//------------------------------------------------------------------------------
gulp.task('sp:watch', () => {
  gulp.watch(`${__dirname}/sp/**/*.scss`, gulp.task('sp:sass'));
});
// ディレクトリを複数設定する場合は配列にする
gulp.task('sp:watch', () => {
  gulp.watch(['path/to/hoge/*.scss', 'path/to/fuga/*.scss'], gulp.task('sp:sass'));
});
```

これで正常にwatchの処理も動くようになり、ひとまず以前と同じように動くようになりました。
使わなくなった`gulp-watch`などを消してあげれば最低限の移行はこれで完了です。

## 今回は大丈夫だったけど遭遇するかもしれないエラー

今回の例として使ったgulpfileでは大丈夫でしたが、遭遇しそうなエラーの対処も書いておきます。

### :warning: Task never defined: hogehoge
定義されていないtaskを実行しようとすると出ます。
v4からは`gulp.task('hogehoge')`でtaskを実行する前に、そのtaskが定義されている必要があります。
記述の順番を見直してみましょう。

### :warning: The following tasks did not complete
v4からはtaskの終了を明示する必要があります。
今回の場合は

```javascript:gulpfile.js
gulp.task('pc:sass', () =>
  gulp
    .src(`${__dirname}/pc/**/*.scss`)
    // 略
);
```

このようにコールバック関数として`gulp`を渡しているのでエラーが出ることはありませんでした。
`gulp`をコールバック関数として渡せない場合は以下のように`gulp`を`return`してあげると良いと思います。

```javascript:gulpfile.js
gulp.task('pc:sass', () => {
  return gulp
    .src(`${__dirname}/pc/**/*.scss`)
    // 略
});
```

## もっとv4のルールに則った形にする
ここまでの段階で使えるようにはなっていますが、非推奨の書き方も含まれているためそこも直していきます。

### :warning: gulp.taskは非推奨
山ほど使っている`gulp.task`が現在では非推奨になっており、タスクごとに関数を定義するように言われています。
ので、その辺りを考慮して書き直すとこうなります。

```javascript:gulpfile.js
//------------------------------------------------------------------------------
// PC:sass / scss / css
//------------------------------------------------------------------------------

const compilePcSass = () =>
  gulp
    .src(`${__dirname}/pc/**/*.scss`)
    // 中略
    .pipe(gulp.dest(`${__dirname}/path/to/css`));

//------------------------------------------------------------------------------
// SP:sass / scss / css
//------------------------------------------------------------------------------

const compileSpSass = () =>
  gulp
    .src(`${__dirname}/sp/**/*.scss`)
    // 中略
    .pipe(gulp.dest(`${__dirname}/path/to/css`));

//------------------------------------------------------------------------------
// PC:watch
//------------------------------------------------------------------------------
const watchPcSassFiles = () =>
  gulp.watch(`${__dirname}/pc/**/*.scss`, compilePcSass);

//------------------------------------------------------------------------------
// SP:watch
//------------------------------------------------------------------------------
const watchSpSassFiles = () =>
  gulp.watch(`${__dirname}/sp/**/*.scss`, compileSpSass);

exports.default = gulp.parallel(watchPcSassFiles, watchSpSassFiles);
```

今まで`default`タスクで実行していた内容は、`exports.default`に渡してあげます。

### :warning: 必要なプラグインだけ読み込むようにする
一番最初に

```js:gulpfile.js
gulp = require('gulp')
```

で`gulp`を丸ごと読み込んでいますが、今回使っているのは`src`、`dest`、`watch`、`parallel`だけなのでそれ以外は不要です。

```js:gulpfile.js
const {src, dest, watch, parallel} = require('gulp')
```
こうすれば必要な部分だけ読み込むことができます。

# 最終的に出来上がったgulpfile.js

```js:gulpfile.js
/*--------------------------------------------------------
  modules
--------------------------------------------------------*/

const { src, watch, dest, parallel } = require('gulp'),
  autoprefixer = require('gulp-autoprefixer'),
  notify = require('gulp-notify'),
  plumber = require('gulp-plumber'),
  sass = require('gulp-sass'),
  encode = require('gulp-convert-encoding'),
  replace = require('gulp-replace');

//------------------------------------------------------------------------------
// PC:sass / scss / css
//------------------------------------------------------------------------------

const compilePcSass = () =>
  src(`${__dirname}/pc/**/*.scss`)
    .pipe(
      plumber({
        errorHandler: notify.onError('<%= error.message %>')
      })
    )
    .pipe(
      sass({
        outputStyle: 'expanded'
      }).on('error', sass.logError)
    )
    .pipe(
      autoprefixer({
        browsers: [
          'last 2 versions',
          'ie >= 9',
          'ChromeAndroid >= 6',
          'Android >= 6',
          'iOS >= 9'
        ]
      })
    )
    .pipe(replace('UTF-8', 'Shift_JIS'))
    .pipe(replace('utf-8', 'Shift_JIS'))
    .pipe(encode({ to: 'Shift_JIS' }))
    .pipe(dest(`${__dirname}/path/to/css/`));

//------------------------------------------------------------------------------
// SP:sass / scss / css
//------------------------------------------------------------------------------

const compileSpSass = () =>
  src(`${__dirname}/sp/**/*.scss`)
    .pipe(
      plumber({
        errorHandler: notify.onError('<%= error.message %>')
      })
    )
    .pipe(
      sass({
        outputStyle: 'expanded'
      }).on('error', sass.logError)
    )
    .pipe(
      autoprefixer({
        browsers: [
          'last 2 versions',
          'ie >= 9',
          'ChromeAndroid >= 6',
          'Android >= 6',
          'iOS >= 9'
        ]
      })
    )
    .pipe(replace('UTF-8', 'Shift_JIS'))
    .pipe(replace('utf-8', 'Shift_JIS'))
    .pipe(encode({ to: 'Shift_JIS' }))
    .pipe(dest(`${__dirname}/path/to/css/`));

//------------------------------------------------------------------------------
// PC:watch
//------------------------------------------------------------------------------
const watchPcSassFiles = () =>
  watch(`${__dirname}/pc/**/*.scss`, compilePcSass);

//------------------------------------------------------------------------------
// SP:watch
//------------------------------------------------------------------------------
const watchSpSassFiles = () =>
  watch(`${__dirname}/sp/**/*.scss`, compileSpSass);

exports.default = parallel(watchPcSassFiles, watchSpSassFiles);
```