gulp4のインクリメンタルビルド


ひさびさにgulpに戻ってきて、初級者脱出でつまづいたところをメモしておきます。

記事の対象は「既存のプラグインを組み合わせてビルドやリントをかけられる」くらいのレベルです。

スタート

普通にビルドするときは下のような感じ。
ひとまず Babel 想定ですが、TypeScriptやSCSSなども同じです。

gulpfile.js
const gulp = require('gulp');
const babel = require('gulp-babel');

const SRC = 'src/**/*.js';
const DEST = 'build';

gulp('default', () => {
  return gulp.src(SRC)
    .pipe(babel, { babelrc: true })
    .pipe(gulp.dest, DEST);
});

インクリメンタル

インクリメンタルビルドは下のような感じ。
lastRun({ since: '<task name> })`がキモです。

gulpfile.js
gulp('build:incremental', () => {
  return gulp.src(SRC, lastRun({ since: 'build:incremental' }))
    .pipe(babel, { babelrc: true })
    .pipe(gulp.dest, DEST);
});

実際には gulp.watch と組み合わせることが多いです。

gulpfile.js
gulp('watch', () => {
  return gulp.watch('SRC', gulp.parallel('build:build'))
});

重複をなくす

最初にコードを載せます。

gulpfile.js
...
const through = require('through2');
const lazypipe = require('lazypipe');

...

const buildTasks = lazypipe()
  .pipe(babel, { babelrc: true })
  .pipe(gulp.dest, DEST);

gulp('build', () => {
  return gulp.src(SRC)
    .pipe(buildTasks())
    .pipe(through());
});

gulp('build:incremental', () => {
  return gulp.src(SRC, { since: gulp.lastRun('build:incremental') })
    .pipe(buildTasks())
    .pipe(through());
});

新しいものがたくさん出てきました。

まず、lazypipe です。これは事前に複数のプラグインのpipeを組み合わせて新しいプラグインをつくるものです。
これを使うと、ワークフローの共通化ができるようになります。
元になるプラグインを直接呼び出さず、それ自体を引数とともに渡していることに注意してください。

ただし、重大な欠点としてそのまま使うと、タスクが終了しません
これは既知の問題ですが、作者は gulp4 に対応する予定がないそうです(gulp あるある)。
ではどうするか、というのが上のワークアラウンドです。
入力に手を加えず後続に流すthroughを使うことで正しくストリームの終了イベントがハンドルできるようになります。

今回のコードは Github に置いてあります。