gulpの基本的な使い方(gulp.jsの基礎をしっかり理解する)

ここのところのgulpの勢いに負けて、自分もついにGruntからgulpに移行しました。gulpはGruntと同様フロントエンド開発用のタスクランナーです。最初はGruntとそれほど変わらないだろうと思っていましたが、タスクを書いてみるとその違いがすぐにわかりました。シンプルに書けるだけでもgulpはかなり良いです。今回、自分の理解を深めるついでに基本的なタスクの書き方をまとめてみました。

gulpjs
gulp.js – the streaming build system

gulpの特徴

まずgulpの特徴を簡単に書いていきます。

  • ストリーム: gulpは、「the streaming build system」と言われているようにNode.jsのストリーム(ファイルのパスとファイルの中身の情報を持ったオブジェクト)を使って処理を行っていきます。そのため中間ファイルを生成することなくタスクが実行されていくため高速に処理がなされます。
  • 並列処理: さらにgulpは、処理が一つ一つ実行されるのではなく、同時進行で複数の処理が実行されるようになっているため高速です。
  • メソッドチェーン: jQueryのような「メソッドチェーン」を使ってタスクを書くため、簡潔にまとめることができ視認性も良いです。Gruntは、馴染み深いJSON形式で書けますが、タスクの量が増えていくとネスト地獄に陥りやすいです。
  • 監視用のタスクwatchがデフォルト: Gruntの場合は、監視用のタスクを別途インストールする必要がありましたが、gulpでは「gulp.watch()」メソッドとして最初から用意されています。
  • プラグインの質と量: ここはGruntに劣っている部分かもしれません。主要なものはglupでも出揃ってきていますが、急いで作ったためか粗雑な作りとなっているものもあります。

gulpを使う準備をする

まずgulpを使う前提としてNode.jsとnpmがインストールされている必要があります。それぞれのインストール方法は当ブログの以下の記事を参考にしてください。

gulpをシステムにインストールする

[gulp]コマンドを使えるようにするために、[npm install]コマンドに[-g]オプションをつけてgulpをシステムにインストールします。

$ npm install -g gulp

package.jsonファイルを作成する

プロジェクト用のディレクトリを作ったら、その中に移動し、プロジェクトのパッケージを管理するためのpackage.jsonファイルを作成します。package.jsonファイルは以下のコマンドで自動生成されます。

$ npm init

gulpをプロジェクトのdevDependenciesにインストールする

プロジェクトディレクトリ内にgulpをインストールします。[save-dev(または-D)]オプションをつけると、package.jsonファイルに記録されます。

$ npm install --save-dev gulp

gulpfile.jsファイルを作成する

プロジェクトディレクトリ内に、タスクを記述するためのgulpfile.jsファイルを作成します。

$ touch gulpfile.js

gulpfile.jsにタスクを書き、gulpを実行する

例としてgulp-uglifyというプラグインを使ってファイルの圧縮(ミニファイ)を行う簡単なタスクを書いて実行してみます。

gulpプラグインをインストールする

gulpのタスクは、自分で好きなように書いていけますが、一般的にはgulpプラグインを使って書いていきます。gulpプラグインは以下にて検索できます。

プラグインのインストールは以下のコマンドで行います。プロジェクトのディレクトリ内で実行してください。save-dev」オプションをつけると、package.jsonに記録されます。

$ npm install --save-dev [gulpプラグイン名]
# または
$ npm i -D [gulpプラグイン名]

今回はgulp-uglifyを使うので、以下のようにコマンドを入力してgulp-uglifyをインストールします。

$ npm install --save-dev gulp-uglify

gulpfile.jsにタスクを書く

gulpfile.jsファイルにタスクを以下のように書きます。

var gulp = require('gulp'); // gulpを読み込む
var uglify = require('gulp-uglify'); // gulp-uglifyを読み込む

// 「uglify」タスクを定義する
gulp.task('uglify', function () {
  // タスクを実行するファイルを指定
  gulp.src('./src/js/example.js')
    // 実行する処理をpipeでつないでいく
    .pipe(uglify()) // uglifyを実行
    .pipe(gulp.dest('dist')) // 圧縮したファイルをdistに出力
});
gulpfile.js

以下は、gulpfile.js内にタスクを書く際に覚えておきたいメソッドです。

gulp.task(name[, deps], fn)

タスクを定義するためのメソッドです。タスク名とタスクを実行した時の処理を書いていきます。

  • name {String}: 実行するタスク名。[gulp タスク名]コマンドでタスクの実行が可能となります。
  • deps {Array}: タスクを実行する前に完了しておきたい別のタスク名。タスクを実行する順番を保証したいときに指定します。
  • fn: 実行するタスク内容。一般的に「gulp.src().pipe(someplugin())」の形式で書いていきます。

gulp.src(globs[, options])

タスクの対象となるファイルのストリームを作ります。ストリームはpipeを通してプラグインに渡されるようになります。

  • globs {String or Array}: 読み込みたいファイルのグロブ(パス名のパターン文字列)またはそれらの配列
  • options {Object}: 以下のオプションを指定します。デフォルト値は、{buffer: true, read: true, base: “”}となっています。
    • buffer {Boolean}: 読み込んだファイルのストリームとして、trueなら「バッファファイル」、falseなら「file.contents」を返します。大きなファイルを扱う場合はfalseが便利。
    • read {Boolean}: falseにすると「file.contents」をnullで返し、ファイルの内容を一切読み込まなくなります。
    • base {String}: ここに指定したディレクリ構成を元にgulp.dest()に指定するディレクトリの構成が形成されます。

gulp.dest(path[, options])

pipeで渡されたストリームのデータをpathで指定したディレクトリ先のファイルに書き込みます。

  • path {String or Function}: pipeで渡されたデータをファイルに書き込むための出力用フォルダのパスを指定します。または、Vinylファイルのインスタンスが渡される関数を指定します。
  • options {Object}: 以下のオプションを指定します。デフォルト値は、{cwd: process.cwd(), mode: ‘0777’}となっています。
    • cwd {string}: cwd(Current Working Directory)を指定。指定したcwdを基準にpathが形成されます。
    • mode {string}: pathで指定したフォルダのパーミッションを指定します。

readable.pipe(destination, [options])

こちらはNode.jsのメソッドとなります。ストリームから全てのデータを引き出し、与えられた行き先に書き込みを行います。

  • destination {Writable Stream}: データの書き込み先
  • options {Object}: 以下のオプションを指定します。デフォルト値は、{end: true}となっています。
    • end {Boolean}: 読み込み元が終了すると書き込み先を終了します。

その他詳細については、gulpのドキュメントを参考にしてください。

Gruntで同じ内容のタスクを書いたとすると

ちなみに上記のgulpfile.jsの内容をGruntのGruntfile.jsで書き換えたとすると以下のようになります。違いがわかると思います。ネストが多いですね。

module.exports = function (grunt) {

  // Gruntの設定
  grunt.initConfig({
    uglify: { // タスク名
      dist: { // ターゲット名
        files: { // ファイルの指定(圧縮後のファイル: 圧縮前のファイル)
          'dist/example.js': './src/js/example.js'
        }
      }
    }
  });

  // gruntプラグイン(grunt-contrib-uglify)を読み込む
  grunt.loadNpmTasks('grunt-contrib-uglify');
  // defaultとしてuglifyタスクを登録する
  grunt.registerTask('default', ['uglify']);

};
Gruntfile.js

タスクを実行する

[gulp タスク名]でコマンドを叩くと、タスクが実行されます。

$ gulp uglify
[21:23:36] Using gulpfile ~/Sites/project/example/gulpfile.js
[21:23:36] Starting 'uglify'...
[21:23:36] Finished 'uglify' after 8.51 ms

Gruntと同じように、以下のように「default」タスクとして「uglify」タスクを登録しておくこともできます。「default」タスクは、[gulp]コマンドのみで実行されます。

gulp.task('default', ['uglify']);
gulpfile.js

タスクの中に複数の処理を書く

次にタスクの中に複数の処理を書いていく方法を説明します。gulpの場合、タスクの対象となるファイル(グロブ)が同じ場合、複数の処理をpipeでつなげていくことができます。ここがGruntとの大きな違いでもあります。

今回は「./src/js」ディレクトリ内のjsファイルに対して、以下のような処理を行うタスクを書いていくことにします。

  • ファイルを結合し、bundle.jsとして出力→ gulp-concat
  • ファイルにライセンスを挿入→ gulp-header
  • ファイルを圧縮→ gulp-uglify
  • distディレクトリに出力

つまり、複数のJSファイルを結合し、ライセンスを挿入し、さらに圧縮した状態でbundle.jsとして出力するタスクとなります。

複数のgulpプラグインをインストールする

以下のように半角スペースでgulpプラグインを列挙して[npm install]コマンドを実行すると、1度に複数のgulpプラグイン(に限らずnpmパッケージ)をインストールすることができます。

$ npm install --save-dev gulp-concat gulp-header gulp-uglify

gulpfile.jsにタスクを書く

gulpfile.jsには以下のようにタスクを書きます。細かい解説は書かないので、コメントを参考にしてください。

// gulpを読み込む
var gulp = require('gulp');
// gulpプラグインを読み込む
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var header = require('gulp-header');

// ライセンス用の情報を取得するためにpackage.jsonを読み込む
var pkg = require('./package.json');
// ライセンス情報を生成する
var banner = ['/**',
  ' * <%= pkg.name %> - <%= pkg.description %>',
  ' * @version v<%= pkg.version %>',
  ' * @link <%= pkg.homepage %>',
  ' * @license <%= pkg.license %>',
  ' */',
  ''].join('\n');

// jsタスクを定義する
gulp.task('js', function () {
  // タスクを実行するグロブを指定
  gulp.src('./src/js/*.js')
    // 実行する処理を実行する順にpipeでつないでいく
    .pipe(concat('bundle.js')) //ファイルを結合し、bundle.jsファイルとして出力
    .pipe(uglify({preserveComments: 'some'})) // ファイルを圧縮する(ライセンス情報は外す)
    .pipe(header(banner, {pkg: pkg})) //ライセンス情報を挿入
    .pipe(gulp.dest('./dist')); //distディレクトリに出力
});

// jsタスクをdefaultタスクとして登録
gulp.task('default', ['js']);
gulpfile.js

処理を一塊にすることができるので、すごくシンプルでわかりやすく書けます。

gulpプラグインのオプション設定

プラグインのオプションは、プラグイン関数の引数にJSON形式でセットします。上記のgulp-uglifyのオプション設定箇所を以下に抜き出してみます。「{preserveComments: ‘some’}」がオプションの部分です。ライセンス部分は圧縮しないというオプションとなっています。

.pipe(uglify({preserveComments: 'some'}))

プラグインのオプションはnpmのプラグインの紹介ページで確認できます。

複数のタスクを順序立てて実行する

タスクを並列処理で実行するgulpでは、複数のタスクを順序立てて実行させるには、少々工夫が必要です。ここではシンプルに説明するためにタスクにはgulpプラグインを使わずに処理を書いていきます。

複数のタスクを設定する

「task1」タスクと「task2」タスクをgulpfile.jsに書きます。

var gulp = require('gulp');

gulp.task('task1', function () {
  consolo.log('Hello');
});

gulp.task('task2', function () {
  console.log('World');
});
gulpfile.js

複数のタスクを「default」タスクに登録して実行すると…

「default」タスクとして、「task1」タスクと「task2」タスクを登録します。[gulp]コマンドを実行すると、「Hello」→「world」と順番に表示されることを期待します。

gulp.task('default', ['task1', 'task2']);
gulpfile.js

この状態で[gulp]コマンドを実行すると、「task1」タスクと「task2」タスクは並列に処理されるので、「task2」タスクの処理の方が「task1」タスクの処理よりも早く終了した場合、「World」→「Hello」と表示されてしまい意図した動きとはなりません

gulp.task()メソッドの第2引数を使う

上の方で説明しましたが、gulp.task()メソッドの第2引数には、「タスクを実行する前に完了しておきたい別のタスク名」を配列としてセットすることができるようになっています。これを利用して、「task2」タスクより先に完了させておきたい「task1」タスクを、「task2」タスクを定義しているgulp.task()メソッドの第2引数にセットします。

さらに「task1」タスクの処理を返すためにreturnをつけて、「default」タスクには「task2」タスクだけを登録するようにします。

var gulp = require('gulp');

gulp.task('task1', function () {
  return consolo.log('hello');
});

gulp.task('task2', ['task1'], function () {
  console.log('world');
});

gulp.task('default', ['task2']);
gulpfile.js

これで意図した順番でタスクが実行されるようになります。

$ gulp
[21:47:24] Using gulpfile ~/Sites/project/example/gulpfile.js
[21:47:24] Starting 'task1'...
hello
[21:47:24] Finished 'task1' after 76 μs
[21:47:24] Starting 'task2'...
world
[21:47:24] Finished 'task2' after 28 μs
[21:47:24] Starting 'default'...
[21:47:24] Finished 'default' after 6.39 μs

順序を保証する書き方は、ドットインストールの「#06 タスクの実行順序について理解しよう」で紹介されている方法を使いましたが、他にもやり方はあります。以下のブログにいくつか他のやり方が書いてありますので併せてご確認ください。

gulp.watch()でファイル状態を監視する

最後によく使うであろうファイルの状態を監視するwatchタスクについて紹介します。Gruntとは違って、gulpではデフォルトでgulp.wathc()メソッドが用意されています。特に特別なプラグインをインストールする必要はありません。

gulp.watch()メソッドを使う

gulp.watch(glob [, opts], tasks)

  • glob String or Array: 状態の変化を監視したいファイルのグロブ(パス名のパターン文字列)またはそれらの配列。
  • opts Object: gazeに渡されるオプション。
  • tasks Array: 監視しているファイルの状態に変化があった際に実行したいタスク名(gulp.task()に追加されているもの)の配列。

gulpfile.jsへの書き方は以下の通りです。gulp.task()でwatchタスクを定義し、タスクとしてgulp.watch()メソッドを実行するようにします。

// watchタスクを定義
gulp.task('watch', function() {
  // 監視するファイルと、実行したいタスク名を指定
  gulp.watch('./src/js/example.js', ['default']);
});
gulpfile.js

上記で書いたgulp-uglifyのタスクにファイルの状態監視を適用させてみます。

var gulp = require('gulp');
var uglify = require('gulp-uglify');

gulp.task('uglify', function () {
  gulp.src('./src/js/example.js')
    .pipe(uglify())
    .pipe(gulp.dest('dist'))
});

gulp.task('watch', function() {
	gulp.watch('./src/js/example.js', ['default']);
});

gulp.task('default', ['uglify', 'watch']);
gulpfile.js

[gulp watch]コマンドで、ファイルの状態監視を開始します。ファイルに変化があった場合、uglifyタスクが実行されるようになります。

$ gulp watch
[19:15:35] Using gulpfile ~/Sites/project/example/gulpfile.js
[19:15:35] Starting 'watch'...
[19:15:36] Finished 'watch' after 42 ms

gulp-plumberで監視を止めないようにする

gulpでは、ファイルの変更時にエラーが出た場合、gulp.watch()の実行が止まってしまいます。これを回避するためにgulp-plumberというgulpプラグインを使うことで、エラーが出た場合でもgulp.watch()の実行自体は止めないようにしておくとよいです。

以下のコマンドでgulp-plumberをインストールします。

$ npm install --save-dev gulp-plumber

エラーが出そうな処理の前にgulp-plumberの処理を入れておきます。

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var plumber = require('gulp-plumber');

gulp.task('uglify', function () {
  gulp.src('./src/js/example.js')
    .pipe(plumber())
    .pipe(uglify())
    .pipe(gulp.dest('dist'))
});

gulp.task('watch', function() {
  gulp.watch('./src/js/example.js', ['default']);
});

gulp.task('default', ['uglify', 'watch']);
gulpfile.js

まとめ

長々と説明してきましたが、これでgulpを一通り使えるようになると思います。Grunt使いの方で、まだgulpへの移行に踏み切れない方も多くいると思いますが、まずは試されることをお勧めします。自分もずっとGruntでやってきましたが、gulpの書き方に慣れるとGruntにはもう戻れない感じです。Gruntの更新も2014年5月から止まっているし、gulp全盛の時期はしばらく続くと思います。

フロントエンドの世界は本当に動きが早くて、自分としてはある程度定番として落ち着いてきた技術を使おうと思っていますが、そろそろ覚えようかなと思った頃には、また次の技術が主流となり始めていたりしてついていくのが大変です。Gruntなのかgulpなのか言っているうちに今度は[npm run]を使おうという動きも強くなってきています

最後に自分も参考にしたドットインストールのgulpの動画も紹介しておきます。gulp-webserverを使ったLivereloadを実現する方法なども紹介されているので参考にしてみてください。

コメント一覧

  • 必須

コメント