gruntがやばいらしい

どうもこんばんは、south37です。
今日は前にちょこっと触れたgruntについて書いていきたいと思います。
最近いろんなブログで名前が出てきてる気がするので、自分の勉強がてら簡単にまとめてみたいと思います。

gruntって?

まずはそもそもgruntってなんやねんって話ですが、公式ホームページではThe JavaScript Task Runnerとして扱われています。じゃあtask runnerってなんやねんって話ですが、その名の通り色んなtaskを勝手に走らせる事が出来るようです。一番簡単な例としては、Coffee Scriptやscssみたいなメタ言語コンパイルをファイル編集と同時にやってくれる、みたいな使い方が考えられます。他にも、HTML, JS, CSSなどのminify・圧縮、jsファイルの結合、自動テストみたいな事も出来ます。というか、出来ない事無いんじゃないですかこれ。。。?gruntすごいですね。

使ってみる

では、実際に使い方を見てましょう。インストールは、node.jsが入っていれば一瞬っぽいです。

minami$ npm install -g grunt-cli

node.jsも一瞬で入るので、問題ないですね。

次に、Gruntfile.jsってファイルをいじります。ここにgruntで実行されるべきタスクをガシガシ書いていくみたいです。試しに、このページにのってたGruntfile.jsを書いてみました

module.exports = function (grunt) {

  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-contrib-requirejs');

  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),
    compass: {
      dist: {
        options: {
          sassDir: 'src/scss',
          cssDir:  'src/css'
        }
      }
    },
    requirejs: {
      compile: {
        options: {
          baseUrl: 'src/js/',
          name: 'app',
          mainConfigFile: 'src/js/app.js',
          out: 'dist/js/app.js'
        }
      }
    }
  });

  grunt.registerTask('default', ['compass', 'requirejs']);
};

まったく同じコードですがw、上から順に解説していきたいと思います。

まずは初っ端の

module.exports = function (grunt) {

って部分ですが、これはnode.jsでモジュールを定義する際に使われる一般的な書き方のようです。ざっくり言うと、node.jsでrequire関数を使ってファイルをincludeした時、require関数が返すオブジェクトがexports(またはmodule.exports)になります。上記Grantfile.jsの例で言えば、

var gruntfile = rquire('./Gruntfile.js');

ってされた時にgruntfileに入ってるのが

function (grunt) { ~ }

の部分になります。

jsがもともとmoduleや名前空間という概念を持たない為に、node.jsではrequireというメソッドを使って擬似的なmodule機構を作っています。requireはオブジェクトを一つだけ返し、そのオブジェクトを介してのみincludeしたファイル内の関数や変数にアクセス出来るようになっています。こうしてグローバル汚染を減らしてる訳です。

exportsとrequireの関係についてもう少し例を挙げてみます。例えば、

// /mod/person.js
var voice = "hello";

exports.say = function() {
    console.log(voice);
};

exports.age = 20;

みたいな内容のモジュールをrequireした時、require関数が返すのはexportsである為、

var person = require('./mod/person');

person.say();  //helloと出力
console.log(person.voice);   //undefinedと出力
console.log(person.age);  //20と出力

の様に、exportsオブジェクトに追加した属性は取得出来るものの、/mod/person.jsのグローバルにおいていただけのvoiceには直接触れなくなります。グローバル変数汚染も、personという一つの変数だけで済んでるのが分かるかと思います。

では、function(grunt)の中身を見ていきたいと思います。初っ端で

  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-contrib-requirejs');

みたいに書いてありますが、これはcompassやrequireJSというツールのgrunt用プラグインをロードしています。有名どころにはだいたいgrunt用プラグインが用意されているっぽいです*1

次の、

grunt.initConfig({~});

って部分で、gruntタスクの設定を行います。

pkg: grunt.file.readJSON('package.json')

pkgプロパティにはpackage.json*2の中身をセットします。これによって、gruntがプロジェクト全体の情報を簡単に取得出来るようにします。

compass:やrequirejs:ではcompass、requireJS、それぞれの設定を書きます。ここはちゃんと調べてないのでアレなんですが、distでディレクトリ構造とかの指定、optionsで細かな設定が出来るようになっているのだと思います。ツール毎に書き方が異なると思うので、調べてから使う必要があるかと思います。

最後に、

  grunt.registerTask('default', ['compass', 'requirejs']);

をする事でタスクの登録をしています。defaultとして登録しておくで、terminalでgruntと打つだけでここまでに指定したタスクが実行される事になります*3

ここまでで、ようやくgruntfile.jsが完成しました。後は、これが置いてあるところでgruntを実行する事によって、タスクがもりもり実行されていく訳です。

以上で、一通りの説明は終わりです。いかがでしたでしょうか?これだけだと利点が薄いように感じるかと思いますが、ファイルのwatch機能を使ったりする事で更に便利になっていくと重いまます。どんどん世の中を便利にしていきましょう。

今日はここまでです。眠くてヤバいので、全力で寝させて頂きます。よろしくお願いします。

追記

gruntコマンド実行までの手順が書かれていなかったですね....
grunt-cliをインストールした後、プロジェクト用のpackage.jsonを作成し、さらにnpmでgruntやそのプラグインをインストールします。

npm init

と打つといろいろ聞かれた後、package.jsonが作成されます。その後、

npm install --save-dev grunt grunt-contrib-compass grunt-contrib-requirejs

で、必要なファイルがnode_modules配下にダウンロードされます*4
後は、Gruntfile.jsを編集してgruntコマンドを実行すればOKです。

*1:以前紹介したHandlebars.jsやHogan.jsにもgrunt用プラグインは存在します。やはり、コンパイルする等の操作には、自動化が必須なのかもしれません

*2:プロジェクトの設定を書いておくファイル。npm initというコマンドをterminalで打つと生成される

*3:distみたいな適当な名前だと、grunt distと打った時に実行されるようです

*4:ついでに、--save-devオプションをつけてるのでpackage.jsonにダウンロードしたファイルの記録が残ります。