jit-grunt で grunt をキビキビ動作の高速化

jit-grunt

この記事を見て知ったのだが、jit-grunt というプラグインが中々よい。Just In Time ということで、タスクのロードを loadNpmTasks でまとめて最初にやる代わりに、各タスクの実行時までロードを遅延させるというもの。

npm の jit-grunt ページを見ると相当数がダウンロードされて利用者がいるようだが、検索するとあまり紹介されてないようだったので書いてみる。

効能

17個のタスクをロードしていた手元の環境に導入したところ、単純なタスクを実行した限りでは次のような実行時間の短縮が見られた。計測は例によって time-grunt だ。

Before

loading tasks で 625ms かかっていて、全体では 651ms を要した。

% grunt concat:lib                                       (git)-[develop]
Running "concat:lib" (concat) task
File "temp/lib.js" created.

Done, without errors.


Execution Time
loading tasks  625ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 96%
concat:lib      25ms  ▇▇ 4%
Total 651ms

After

loading tasks で無駄にロードしなくなった結果、全体では 112ms に短縮された。

% grunt concat:lib                                       (git)-[develop]
Loading "grunt-contrib-concat" plugin

Running "concat:lib" (concat) task
File "temp/lib.js" created.

Done, without errors.


Execution Time
loading tasks               91ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 81%
loading grun...trib-concat   3ms  ▇ 3%
concat:lib                  18ms  ▇▇▇▇▇ 16%
Total 112ms

条件と使用例

元々、次のような loadNpmTasks 列を成していた。

grunt.loadNpmTasks('grunt-styl');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-contrib-csslint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-autoprefixer');
grunt.loadNpmTasks('grunt-csso');
grunt.loadNpmTasks('grunt-spritesmith');

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks("grunt-jscs-checker");
grunt.loadNpmTasks('grunt-angular-templates');
grunt.loadNpmTasks('grunt-ng-annotate');
grunt.loadNpmTasks("grunt-contrib-concat");
grunt.loadNpmTasks("grunt-contrib-uglify");

grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks("grunt-nodemon");
grunt.loadNpmTasks("grunt-concurrent");
grunt.loadNpmTasks("grunt-bump");

これに jit-grunt を次のように導入する。命名規則ベースでオートロードするので loadNpmTasks は不要になる。

require('jit-grunt')(grunt, {
  sprite      : 'grunt-spritesmith',
  ngtemplates : 'grunt-angular-templates',
  ngAnnotate  : 'grunt-ng-annotate',
  jscs        : 'grunt-jscs-checker'
});

タスク名からパッケージ名を探索するときは次のようなルールに基づいている。このルールに当てはまらないパターンは、例に示したようなマッピングをしてやればいい。

  1. node_modules/grunt-contrib-task-name
  2. node_modules/grunt-task-name
  3. node_modules/task-name shootaroo/jit-grunt

賢明な諸氏ならお気づきだろうが

さきほどの 651ms → 112ms の短縮例はちょっとインチキだ。

使用するタスクが多くなれば、結局は多くのタスクをロードするので相応の時間がかかる。リリースビルドのような複合タスクを実行すれば、ほぼ全タスクが読まれるので、例に挙げたような性能改善は見られない。

とはいえ Grunt の初期化時間が短縮することで次のような効果は期待できる。

  • Grunt 実行時の立ち上がりが速くなるので、体感速度が改善する
  • nodemon + watch のような開発環境タスクの立ち上がりは単純に早くなる
  • loadNpmTasks を書く必要がなくなる(副次効果)

導入コストはかなり低いので、これらの利点に益を見いだせるなら試す価値はある。

Grunt 好きですよ

Grunt の性能改善系でいうと grunt-concurrent によるタスク並列化や、タスク自体を水平分割する grunt-parallelize もある。1〜2年前に発生した Grunt なプロジェクトも今や順調に肥大化している頃だろう。こういうライブラリを使って改善してみるには良い時期かもしれない。

ところで最近はビルド系の仕事は gulp、開発環境系は npm run でやるとコンパクトなのではと思っている。gulp ってほら、タスクランナーっていうかビルドシステムだから、役割を限定する上で筋を通しやすい感。(Gruntどこ行った)