gulpプラグインの基本構造（プラグイン開発者向け）


プラグイン開発に関する詳細な情報は[gulpの公式ドキュメント](https://github.com/gulpjs/gulp/tree/master/docs)を参照のこと。また、開発したプラグインを公開する場合はgulp公式による[プラグイン開発ガイドライン](https://github.com/gulpjs/gulp/blob/master/docs/writing-a-plugin/guidelines.md)は必読（MUST readと書かれている）。[README規約](https://github.com/gulpjs/gulp/blob/master/docs/writing-a-plugin/readme-conventions.md)も見ておくとよい。

（2014/2/9追記：プラグイン開発ガイドラインの[日本語訳](http://qiita.com/morou/items/abc05013053f6110bcb1)を公開）


なお、gulpプラグインはnode.jsパッケージの一種なので、公開する際の手順や[package.jsonの記述方法](https://npmjs.org/doc/json.html)などはnode.jsのドキュメントを参照のこと。

## プラグインの基本構造
- gulpプラグインの実態（i.e. pipeに渡すもの）はnode.jsの[Stream.Transform](http://nodejs.org/api/stream.html#stream_class_stream_transform)のサブクラス
- Stream.Transformとは、ストリームからの入力を受け取り、加工し、ストリームに出力するもの
- Stream.Transformのサブクラスを直接作って実装しても良いが、gulpプラグインではStream.Transformラッパーである[through2](https://github.com/rvagg/through2)を利用するのが一般的
- through2がストリームの入出力部分を面倒見てくれるので「加工」の実装に注力するだけ
- through2のオブジェクトを生成する時に「加工」用の関数を2つ渡す
 - transformFunction：入力ストリームからファイルを受け取る度に呼び出される。通常はファイル毎に行う処理を実装する。省略不可。
 - flushFunction：出力ストリームへの書き込み（flush）前に一回だけ呼び出される。通常は最後にまとめて行う処理を実装する。省略可能。

![gulp plugin basics.png](https://qiita-image-store.s3.amazonaws.com/0/21655/fd411c87-9dd9-300e-5824-dfee4509c924.png "gulp plugin basics.png")


## transform関数
- 関数抜ける前に必ずcallbackを呼び出すこと（callback呼ばないとプラグイン呼び出し側に'end'イベントが発生しない）
- 出力ストリームへの書き出しはthrough2.push(file)を呼び出す（複数ファイル書き出ししてもいい）
- transformに渡されるfileはnull / buffer /streamの3種類ある
 - null：入力ストリームからデータを受け取らなかった場合
 - buffer：入力ストリームのデータを1ファイル分バッファリングしてからtransformに渡される場合
 - stream：入力ストリームのデータが順次transformに渡される場合

## flush関数
- 関数抜ける前に必ずcallbackを呼び出すこと（callback呼ばないとプラグイン呼び出し側に'end'イベントが発生しない）
- 出力ストリームへの書き出しはthrough2.push(file)を呼び出す（複数ファイル書き出ししてもいい）


## サンプルプラグイン
サンプルとして、入力ストリームから2つのJSONファイルを受け取り、diffをとってその結果を出力するプラグインを作ってみる。

- diffの処理自体はnode.jsパッケージの[JsonDiffPatch](https://github.com/benjamine/JsonDiffPatch)を利用（出力の仕様などはJsonDiffPatchに従う）
- プラグインに渡す引数は「出力ファイル名」のみ

```javascript
var gutil    = require('gulp-util');
var through  = require('through2');
var jsonDiff = require('jsondiffpatch');

module.exports = function (outputFileName) {

  var file1, file2; // diff対象のファイル

  /*
    transform関数：ファイル毎に呼び出される
  */
  function transform(file, encoding, callback) {

    // ファイルがnullの場合
    if (file.isNull()) {
      // 次のプラグインに処理を渡すためにthis.push(file)しておく
      this.push(file);
      // callback()は必ず実行
      return callback();
    }

    // ファイルがstreamの場合（このサンプルプラグインはstreamに対応しない）
    if (file.isStream()) {
      // emit('error')を使って、プラグイン呼び出し側に'error'イベントを発生させる
      this.emit('error', new gutil.PluginError('gulp-diff', 'Streaming not supported'));
      // callback()は必ず実行
      return callback();
    }

    // fileをローカル変数に保持しておく
    if (!file1) {
      file1 = file;
    }
    else if (!file2) {
      file2 = file;
    }
    else {
      // 入力ファイルが3つ以上指定された場合はエラーとする
      this.emit('error', new gutil.PluginError('gulp-diff', 'File too much'));
    }

    // callback()は必ず実行
    callback();
  }

  /*
    flush関数：flush前に呼ばれる
  */
  function flush(callback) {

    // ファイルが2つ指定されなかった場合
    if (!file1 || !file2) {
      // エラーとする
      this.emit('error', new gutil.PluginError('gulp-diff', 'File too less'));
    }

    // ファイルの内容（file.contents）をJSONオブジェクトに変換
    var json1 = JSON.parse(file1.contents.toString('utf8'));
    var json2 = JSON.parse(file2.contents.toString('utf8'));

    // diff実行
    var diff = jsonDiff.diff(json1, json2);

    // 出力ファイルを生成（新規ファイル生成にはgulp-utilのFileを利用する）
    var output = new gutil.File({
      cwd:  file1.cwd,
      base: file1.base,
      path: file1.base + outputFileName,
    });

    // ファイルのコンテンツにはnode.jsのBufferを使う
    output.contents = new Buffer(JSON.stringify(diff));

    // ファイルを出力ストリームに流す
    this.push(output);

    // callback()は必ず実行
    callback();
  }

  /*
    through2オブジェクトを生成してreturn
  */
  return through.obj(transform, flush);

};
```