Lazuli

らずり

Gruntを使ってjavascriptを自動バインドする

f:id:app2641:20130227191033p:plain

バインドがめんどくさい

いま開発・運用してるプロジェクトではjavascriptを多用していて、通常はコンポーネント毎にjsファイルが分かれているんだけどデプロイ時に全jsを結合して圧縮してーっていうバインド処理を行ってる。特に珍しい運用ではないと思う。で、バインドの処理はプロジェクト独自のコマンドツールが作ってあるからそれでGoogle Closure Compilerを動かして結合してく感じ。結合するファイルは順番があるからそれらは適当なyamlに書いて読み込ます。
こんな感じのコマンド。

$ app bind

こうやってコマンド叩けば出来るんだけど、人って欲深くてこれさえもめんどくさくなるんだよね。
そこで出ましたGrunt! そこそこ話題になってるなういツール。javascriptタスクランナーってやつ。

grunt: a task-based command line build tool for JavaScript projects

Grunt使うとjsファイルを常時監視して変更があったら、あらかじめ設定しておいたタスクを実行なんてことが出来る。これなら自動バインドできそうだよね。

Grunt導入

まず、Node.jeとnpmが要ります。

$ brew install node

次に、GruntとGrunt-cliというのを入れる。

$ npm install -g grunt
$ npm install -g grunt-cli

Gruntを動かすにはプロジェクトのルートにpackage.jsonとGruntfile.jsというのが必要。
package.jsonにはプロジェクトの基本情報とか必要モジュールなどを記載する。package.jsonを使ってあらかじめ決めた設定をコマンドひとつで構築、なんてことが可能。Grunt導入時にはもちろんそんなものはないので自分で記載してきます。なんかテンプレート集みたいのもあるようだけど俺は使ってない。
Gruntfile.jsは実行させるタスクを記載する。書き方があるので後述。

package.json

まず、package.jsonを作ろう。下記ような感じで書く。

{
  "name": "Project Name",
  "version": "0.1.0",
  "devDependencies": {},
  "hoge": "hoge"
}

nameにはプロジェクト名をversionにはバージョンを。devDependanciesにはこのプロジェクトで利用するタスクを実行するのに使うモジュール名を記述する。今回はgrunt-contrib-watchというモジュールを使うのでここに記載をするんだけど、モジュールインストール時に

$ npm install grunt-contrib-watch --save-dev

と、--save-devオプションを付けると自動的にpackage.jsonが更新される。自分で書く必要がない。いいね。
最後のhogeは自分で設定したカスタムオプション。ここに記述しておくとGruntfile.js側でhogeを呼び出すことが出来るようになる。なんらかのパラメータが必要な時なんかに書くといいかも。

Gruntfile.js

Gruntで動かすタスクを記述します。こんな感じに。

module.exports = function (grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        watch: {
            files: [
                'public_html/js/app/*.js',
                'public_html/js/app/**/*.js'
            ],
            tasks: ['bind']
        }
    });

    /* Node.js モジュールの読み込み */
    grunt.loadNpmTasks('grunt-contrib-watch');

    /* bind タスク */
    grunt.registerTask('bind', 'bind javascript files.', function () {

        var process = require('child_process'),
            done = this.async(),
            command = './app bind';

        var callback = function (error, stdout, stderr) {
            if (error) {
                console.log(stderr);
                done(false);
            } else {
                console.log('js compile!');
                done();
            }
        };

        /* app bindの実行 */
        process.exec(command, null, callback);
    });

    grunt.registerTask("default", "watch");
};

タスク処理は module.export に記述する。 grunt.initConfig でGruntの初期化を行う。pkgにでpackage.jsonを指定すると先程のhoge変数が使用出来るようになる。
watchではファイル監視の設定を行う。filesには監視対象のファイルを記述する。ワイルドカードが使えるけれどディレクトリを再帰的には読み込んでくれないため階層毎ファイルを指定する必要がある。ちょっとめんどい。
tasksには指定ファイルの変更があった時に実行するタスク名を指定する。配列で複数指定することも可能。今回はbindを指定している。
また、今回のタスクは先程インストールしたgrunt-contrib-watchモジュールを使用するためgrunt.loadNpmTasksでモジュール名を指定しなければならない。

そして、grunt.registerTask。tasksで指定したbindの処理を設定する。第一引数にタスク名を第二引数にタスクの詳細、第三引数に処理の内容を記述。今回はchild_processを読んで app bind というコマンドを実行させている。コマンド実行時にはcallbackで成否をごにょごにょしたり。growl用のモジュールとかってバインド結果を通知したりも出来るようだけどGrowlが有料で手付かず。
最後に grunt.registerTask("default", "watch"); という所。Tasksではなく、Task。さっきのとは用途が違う。これを書かなくても、

$ grunt watch

とやると監視がはじめってタスクが動くのだが、ここでdefaultを指定しておくと、

$ grunt

でwatchタスクが起動するようになる。

まとめ

これでgruntを起動しておくだけで、jsを監視して自動的にバインドできるようになった。便利。
LiveReloadと併用してjsに変更があったらブラウザを自動リロードなんてことも出来るみたい。これは今後試してみたい。
駆け足でGruntの簡単説明でした。