JavaScript
task
gulp

帰ってきたGulp 4

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

タスクは関数に

以下、GitHubにある例からの抜粋です。(多少変形しています)

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の作法でエクスポート

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

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が使えるようになって、だいぶ書きやすさが増しました。

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

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

インストール

インストールに際して、

$ npm install --save-dev gulp

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

$ npm install --save-dev gulp@next

CLIを使う

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

$ npx gulp

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

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

なお、上述の例のようにimport/exportを使う場合、Nodeのネイティブ対応はまだなので、トランスパイルの必要があります。GitHubのドキュメントにbabelを使う方法が書かれていますので、そちらをどうぞ。

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

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

CLIを使わない

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

// 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.seriesgulp.parallelで制御するのが良いでしょう。どうしても使いたい場合はstream-to-promiseあたりで、Promiseの文脈に持ち込む手はあります。

// 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を使う手もあります。

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

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

といったシーンです。

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