はじめに
Sassのコンパイル、画像の最適化、JSのミニファイ、ファイルの結合……フロントエンド開発には「毎回同じ手順を繰り返すだけの作業」がたくさんあります。これを手作業でやっていると、うっかり一手順飛ばしてビルド漏れの成果物をデプロイしてしまう、といった事故も起きがちです。
Gulpは、こうした定型作業をコードで自動化するためのタスクランナー(ビルドツール)です。設定ファイルを書き連ねるのではなく、Node.jsのストリームを使って「入力ファイルを読み込み → 変換を通し → 出力する」という一連の流れを、パイプでつないだ関数として素直に書けるのが特徴です。この記事では、Gulpの特徴からタスクの組み立て方、ファイル監視までの実践的な使い方を解説します。
Gulpとは
Gulpは、Node.js上で動くタスクランナーで、「ワークフローを自動化・効率化するためのツールキット」を掲げています。プラグイン形式でファイル変換処理を追加していく設計になっており、npm上には3,000を超えるgulp-*系プラグインが公開されています。
主な特徴
- ストリームベースの処理 -
gulp.src()で読み込んだファイルをストリームとして扱い、.pipe()でプラグインをつなげていく。中間ファイルをディスクに書き出さないため高速 - コードとしてのタスク定義 -
gulpfile.jsはただのJavaScriptです。条件分岐やループなど、設定ファイル形式では書きづらいロジックも自然に書ける seriesとparallelによるタスク合成 - タスクを順番に実行するか同時に実行するかを、関数を組み合わせるだけで制御できる- 豊富なエコシステム - Sass、TypeScript、画像最適化、リロードなど、ほぼすべての定型作業に対応するプラグインが揃っている
- プラットフォーム非依存 - Node.js製ですが、PHPや.NETのプロジェクトのアセットビルドに使われることもある
インストール
Gulpの利用には、CLIのグローバルインストールと、プロジェクトごとのgulp本体のインストールの両方が必要です。
npm install --global gulp-cli
npm install --save-dev gulp
プロジェクトルートにgulpfile.jsを作成すれば準備は完了です。
基本的な使い方
Gulpのタスクは「何かを返す関数」として定義します。gulp.src()でファイルを取得し、変換用のプラグインを.pipe()でつなぎ、最後にgulp.dest()で出力先に書き出す、という形が基本形です。
// gulpfile.js
const { src, dest, series, parallel, watch } = require('gulp');
const less = require('gulp-less');
const cleanCSS = require('gulp-clean-css');
const rename = require('gulp-rename');
function styles() {
return src('src/styles/**/*.less')
.pipe(less())
.pipe(cleanCSS())
.pipe(rename({ suffix: '.min' }))
.pipe(dest('dist/styles/'));
}
exports.styles = styles;
exports.default = styles;
CLIからgulp stylesと実行すると、src/styles/以下のLESSファイルがコンパイル・圧縮され、dist/styles/に出力されます。タスク名を指定しないgulpだけの実行ではexports.defaultが呼ばれます。
実践的なユースケース
複数タスクを順番・同時に実行する
CSSのビルドとJSのビルドのように依存関係のないタスクはparallel()で同時実行し、「クリーン → ビルド」のように順序が重要なタスクはseries()で直列実行します。この使い分けによって、ビルド全体の待ち時間を無駄なく短縮できます。
const { src, dest, series, parallel } = require('gulp');
const del = require('del');
function clean() {
return del(['dist']);
}
function styles() {
return src('src/styles/**/*.less')
.pipe(require('gulp-less')())
.pipe(dest('dist/styles/'));
}
function scripts() {
return src('src/scripts/**/*.js')
.pipe(require('gulp-uglify')())
.pipe(dest('dist/scripts/'));
}
// clean完了後に、styles・scriptsを同時実行
exports.build = series(clean, parallel(styles, scripts));
gulp buildを実行すると、まずdistディレクトリを削除し、その完了を待ってからstylesとscriptsが並行して走ります。parallelの中身を入れ替えるだけで、実行順序を気にせずタスクを増やせます。
ファイル変更を監視して自動ビルドする
開発中はファイルを保存するたびに手動でビルドコマンドを叩くのは手間です。watch()APIを使えば、指定したファイルパターンの変更を検知して該当タスクを自動実行できます。
const { watch, series } = require('gulp');
function watchFiles() {
// LESSファイルの変更を検知したらstylesタスクを実行
watch('src/styles/**/*.less', styles);
// JSファイルの変更を検知したらclean→scriptsの順で実行
watch('src/scripts/**/*.js', series(clean, scripts));
}
exports.watch = watchFiles;
watch()に渡すタスク関数は非同期完了を通知する必要があります(ストリームをreturnするか、コールバックを呼ぶ)。同期的に終わらないタスクを渡すと、2回目以降の変更で実行されなくなるので注意してください。デフォルトでは実行中のタスクへの再実行はキューイングされますが、{ queue: false }オプションで同時実行に切り替えることもできます。
エラーで監視タスクを止めない
.pipe()チェーンの途中でエラーが起きると、デフォルトではストリームが破棄され、watchタスク自体が止まってしまいます。開発中はエラーを画面に表示しつつ監視を継続したい場面が多いため、plumberのようなプラグインでエラーハンドリングを挟むのが定石です。
const { src, dest } = require('gulp');
const plumber = require('gulp-plumber');
const sass = require('gulp-sass')(require('sass'));
function styles() {
return src('src/styles/**/*.scss')
.pipe(plumber()) // Sassの構文エラーでwatchが停止するのを防ぐ
.pipe(sass())
.pipe(dest('dist/styles/'));
}
exports.styles = styles;
plumber()を.pipe()チェーンの先頭に挟むだけで、以降のプラグインで例外が発生してもストリームが破棄されずコンソールにエラーが出力され、watchは次の変更を待ち続けます。開発用と本番ビルド用でタスクを分け、本番ビルドではplumberを外してエラーで確実に失敗させる、という使い分けもよく行われます。
まとめ
Gulpは、ストリームで「読み込み・変換・出力」をつなぐというシンプルな設計思想のまま、series/parallelによるタスク合成とwatchによるファイル監視で、実務に必要な自動化を一通りカバーできるツールです。設定ファイルの記法を新たに覚える必要がなく、素のJavaScriptでタスクを組み立てられる分、条件分岐やカスタムロジックを差し込みやすいのも強みです。まずは手元のプロジェクトで繰り返している定型作業を1つ、gulpfile.jsに書き起こすところから試してみてください。