帰ってきたGulp 4

2014年あたりから、v4が出なくてやきもきしていましたが、待つこと3年(4年?)、2018年のはじめに`v4.0.0`が[リリース](https://github.com/gulpjs/gulp/releases/tag/v4.0.0)されました。2018年現在の視点で、v4をチェックしてみたいと思います。

## タスクは関数に

以下、[GitHubにある例](https://github.com/gulpjs/gulp#use-latest-javascript-version-in-your-gulpfile)からの抜粋です。(多少変形しています)

```javascript
import {src, dest} from 'gulp'
import less from 'gulp-less'
import rename from 'gulp-rename'
import cleanCSS from 'gulp-clean-css'

export default () => src('src/style.css')
  .pipe(less())
  .pipe(cleanCSS())
  .pipe(rename({basename: 'main', suffix: '.min'}))
  .pipe(dest('somewhere/'))
```

今まで`gulp.task('default', function () {...})`とやっていた部分が、ただの関数をエクスポートする形になります。

> **メモ**: 後方互換も維持されていて、`gulp.task()`も引き続き使えます。

## JavaScriptの作法でエクスポート

複数のタスクがある場合は、それぞれをエクスポートする形です。`series`や`parallel`で、順番に実行したり並行実行を指定できます。

```javascript
import {src, dest, series} from 'gulp'

export const styles = () => src(...).pipe(...) // 略
export const scripts = () => src(...).pipe(...) // 略
export default series(styles, scripts)
```

以前よりJavaScriptの文法に沿った書き方ができる分、だいぶ読みやすい!

## タスクの中でgulpを使わなくてもいい

`v3`でもそうでしたが、Promiseを返すものなら何でもOKです。`v4`になって、というよりも`async/await`が使えるようになって、だいぶ書きやすさが増しました。

```javascript
import {src, dest} from 'gulp'
import del from 'del'

export const clean = () => del(['assets'])
export const someAsyncFunc = async () => {...} // 略
export const other = () => src(...).pipe(...) // 略
```

## インストール

インストールに際して、

```bash
$ npm install --save-dev gulp
```

だと、まだv3系がインストールされます。代わりに次のようにします。

```bash
$ npm install --save-dev gulp@next
```

## CLIを使う

CLIの使い方はあまり変化ありませんが、`npm@5.2.0`から、`npx`コマンドが追加されたので、次のような呼び出しが可能です。

```bash
$ npx gulp
```

グローバルに`gulp-cli`を入れなくて済みます。

> **メモ**: `gulp-cli`は、`gulp`の依存性として、ローカルの`node_modules`にインストールされています。

なお、上述の例のように`import`/`export`を使う場合、Nodeのネイティブ対応はまだなので、トランスパイルの必要があります。[GitHubのドキュメントにbabelを使う方法](https://github.com/gulpjs/gulp#use-latest-javascript-version-in-your-gulpfile)が書かれていますので、そちらをどうぞ。

あるいは、babelが大げさに感じるなら`reify`でも動きました。

```bash
$ npm install --save-dev reify
$ npx gulp --require reify
```


## CLIを使わない

`gulp.task()`の呪縛から離れたので、gulpのスクリプトを直接「プログラマブルに」扱いたいケースは増えそうです。

```javascript
// build.js
import {src, dest, series} from 'gulp'

const styles = src(...).pipe(...) // 略
const scripts = src(...).pipe(...) // 略
const build = series(styles, scripts)

build()
```

上記の例は、`node build.js`すれば動きます。(実際には、babelかreifyが必要)

なお、gulpのタスクは基本的にはストリームなので、`async`/`await`では直接扱えません。`gulp.series`や`gulp.parallel`で制御するのが良いでしょう。どうしても使いたい場合は[stream-to-promise](https://www.npmjs.com/package/stream-to-promise)あたりで、Promiseの文脈に持ち込む手はあります。

```javascript
// build2.js
import {src, dest} from 'gulp'
import toPromise from 'stream-to-promise'

const styles = src(...).pipe(...) // 略
const scripts = src(...).pipe(...) // 略

const main = async () => {
  await toPromise(styles)
  await toPromise(scripts)
}
main()
```

> **メモ**: `gulp.start()`はディスコンになりました。

## まとめ

gulp4になって、次の点が寄与してboilerplateから完全に解放されました。:tada:

- タスクがただの関数になったこと
- ES2015の文法

素のNodeのスクリプトを書くのともう変わらないので、タスクランナー嫌い(?)のひとも、改めてチェックしてみても良いかも。

## 雑感

gulpの最大の利点は(今も昔も)次の2点です。プラグインのエコシステムのおかげで、シンプルに書ける面は多分にありますが、それほど本質的ではありません。

- ファイルを`vinyl`ストリームで扱う
- 非同期タスクの実行順序制御 (series, parallel)

この2点は、シェルスクリプトだけで実現するのは厄介なので、引き続きgulpを使う理由として残るように思います。特に前者について、複数ファイルの扱いが秀逸です。この点、シェルスクリプトでは`tar`で固めるしかなく、キレイに書く方法があまりありません。

> **メモ**: 後者については、[npm-run-all](https://github.com/mysticatea/npm-run-all)を使う手もあります。

最近は、シェルスクリプト(`sh`/`bash`)を触らない人も多いので、JavaScriptのプロジェクトについてはタスクもJavaScriptで書くというのは自然な気もします。個人的な指針としては、npm scriptsに書ききれなくなった時は`gulp`の出番です。具体的には

- 複数のファイルを扱う
- 複数のツールを組み合わせる

といったシーンです。

WebPack, [Parcel](https://parceljs.org/)などの包括的なツールの流行りで、元々のgulpの使い途だった、トランスパイル系の操作での需要は減っていますが、2018年も意外と(?)使いどころが残っているgulpでした。