人気そうなGruntで、StylusとかCoffeeScriptをビルドする

ナウいらしいからgruntいれてみるよ

GruntはJSで作られている、Rakeみたいなビルドタスクの管理ツールといえばよいのでしょうか。LESSやCoffee Scriptのようなコンパイルを必要するファイルの処理や、minify・concatなどもタスクとして実行できます。

デフォルトで、ファイル更新を監視するwatchタスクもあるため、コンパイル操作の自動化などにも使っていけます。npmでもgrunt関係のタスクが多数公開されているので、簡単にタスクを追加できます。

まずはnpmでgrunt本体をインストール。

# ng
npm instlal grunt
npm WARN prefer global grunt@0.3.11 should be installed with -g

# ok
npm install -g grunt

あ、グローバルじゃないとダメだった。よく読もう → 自分。-gでインストールした。

package.jsonの作成

gruntで簡単JSビルド - rh7's blogを参考に、ちゃんとpackage.json作る所から始める。

% npm init
Package name: (grunt_test) GruntTest
Description: grunt install test and try use.
Package version: (0.0.0) 
Project homepage: (none) 
Project git repository: (none) 
Author name: ahomu
Author email: (none) 
Author url: (none) http://havelog.ayumusato.com
Main module/entry point: (none) 
Test command: (none) 
About to write to /Users/ayumusato/Desktop/grunt_test/package.json

{
  "author": "ahomu (http://havelog.ayumusato.com)",
  "name": "GruntTest",
  "description": "grunt install test and try use.",
  "version": "0.0.0",
  "dependencies": {},
  "devDependencies": {},
  "optionalDependencies": {},
  "engines": {
    "node": "*"
  }
}
Is this ok? (yes) 

grunt.jsを初期化

initタスクで、grunt.jsを対話的に初期化できます。1度オレオレgruntできたらコマンド使わずに普通に使い回すような気もしつつ。

% grunt init:gruntfile
Running "init:gruntfile" (init) task

Please answer the following:
[?] Is the DOM involved in ANY way? (Y/n) Y
[?] Will files be concatenated or minified? (Y/n) Y
[?] Will you have a package.json file? (Y/n) Y
[?] Do you need to make any changes to the above before continuing? (y/N) N

Writing grunt.js...OK

Initialized from template "gruntfile".

Done, without errors.

grunt.jsが初期化されました。"Is the DOM involved in ANY way?"の設問が、特に生成されるgrunt.jsに影響を及ぼしてなかった気がしつつ…。

タスクを入れてみる

Grunt: A JavaScript command line build tool | Public Draft | Just writing down what I've found and collectedを参考に、いくつかGrunt用のタスクをインストールします。

grunt-contribはかなり万能な感じ。上記の引用URLいわく、以下のタスクが入るらしい。CSSの圧縮・CoffeeScriptのコンパイル・Stylusのコンパイルをカバーしてくれたらとりあえず最低限はOK。あとはunderscoreのテンプレートをコンパイルってのが関数オブジェクトへの変換を指しているなら完璧?

  • clean / ファイル、フォルダを削除
  • coffee / CoffeeScriptをJavaScriptにコンパイル
  • handlebars / handlebarsのテンプレートをコンパイル
  • jade / jadeテンプレートをHTMLにコンパイル
  • jst / underscoreのテンプレートをコンパイル
  • less / lessファイルをCSSにコンパイル
  • mincss / CSSを圧縮
  • stylus / StylusファイルをCSSにコンパイル
  • zip / ファイル、フォルダをzip圧縮 Grunt: A JavaScript command line build tool
% npm install grunt-contrib
grunt-contrib@0.1.1 node_modules/grunt-contrib
├── zipstream@0.2.1
├── underscore@1.3.3
├── requirejs@2.0.4
├── less@1.3.0
├── stylus@0.27.2 (debug@0.7.0, cssom@0.2.4, mkdirp@0.3.3)
├── rimraf@2.0.2 (graceful-fs@1.1.8)
├── fstream@0.1.18 (inherits@1.0.0, graceful-fs@1.1.8, mkdirp@0.3.3)
├── tar@0.1.13 (inherits@1.0.0, block-stream@0.0.6)
├── jade@0.26.3 (commander@0.6.1, mkdirp@0.3.0)
├── handlebars@1.0.5beta (uglify-js@1.2.6, optimist@0.3.4)
├── clean-css@0.4.2 (optimist@0.3.4)
└── yuidocjs@0.3.15 (graceful-fs@1.1.8, node-markdown@0.1.0, minimatch@0.1.5, express@2.5.11, yui@3.6.0pr3)
% npm install grunt-exec
grunt-exec@0.1.1 node_modules/grunt-exec

task.registerTaskで独自定義のタスクも追加できるようだけど、なるべくベーシックなライブラリに由来するタスクは、さっさとこういうライブラリで入れてしまったほうがよさそう。

そいでgrunt.jsは今こんな感じ

最新ではなくて、このあと更に色々やっている最中なんですが、そのへんはまた次のエントリーとしてログを載せます。

/*global module:false*/
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: '<json:package.json>',
    meta: {
      banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
        '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
        '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
        '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' +
        ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
    },
    concat: {
      dist: {
        src: ['<banner:meta.banner>', '<file_strip_banner:dist/js/main.js>'],
        dest: 'dist/js/main.js'
      }
    },
    min: {
      dist: {
        src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
        dest: '<%= grunt.config("concat").dist.dest.replace(/js$/, "min.js") %>'
      }
    },
    coffee: {
      app: {
        src   : ['src/coffee/*.coffee'],
        dest  : 'dist/js/main.js'
      },
      test:{
        src : ['test/spec_coffee/*.coffee'],
        dest: 'test/spec/main.js'
      }
    },
    stylus: {
      app: {
        src : ['src/stylus/*.styl'],
        dest: 'dist/css/main.css'
      }
    },
    watch: {
      coffee: {
        files: ['<config:coffee.app.src>', '<config:coffee.test.src>'],
        tasks: 'coffee'
      },
      stylus: {
        files: ['<config:stylus.app.src>'],
        tasks: 'stylus'
      }
    }
  });

  // Load
  grunt.loadNpmTasks('grunt-contrib');

  // Default task.(から、lintとqunitを削除、あとでjasmine入れたい)
  // grunt.registerTask('default', 'lint qunit concat min');
  grunt.registerTask('default', 'stylus coffee concat min');
};

npmで入れたgrunt-xxxxxなタスクは、grunt.loadNpmTasksでロードできて、あとは普通にregsiterTaskでcoffeeとかstylusとかキーワードを指定でいます。(ここでのcoffee, stylusはgrant-contribに含まれるものを利用しています)

gruntを実行

実行はとりあえずgruntコマンドを実行するだけです。

# grunt default と同意
% grunt

これでdefaultタスク(上の例では、stylus, coffee, concat, minの順次実行)が実行されます。grunt task:argsのような感じで、特定のタスクのみ、そして引数として文字列を更に渡すことができるようです。

configのsrcとdest

coffeeタスクに対するconfigであれば、タスク名はcoffeeとして記述します。その中にサンプルのような構造でオブジェクトを指定することで、srcとdestのペアを複数指定できます。

srcがリソースファイル、coffeeならCoffeeScriptファイル(*.coffee)で、これは配列で複数指定できます。destは出力先のパスで、ここで指定したファイルにcoffeeのコンパイル結果が出力されます。

grunt.initConfig({
    "タスク名": {
        "グループ名(なんでもいい)": {
            src : ['リソースファイルのパス', 'path/to/**/*.extention'],
            dest : 'path/to/dist.extension'
        },
        "グループ名(なんでもいい)": {
            src : ['リソースファイルのパス', 'path/to/**/*.extention'],
            dest : 'path/to/dist.extension'
        }
    }
}

grunt-contribのcoffeeに、前述のような指定をしたら、srcに該当しているファイルが全部コンパイル&結合されて、destのパスに渡されました。できれば結合させずに同名のJSファイルとして吐いてほしい…。

参考

次回は、watch周辺と、独自タスク、grunt 0.4.0の利用あたりをログる予定。