grunt-pluginの作り方と解剖


先日、[はじめてgrunt-pluginを公開](https://github.com/fnobi/grunt-koko)してみました。

そこでいくつか、「!?」となる箇所があったので、grunt-pluginをつくるときの注意まとめしてみます。
ある程度gruntを使っていて、Gruntfileもしこしこ書いたことある方向けです。

## grunt-pluginとは?

そもそもgrunt-pluginとは何なんでしょうか。
最低限必要なのは、 **gruntにtaskを追加するスクリプト** 、ってとこだと思います。

例えば、こんなものでも、grunt-pluginと言えば言えそう。

```javascript
var grunt = require('grunt');

grunt.registerTask('sample', 'sample task', function () {
    console.log('this is sample task!');
});
```

この```grunt.registerTask```というやつが、gruntにtaskを追加するメソッドです。
上の例の場合、sampleという名前の、"this is sample task!"と出力するだけのtaskができました。

これをGruntfileに混ぜ込んで書いておけば、ちゃんと↓のような感じで動くはず。

```bash
% grunt sample
this is sample task! 
```

## 公式なgrunt-plugin
しかしせっかくなら、自分で作ったtaskを公開して、
他の人に使ってもらったりもしたくなるわけです。

オレオレなgrunt-pluginを、
grunt公式のplugin置き場(http://gruntjs.com/plugins) で公開するためには、
以下3つの条件を満たす必要があります。

1. **npmモジュールとして公開する** (package.jsonがある)
2. **npmのpackage.jsonの中で、keywordsに``gruntplugin``を入れる**
3. **registerTaskを呼ぶスクリプトは、tasksディレクトリ以下に置く**

それでも案外シンプルですね!
また1と3を満たしていないと、```grunt.loadNpmTasks```で読み込むこともできません。

以下は、必要最低限なgrunt-plugin構成の例です。

```plain:ディレクトリ構成
grunt-sample
├── package.json
└── tasks
    └── sample.js
```

```json:package.json
{
  "name": "grunt-sample",
  "version": "0.0.1",
  "description": "sample grunt plugin",
  "keywords": [
    "gruntplugin"
  ],
  "devDependencies": {
    "grunt": "~0.4.1"
  }
}
```

```javascript:tasks/sample.js
module.exports = function (grunt) {
    grunt.registerTask('sample', 'sample task', function () {
        console.log('this is sample task!');
    });
};
```

あとは、このtasks以下のスクリプトの中で、
ごりごりやりたいことを書いていけば、pluginの完成です。

なお、npmモジュール公開までの手順は、このあとすぐ別記事にまとめるつもりです。


## configのとりかた
Gruntfileを使って、様々な開発ツールのconfigをまとめて書けるのが、
gruntのすばらしいところの一つです。


Gruntfileがこんな感じだったら、

```javascript:Gruntfile.js
var grant = require('grunt');

module.exports = function () {
    grunt.initConfig({
        sample: {
            hoge: 'hogehoge'
        }
    });  

    grunt.loadNpmTasks('grunt-sample');
};
```

このtask(simple)専用のconfigを参照したいときは、こんな感じ。

```javascript:tasks/simple.js
module.exports = function (grunt) {
    grunt.registerTask('sample', 'sample task', function () {
        // configから"hoge"を取得
        var hoge = grunt.config('sample').hoge;

        console.log(hoge); // => 'hogehoge'
    });
};
```

このようにgruntオブジェクトからは、いろいろな情報がとれ、
jsonファイルを読み込むなどのUtilまでついてるので、
[APIドキュメント](http://gruntjs.com/api/grunt)を読んでいろいろ調べてみるといいと思います。

## configを環境によって使い分ける

なお、grunt-compassやgrung-uglifyみたいに、
環境によってconfigを書き分けたい場合は、
```grunt.registerTask```ではなく**```grunt.registerMultiTask```**を使います。

こうすると、configのトップレベルのプロパティは、
その設定の環境名(**target**)とみなされます。

```javascript:Gruntfile.js
var grant = require('grunt');

module.exports = function () {
    grunt.initConfig({
        sample: {
            dev: { // 開発環境
                hoge: 'hogehoge'
            },
            prod: { // 本番環境
                hoge: 'moge'
            }
        }
    });  

    grunt.loadNpmTask('grunt-sample');
};
```


```javascript:tasks/simple.js
module.exports = function (grunt) {
    grunt.registerMultiTask('sample', 'sample task', function () {
        // 環境名の取得
        var target = this.target;

        // configから"hoge"を取得
        var hoge = grunt.config('sample')[target].hoge;
        console.log(hoge);
    });
};
```

この場合、gruntの実行結果は以下。

```bash
% grunt simple:dev
hogehoge
% grunt simple:prod
moge
% grunt simple
hogehoge
moge
```

環境名を付けずに実行すると、
すべての環境用の処理が一気に実行されてしまうので注意しましょう。

## 非同期taskを書くときは

ファイルを扱ったりして、非同期で進むtaskの場合、
そのまま書くと、taskの処理が終了する前にgrunt自体が終わってしまいます。

そういう場合には、```async```を使って、callbackの形にしましょう。

registerTaskに渡す関数の引数とかに、callback関数を入れても無駄なので、これまた注意です。

```javascript
var fs = require('fs');

module.exports = function (grunt) {
    grunt.registerTask('sample', 'sample task', function () {
        // 環境名の取得
        var target = this.target;

        // 非同期タスクにする(doneにcallback関数が入る)
        var done = this.async();

        // configから"filename"を取得
        var filename = grunt.config('sample')[target].filename;

        fs.readFile(filename, 'utf8', function (err, data) {
            // ファイルをreadするのを待って、出力したら終了
            console.log(data);
            done();
        });
    });
};
```

## まとめ
- grunt-pluginのタスクは、tasks以下で書こう
- registerTaskとregisterMultiTaskの違いは大事

- 非同期処理の作り方がすこし特殊

- 意味がわからなくなったら、コードを直接読むのがはやい
- でもおおむね簡単。設定ファイル付きの開発ツールをいいかんじに公開できてすばらしい。


## 参考
- [grunt - Grunt: The JavaScript Task Runner (公式APIリファレンス)](http://gruntjs.com/api/grunt)
- [Grunt0.4.xでのGruntプラグインの作り方 - Kokudoriing](http://kokudori.hatenablog.com/entry/2013/04/03/010236)
- [GruntJSでシェルのコマンドを（非同期に）実行する方法。 | Ginpen.com](http://ginpen.com/2012/12/13/grunt-exec/)