Gulp4の変更点と新しい書き方

[gulpjs公式ブログ](https://medium.com/gulpjs/version-4-now-default-92c6cd4beb45)でもアナウンスされたように、2018年12月にGulp4が正式にリリースされました。Gulp4未満は数ヶ月後から非推奨になるようです。

「Gulp4」で検索すると、それぞれが違う書き方をしていました。この記事では[gulp.js](https://gulpjs.com/)の公式ドキュメントを参考に、Gulp3からGulp4に移行するための情報をまとめています。


- `gulp.task()`が非推奨になり、関数宣言と`exports`が推奨になった
- run-sequenceを使った直列・並列処理を、公式APIの`series()`と`parallel()`で実行できるようになった
- `gulp.watch()`の第二引数が配列から関数名になった
- 公式APIに`sourcemaps`オプションが追加された
- 公式APIの`gulp.lastRun()`で差分ビルドができるようになった

## `gulp.task()`が非推奨になり、関数宣言と`exports`が推奨になった
[Creating Tasks](https://gulpjs.com/docs/en/getting-started/creating-tasks)にあるように、関数宣言（`function タスク名(){}`）でプライベートタスクを作成、`exports`でパブリックタスクにする書き方へ変わりました。

```js
// 非推奨
gulp.task('task', function() {
  ...
});
gulp.task('task', () => {
  ...
});
```

```js
// 推奨
function task() { // この時点では`gulp`コマンドで実行不可能
  ...
}
exports.task = task; // `gulp task`で実行可能
```

[Async Completion](https://gulpjs.com/docs/en/getting-started/async-completion)にあるように、`return`を返さない処理の場合はコールバック関数で完了を知らせる必要があります。

基本的なタスクは`return gulp`から始めれば大丈夫です。

```js
// OK
function task() {
  return
    gulp
      .src('')
      .pipe()
      .pipe(
        gulp.dest()
      )
  );
}
```

[browser-sync](https://www.browsersync.io/)では`return`を使っていないので、`done()`というコールバック関数を実行するようにしました（公式では`cb()`となっています）。

```js
// OK
function serve(done) {
  browserSync({
    server: {
      baseDir: '',
    },
    ghostMode: false,
    open: 'external',
    notify: false,
  });
  done();
}
```

## run-sequenceを使った直列・並列処理を、公式APIの`series()`と`parallel()`で実行できるようになった
直列・並列処理は[run-sequence](https://github.com/OverZealous/run-sequence)をインストールしていましたが、直列を[`series()`](https://gulpjs.com/docs/en/api/series)で、並列を[`parallel()`](https://gulpjs.com/docs/en/api/parallel)で実行できるようになりました。

```js
// run-sequenceを使った場合
const runSequence = require('run-sequence');
gulp.task('default', function() {
  runSequence(
    ['pug', 'sass', 'js'],
    'server'
    );
});
```

```js
// `series()`と`parallel()`を使った場合
exports.default = gulp.series(
  gulp.parallel(pug, sass, js),
  server,
);
```

## `gulp.watch()`の第二引数が配列から関数名になった
Gulp3では、`gulp.watch()`の第二引数に配列でタスク名を渡していましたが、Gulp4ではタスクの関数を渡すようになりました。

```js
// Gulp3
gulp.watch([''], ['task']);
```

```js
// Gulp4
gulp.watch('', task);
```

`gulp.series`や`function()`を渡すこともできます。

```js
gulp.watch('', gulp.series(task));
gulp.watch('', gulp.parallel(task));
gulp.watch('', function(done) {
  done();
});
```

## 公式APIに`sourcemaps`オプションが追加された
sourcemapsは[gulp-sourcemaps](https://github.com/gulp-sourcemaps/gulp-sourcemaps)をインストールしていましたが、[`gulp.src()`](https://gulpjs.com/docs/en/api/src)と[`gulp.dest()`](https://gulpjs.com/docs/en/api/dest)の`options`に`sourcemaps`が追加されました。

```js
// Gulp3
const sourcemaps = require('gulp-sourcemaps');
gulp.task('task', function() {
  gulp.src('')
    .pipe(sourcemaps.init())
    .pipe(sourcemaps.write())
    .pipe(gulp.dest(''));
});
```

```js
// Gulp4
function task() {
  return gulp
    .src('', {
      sourcemaps: true, // init
    })
    .pipe(dest('',{
      sourcemaps: true, // write
    }));
}
```

`gulp.dest()`で`sourcemaps: true`にするとインラインで、パスを渡すと別ファイルで出力されます。たとえば`dest`の`sourcemaps: '.'`にすると同階層に出力されます。


## 公式APIの`gulp.lastRun()`で差分ビルドができるようになった
差分ビルドには[gulp-changed](https://github.com/sindresorhus/gulp-changed)などをインストールしていましたが、[`gulp.src()`](https://gulpjs.com/docs/en/api/src)の`since`オプションに[`gulp.lastRun()`](https://gulpjs.com/docs/en/api/lastrun)を渡して実行できるようになりました。

```js
// gulp-changedの場合
const changed = require('gulp-changed');
gulp.task('image', function() {
  return gulp
    .src(src.img)
    .pipe(changed(dest.img)) // `gulp.dest()`のパスを渡す
    .pipe(imagemin())
    .pipe(gulp.dest(dest.img));
);
```

```js
// `gulp.lastRun()`の場合
function image() {
  return gulp
    .src(src.img, {
      since: lastRun(image) // タスク名を渡す
    })
    .pipe(imagemin())
    .pipe(dest(dest.img));
}
```

試したときは若干不安定に感じたので、まだ導入はしていません。

## 名前の衝突に気をつける
書き方が変わって名前の衝突が起こりやすくなっています。

実際にあったのが、browser-syncでの名前の衝突。

```js
// NG
const browserSync = require('browser-sync');
function browserSync(done) { // const browserSyncと衝突している
  browserSync({
    server: {
      baseDir: '',
    },
    ghostMode: false,
    open: 'external',
    notify: false,
  });
  done();
}
```

`browserSync`が衝突していてエラーになりました。

```js
function browserSync(done) {
^

SyntaxError: Identifier 'browserSync' has already been declared
```

ここでは`serve`というタスク名にすることで回避しました。

```js
// OK
const browserSync = require('browser-sync');
function serve(done) {
  browserSync({
    server: {
      baseDir: '',
    },
    ghostMode: false,
    open: 'external',
    notify: false,
  });
  done();
}
```

公式ドキュメントで以下のように名前付きインポートをしている箇所があります。

```js
const { src, dest, lastRun, watch } = require('gulp');
```

`const gulp = require('gulp');`だと`gulp.src()`のようにしますが、名前付きインポートを使うと`src()`のようにできます。

ただ、`src`・`dest`はパスを変数化するときに使ったり、`watch`はタスク名に使っていることも多いです。名前付きインポートは使わない方がベターなのかなと思いました。

Gulp4に移行したgulpfile.jsは以下のリンクを参考にしてください。

https://github.com/manabuyasuda/website-template/blob/v4/gulpfile.js