Grunt for javascript libs

ナウでもヤングでもないけどGruntを使ったJS(ライブラリ|プラグイン)配布環境の構築メモ

scaffold的なテンプレを用意したほうが便利なんだけど、その前に基本的な構築とか

忙しい人はあとがきから読むことをおすすめする

やること

おおまかにはJSライブラリのテスト&ビルド用タスクの構築

  1. test
  2. jshint
  3. concat
  4. minify
  5. document生成(今回はやめました)

これらを拙作のgithub.com/dameleon/tt.jsで整えてみる

ファイル構造などが分からなければ、上記リポジトリをcloneして、git checkout developされたし(masterに反映していないので)

package.json

package.jsonってなんぞや

ビルド環境や依存関係、バージョンなどのパッケージ情報を管理するファイル

用意しておけば、どこでも差異なく開発環境が作れる(はず)

気になる方はCommon.jsとかnpmのアレとか見てください。

see

package.jsonを作る

良いサンプルが見つからなかったのでとりあえずjQueryからパクる影響を受けましょう。

// package.json from jQuery
// https://github.com/jquery/jquery/blob/master/package.json
{
    "name": "jquery",
    "title": "jQuery",
    "description": "JavaScript library for DOM operations",
    "version": "2.0.0pre",
    "homepage": "http://jquery.com",
    "author": {
        "name": "jQuery Foundation and other contributors",
        "url": "https://github.com/jquery/jquery/blob/master/AUTHORS.txt"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/jquery/jquery.git"
    },
    "bugs": {
        "url": "http://bugs.jquery.com"
    },
    "licenses": [
        {
            "type": "MIT",
            "url": "https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt"
        }
    ],
    "dependencies": {},
    "devDependencies": {
        "grunt-compare-size": "0.3.1",
        "grunt-git-authors": "1.0.0",
        "grunt-update-submodules": "0.2.0",
        "grunt-contrib-watch": "0.1.4",
        "grunt-contrib-jshint": "0.1.1rc6",
        "grunt-contrib-uglify": "0.1.1rc6",
        "grunt": "0.4.0rc5",
        "testswarm": "0.2.2"
    },
    "keywords": 
}


// package.json for tt.js
{
    "name": "tt.js",
    "title": "jQuery",
    "description": "JavaScript Library for fast DOM operations",
    "version": "0.2.0",
    "homepage": "",
    "author": {
        "name": "dameleon",
        "url": "https://twitter.com/damele0n"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/dameleon/tt.js"
    },
    "bugs": {
        "url": "https://github.com/dameleon/tt.js/issues"
    },
    "licenses": [
        {
            "type": "MIT",
        }
    ],
    "dependencies": {},
    "devDependencies": {},
    "keywords": 
}

インスコ

Grunt本体のインスコ

// 本体をグローバルインストール(直接でもいいです)
$ npm install -g grunt

// projectのディレクトリで作業
$ cd path_to_project

// node_modulesをignore
$ echo node_modules >> .gitignore

// --save-dev付けておけば、package.json の devDependencies へ依存したパッケージとして書き込まれる
$ npm link grunt --save-dev

Gruntプラグインのインストール

今回の構成はこんな感じ

  • linter : jshint > grunt-contrib-jshint
  • test : buster.js > grunt-buster
  • concat : grunt-contrib-concat
  • minify : uglify > grunt-contrib-uglify
  • document : YUIDoc > grunt-contrib-yuidoc

この辺りは好みによって変更のこと。

Gruntプラグイン検索は本家で。

// 全部入れる
$ npm install grunt-contrib-jshint grunt-contrib-uglify grunt-contrib-concat grunt-contrib-yuidoc grunt-buster --save-dev

// buster.jsを使う場合、phantomjsのインストールが必要
$ brew install phantomjs

ここまでやると、package.json に以下の依存関係が書き込まれてるはず。

// @package.json
"devDependencies": {
    "grunt": "~0.4.0",
    "grunt-contrib-jshint": "~0.2.0",
    "grunt-contrib-uglify": "~0.1.2",
    "grunt-contrib-concat": "~0.1.3",
    "grunt-contrib-yuidoc": "~0.4.0"
    "grunt-buster": "~0.1.2",
}

setup grunt.js

環境は整ったので、grunt.jsの動作を設定していく

基本的な書き方はthe-gruntfileらへん

プラグインを入れた際の設定は各プラグインのnpmページ(ex.grunt-contrib-concat)を参考に

// projectのrootで(じゃなくてもいいけど)、gruntfile.jsを作って編集
// coffee scriptでも書けるので、お好きな方でどうぞ
$ vim gruntfile.js

// gruntfile.js
module.exports = function(grunt) {

    grunt.initConfig({

        // package.jsonを読み込む
        // 以後の設定値で pkg.name のようにプロパティをテンプレート内で展開できる
        pkg: grunt.file.readJSON('package.json'),

        buster: {
            test: {
                // buster.jsのconfigファイルを指定
                config: 'test/buster.js',
            },
            server: {
                // port を指定(いらない?)
                port: 1111
            }
        },

        concat: {
            options: {
                // 既存の(既に書かれている)バナーを削除
                stripBanners: true,
                // 結合後のバナーのスタイルを指定
                banner: '/** <%= pkg.title %> version:<%= pkg.version %> author:<%= pkg.author.name %> at:<%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            dist: {
                // ソースファイルの指定。ワイルドカードを用いず、1つずつでもOK
                src: 'src/*.js',
                // 結合後のファイル名を指定
                dest: '<%= pkg.name %>.all.js'
            }
        },

        uglify: {
            options: {
                // minify後のバナーのスタイルを指定
                banner: '/** <%= pkg.title %> version:<%= pkg.version %> author:<%= pkg.author.name %> at:<%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                // ソースファイルを指定
                src: '<%= pkg.name %>.all.js',
                // minify後のファイル名を指定
                dest: '<%= pkg.name %>.min.js'
            }
        },

        jshint: {
            // ターゲットを指定
            all: 'src/*.js',
            // option を設定
            options: {
                curly: true,
                eqnull: true,
                eqeqeq: true,
                undef: true,
                validthis: true,
                globals: {
                    tt: true
                }
            }
        },
    });

    // pluginをロードする
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-buster');

    // taskを登録
    grunt.registerTask('test', ['jshint', 'buster']);
    grunt.registerTask('default', ['jshint', 'buster', 'concat', 'uglify']);
};

run Grunt

必要なものは揃ったので、試しに走らせてみるテスト

// testのタスクを実行
$ grunt test

Running "jshint:all" (jshint) task
>> 1 file lint free.

Running "buster" task
buster-server running on http://localhost:1111
PhantomJS 1.8.1, Mac OS X: ...................................................
2 test cases, 51 tests, 164 assertions, 0 failures, 0 errors, 0 timeouts
Finished in 0.055s

Done, without errors.

// 何も指定していなければ default のタスクが実行される
$ grunt

Running "jshint:all" (jshint) task
>> 1 file lint free.

Running "buster" task
buster-server running on http://localhost:1111
PhantomJS 1.8.1, Mac OS X: ...................................................
2 test cases, 51 tests, 164 assertions, 0 failures, 0 errors, 0 timeouts
Finished in 0.062s

Running "concat:dist" (concat) task
File "tt.all.js" created.

Running "uglify:build" (uglify) task
File "tt.min.js" created.
Uncompressed size: 27308 bytes.
Compressed size: 2293 bytes gzipped (10683 bytes minified).

Done, without errors.

出力としてエラーを受け取った場合は、その箇所を修正して再度実行

結果、ファイル出力として、今回の設定では2つのファイルが生成される

  • tt.all.js
  • tt.min.js

また、concatとuglifyともにバナーを設定したので、それも確認する(今回はどっちも一緒[それもどうかと思うが])

// @tt.all.js#1
/** tt.js version:0.2.0 author:kei takahashi(twitter@dameleon) at:2013-03-05 */

// @tt.min.js#1
/** tt.js version:0.2.0 author:kei takahashi(twitter@dameleon) at:2013-03-05 */

以上で、簡単なビルド環境の設定は終了

あとは複数人でも一人で複数環境でも、安心して開発を行える(はず)

あとがき

実はgrunt-initなる機能があって、既に用意されたテンプレみたいなのも展開できたりもする

手っ取り早くGruntを利用したければこちらのほうがいいかも(ある程度の編集は必要)

see: Project Scaffolding

$ npm install -g grunt-init

// 試しにcommonjsのテンプレをcloneして
$ git clone git@github.com:gruntjs/grunt-init-commonjs.git ~/.grunt-init/commonjs

// 走らせてみる
$ grunt-init commonjs

// 対話式でいろいろ聞かれる
Please answer the following:
[?] Project name (grunttest) hoge
[?] Description (The best project ever.) fuga
[?] Version (0.1.0) 0.1.0
[?] Project git repository (git://github.com/dameleon/grunttest.git)
[?] Project homepage (https://github.com/dameleon/grunttest)
[?] Project issues tracker (https://github.com/dameleon/grunttest/issues)
[?] Licenses (MIT)
[?] Author name (dameleon)
[?] Author email ()
[?] Author url (none)
[?] What versions of node does it run on? (>= 0.6.0)
[?] Main module/entry point (lib/hoge)
[?] Npm test command (grunt nodeunit)
[?] Do you need to make any changes to the above before continuing? (y/N)

// なんか色々作ってくれる
$ ls

.gitignore
.jshintrc
Gruntfile.js
LICENSE-MIT
README.md
lib
package.json
test

もちろん、テンプレートを自分で作ることもできる

see: Project Scaffolding#custom-templates

今度は、今回作ったGrunt周りのものをオレオレテンプレ化してみたりとか

その前にはてブロを綺麗にしないと見づらくてしょうがない…