やったことだけ書く備忘録

grunt-sprocketsタスクプラグイン作成とテストとTravis CIまでやってみる

やってみたかったシリーズです。そろそろNode周りの環境をちゃんとしたかったので。
ちょっと長めのエントリになります。

2013/08/28:grunt.loadTaskの挙動について追記しました(;´Д`)



gruntの自分タスクをちゃんと作ってみる


今までは、grunt-contrib-xxxみたいなのをloadNpmTasksして使ってるだけだったんですが、そろそろgrunt内部の動きとか、オリジナルのタスク(これから必要になりそう)の作り方なんかも知っておきたかったので作ってみました。できたものはこちら。




ysugimoto/grunt-sprockets




npmには登録してません…><



Railsのsprocketsをやるやつです


以前にmiddlemanを少し触ってまして、(エントリはこちら)そのときのasset pipelineがすごい便利でした。で、sprocketsはその根幹になる仕組み?なのかな?

下記のようなコード、見たことありますよね。




//= require hoge.js

//= require_tree somedir
 


これをgruntのタスクとして動かせるようにしただけの単純なやつです。



「え、そんなんgrunt-contrib-concatでいいやん」


はい、その方がラクなんですが、ちょっと用途が違ってまして。

メインのソースコードがあって、その途中で任意の外部スクリプトを注入したいケース

を想定しています。grunt-contrib-concatの場合は、単純にファイルを結合するだけのものなので、最終的には結合されたファイルをメインファイルとして扱うことが多いですよね。でもgrunt-sprocketsの場合は、すでにメインファイルはあって、その中にファイルをインクルードすることを目的にしています。例えば、匿名関数のクロージャの場合、grunt-contrib-concatだと、




// start.js
(function(global) {

// end.js
})(this);
 


みたいなファイルを用意する必要があります。jqueryだと、intro.js/outro.jsというファイルが用意されていますね。つまりは、これが面倒じゃないかなってことで。grunt-sprocketsなら、




(function(global) {

//= require a.js
//= require b.js

})(this);
 


と書くことでビルドができます。どちらがいいかは好みにもよるので一概には言えないと思いますが。慣れてる方でいいんじゃないですかね。



gruntの内部構造について


今回はここの知識が欲しかったので、ここがメインです。目標は、

・処理対象ファイル/ファイルの中身の取得
・ディレクトリ内のファイルリストの取得
・Gruntfile.jsで書いた設定の情報の取得
・コンソール出力系
・タスク追加方法

あたりです。なお、使ってるgruntのバージョンは0.4.1です。0.3系だと動かないかもしれません。

公式のリファレンスとか、翻訳されている方もいらっしゃるので紹介。




Getting started - Grunt: The JavaScript Task Runner


grunt.file | Grunt 日本語リファレンス | js STUDIO




処理対象ファイル/ファイルの中身の取得


grunt.file.xxxで操作できるみたいです。



// file.jsを読み込み
var data grunt.file.read('file.js');

// ファイル/ディレクトリが存在するかどうかを判定
if ( grunt.file.exists('xxx') ) {
  
// ファイル/ディレクトリが存在する場合の処理
}

// ファイルかどうかを判定
if ( grunt.file.isFile('xxx') ) {
  
// ファイルの場合の処理
}

// ディレクトリかどうかを判定
if ( grunt.file.isDir('xxx') ) {
  
// ディレクトリの場合の処理
}
 


基本、これでできるかと。注意点は、ファイルパスは、Gruntfile.jsのあるディレクトリからの相対パスであることです。grunt.file.setBase()で変更もできるようです。



ディレクトリ内のファイルリストの取得


grunt.file.expand()を使えばOK。



// srcディレクトリ以下のファイルリストを取得
var files grunt.file.expand({}, 'src/*');
 


ワイルドカードが使えるので、これで一覧が取得できます。第一引数はいくつかパラメータがあるので、リファレンス参照です。



Gruntfile.jsで書いた設定の情報の取得


grunt.config('タスク名');で取得できます。



// ----------------- Gruntfile.js
grunt.initConfig({
  
sprockets : {
    
files: ['sample/test.js'],
    
dest'out/assets.js'
  
}
});

// ---------------- task/sprockets.js 
// "sprockets"タスクの設定を取得
var config grunt.config('sprockets');

console.log(config.dest);  // out/assets.js
 


これは必ず使うでしょうね。



コンソール出力系


grunt.log.xxxで出力。これはリファレンスを見てもらうといいです。grunt.log.okxxxで成功系、grunt.log.errorxxxで失敗系のログが色付きで出ます。



タスク追加方法


これは勘違いしていました。
タスクファイルの中で、必ずgruntを引数に取る関数をエスクポートしないといけないと思っていたのですが、内部でregiterTaskをすれば何もエクスポートしなくてもOKなようです。



// 初めはこうしないといけないと思っていた
module.exports = function(grunt) {
  
// do task
};

// これでも良い
var grunt = require('grunt');

grunt.registerTask('task name''task description'taskRunner);
 





2013/08/28追記


勘違いを勘違いしていました>< grunt.loadTaskでロードしたタスクファイル内で、module.exportsの結果に関数オブジェクトを返却すると、その関数を実行するようです。なので、今回のケースだと、タスクが2重に走ってしまうみたいです。(gruntのソースコードを確認)

今回はタスクファイルの中でregisterTaskをするので、gruntでロードした場合はmodule.exportsせず、busterでロードした場合は関数を返却するようにする必要があります(テストを実行したいので)。なので、苦肉の策を…。




if ( ! /grunt$/.test(process.argv[1]) ) {
  
module.exports Sprockets;
}
 


gruntで起動している場合は関数エクスポートを行わないようにしました(;´Д`)もう少しいい方法があると思うので調査。






ただし、今回はメインコンストラクタであるSprocketsをエクスポートしています(関連のないものをエクスポートしてもOK)。これは後述のように、コンストラクタが戻らないとテストにしくいからです。

こんな感じで、独自ファイル操作系のタスクが作れました(∩´∀`)∩ワーイ
詳しくはソースを見てもらえれば。



テストもやるよ


次はタスクのテストですが、私はbuster.jsを使っているので、こちらのテストケースを書きます。
他のテスティングフレームーワークでもいいんですが、個人的にRSpecのようなテストの書き方にちょっと慣れなくて、普通にアサーションができるテストが好みです…。あ、ちなみにbuster.jsはアサーションするタイプでもRSpecのような書き方のどちらでもできるようで大変優秀です。(使ってる人増えると嬉しいな…)

テストとしては不完全かもですが、とりあえずインストールとテストを書いてみます。




# インストール
npm install buster --save-dev



// buster.js
var config module.exports;

config['sprockets'] = {
    
env"node",
    
tests: [ 'tests/sprockets.js' ]
};
 


続いて、テストケース。




// tests/sprockets.js

var buster    = require('buster');
var 
assert    buster.assertions.assert;
var 
refute    buster.assertions.refute;
var 
Sprockets = require('../tasks/sprockets');

buster.testCase('SprocketsTest', {
    
    
"resolveDepenencyRequire#Success" : function() {
        var 
result Sprockets.resolveDepenencyRequire('sample/test.js');
        
        
assert.isString(result);
    },
    
    
"resolveDepenencyRequire#Failed" : function() {
        var 
result Sprockets.resolveDepenencyRequire('sample/notfound.js');
        
        
refute.defined(result);
    },
    
    
"loadDirectoryFiles#Success" : function() {
        var 
result Sprockets.loadDirectoryFiles('sample/src');
        
        
assert.isArray(result);
    },
    
    
"loadDirectoryFiles#Failed" : function() {
        var 
result Sprockets.loadDirectoryFiles('sample/notfound');
        
        
assert.equals(result"");
    }
});
 


あとは、package.jsonにテスト設定を書きます(後述のTravis CIのため)。




// package.json

{
  
"name""grunt-sprockets",
   -- 
中略 --
  
"scripts": {
    
"test""./node_modules/.bin/buster-test"
  
},
  -- 
中略 --
  
"devDependencies": {
    
"grunt-cli""~0.1.9",
    
"grunt""~0.4.1",
    
"buster""~0.6.12"
  
}
}
 


"scripts":"test"のセクションにbuster-testへの相対パスを指定します。これでnpm testでbusterのテストケースが走ります。

shot1

最後に、.travis.ymlを書いて終了です(Travis CI初めて触りました)




// .travis.yml
languagenode_js
node_js
:
  - 
"0.10"
before_script:
  - 
npm install
script
:
  - 
"npm test"
 


Travis CI側の設定方法は以下のサイトを参考にさせて頂きました。




Travis CIを使ったGitHubプロジェクトの継続的インテグレーション | 1000ch.net




感想など


なかなか手を出してなかったんですが、やってみるとサクサクできたので面白いかなー。
社内の独自アプリのビルド用タスクも簡単に作れそうです( ╹◡╹)

あとは、コツコツHaxeを勉強しているので、Haxeで静的にコンパイルチェック+テストでより安全なビルドが作れるようになれば最高ですね。

現場からは以上です。

« 前の記事 次の記事 »

2件のコメント

Cicera さん

People noralmly pay me for this and you are giving it away!

Rumor さん

That's the pecrfet insight in a thread like this.

コメントを投稿する

 画像に表示されている文字を入力してください。