lxyuma BLOG

開発関係のメモ

gulp vs Grunt

概要

gulpの入門記事を以下に書いて行きます。

  • 目次
    • gulpとは
    • gulp sample
    • gulp支える技術(Streamと並行性)
    • grunt vs gulp どっち使う?

gulp.jsとは

  • いわゆるフロントエンド周りの
    • task runner
    • build tool

Gruntあるでしょ

  • その通り
    • いわば、Gruntの簡易版
  • 去年位?出来た後発のtool
  • 最近、blog記事も増えてきた

公式Page売り文句

  • 複雑なtaskも管理し易く、simpleで、使うのが簡単
  • 一時fileのdisk書き込みが無いので早くて能率的
  • simpleで期待通り動くようにPluginのGuidelineがある
  • APIが小さく学習時間かからない

リソース

使い方の流れ

  • Gruntと一緒
  • pluginを追加
    • npm install gulp-xxx --save-dev
  • gulpfileの更新
    • gulp.task('task-name', function(){ /* settings */ });

gulpfileとは

  • 設定file(Gruntfileと一緒)
// 公式pageより
// requireは省略。
gulp.task('scripts', function() {
  return gulp.src(paths.scripts)
    .pipe(coffee())
    .pipe(uglify())
    .pipe(concat('all.min.js'))
    .pipe(gulp.dest('build/js')); });

gulp.task('watch', function() {
  gulp.watch(paths.scripts, ['scripts']); });

gulp.task('default', ['scripts', 'watch']);

install

npm install -g gulp
$ gulp -v
[gulp] CLI version 3.6.2

やってみる

  • 今から、jsファイルをミニファイする簡単なgulp project作ってみる

project作成

  • まずは、地盤固めから
mkdir my_first_gulp
cd my_first_gulp
npm init
npm install --save-dev gulp

plugin追加

  • minifyするplugin
npm install gulp-uglify --save-dev

設定ファイル

  • gulpfile.jsを作成
var gulp = require('gulp');
var uglify = require('gulp-uglify');

gulp.task("uglify", function() {
    return gulp.src('js/*js')
        .pipe(uglify())
        .pipe(gulp.dest("build/js"));
});

gulp.task("watch", function() {
    return gulp.watch('js/*js', ['uglify']);
});

gulp.task('default', ['uglify', 'watch']);

実験

  • 試しにBackbone突っ込む
mkdir js
cd js
curl -O http://backbonejs.org/backbone.js
cd ..

run

  • 実行
    • build/js/backbone.jsがminifyした?
gulp

振り返ってみる

  • 今作ったsampleを一つずつ振り返ってみる
  • 関連する技術をピックアップしながら進める
    • streamや並列性の話

library呼び出し

var gulp = require('gulp');
var uglify = require('gulp-uglify');
  • gulp本体とplugin呼び出してる

task

gulp.task("uglify", function() {
    return // ...any action
});
  • これで、taskを定義している
  • この後、以下を黒い画面で実行できる
    • gulp uglify

taskの中身

    return gulp.src('js/*js')
        .pipe(uglify())
        .pipe(gulp.dest("build/js"));
  • src => pipe => destという流れ
    • gulp = node.jsのstreamを使ってる(後述)
  • pipeの中で、uglify()等、先ほど呼び出した関数を実行

streamとは

例示

  • ※stream handbookより
  • 今から以下の2つを比較して、何故stream要るか見てみる
    • text読んで渡す処理を
    • 通常とstream使用で比較してみる

通常で書くと

var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
    fs.readFile(__dirname + '/data.txt', function (err, data) {
        res.end(data); }); });
server.listen(8000);
  • 問題点
    • 扱いづらい
    • memoryにfile内容をbufferしてる
      • もし、data.txt馬鹿でかいと、多くのUserがメモリを食い散らかす

Stream使うと

var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
    var stream = fs.createReadStream(__dirname + '/data.txt');
    stream.pipe(res);
});
server.listen(8000);
  • happy
    • 奇麗
    • diskからdata受け取るときに、data破片毎にclientに書き込める
      • さっきの、でかいバッファ分の利用がなくなった
  • このstreamの運搬してるのがpipe
  • ここまでstream handbookでしたー

gulpのpipe

    return gulp.src('js/*js')
        .pipe(uglify())
        .pipe(gulp.dest("build/js"));
  • gulp.src : file のstreamを作ってる
  • pipe : streamを流す
  • dest : streamを保存

gulp.srcについて

  • 第2引数がoptions objでbuffer有無を設定出来る
    • falseにすると、streamとしてfile.contentsを渡せるが
    • 実はデフォルトはtrue
      • File bufferも一応残してる
      • pluginがstream対応していない事があるので、そうしてるらしい

sample

gulp.task('default', ['uglify', 'watch']);
  • default taskを指定
    • これで、黒い画面でgulpだけで打つと、uglifyとwatchタスクを実行する
    • ※ちなみに、以前あったgulp.runは今使えないっぽいので注意

並列化について

gulp.task('default', ['uglify', 'watch']);
  • 基本的に、自動的に並列化(並行化※後述)される
    • 例だと、uglifyとwatchは順序は保証されない

並列化の仕組み

  • js = single thread
    • あれ?並列化?
    • どうやってるんだろう?

言葉の整理から

  • 1task以上を同時に実行する言葉は2つある
    • parrallel(並列)
    • concurrent(並行)
  • 違いは、後者の並行が、
    • 本当に並列である場合もあれば、
    • 場合によっては時間毎に切り替える等の仮想的な並列である場合もある
  • 参考

gulpは?

  • gulpは、concurrency(並行)
  • orchestratorを使ってるらしい
    • orchestratorは、出来る範囲の並行性の中でタスクや依存性の順序並べや実行をしていくmodule

Orchestratorのconcurrency

  • ソースを追うと、どうやら、
    • 単に全task実行して
    • それぞれ終了時のcallbackを指定していってるだけっぽい
    • IO部分は確かに並列だが、それ以外はいつも通り
    • (実際は、後述の直列の要素もあるのでもう少し複雑だが)
  • "In node, everything runs in parallel, except your code" っていうヤツでしょうか。

gulpのTask実行順を指定

  • gulpの話に戻ると
  • taskを並列(並行)でなく直列にするには、以下、2つの条件が必要
    • 1)依存してるtaskは非同期
    • 2)taskの依存関係を指定

1)依存してるtaskは非同期

  • 以下、いずれかの方法
    • streamを返す事(src->pipe->destまでは非同期で直列)
    • callbackを呼び出す事(mochaのdoneと同じ)
    • promiseを返す事

2)taskの依存関係を指定

  • 以下のdepsに、依存してるタスク名を書く
  • taskの関数
    • gulp.task(name[, deps], fn)
    • name : taskの名前
    • deps:依存関係のあるタスク
    • fn:その後実行する関数
gulp.task('default', ['uglify', 'watch'], function(){
    // uglifyとwatch終わった後に実行される
});

複雑な順番

gulp.task('a');
gulp.task('b', ['a']);
gulp.task('c', ['a']);
gulp.task('d', ['b', 'c']);

以上

gulp vs Grunt

  • これも、どっちが生き残るのかな?

pluginの数は?

  • Gruntが勿論、多い
    • 2899個
  • gulpは比較すると少ないが、数は増えてる
    • 706個
    • 普通に使う分には困らなさそう
  • 数値はいずれも、公式ページから(2014/5/25現在)

Gruntのこれから

  • 視点を変えてGruntの方を見てみる
  • ver0.5で、並列実行が付く?(Roldmap
    • grunt.registerTask('name', ['jshint', 'concat'], { parallel:true });
  • ver1.0で、先程出てきたOrchestrator対応する?(grunt 1.0 alfa

Gruntさん

  • 大分gulpを意識してる?
  • 並列性(並行性?)が入ってきそう
  • 書き方も変わりそう

Gruntでgulp like

  • gulp本家がsrcからdestまでのstream使う仕組みを外出し
  • これでGruntでもgulp likeに書ける

まとめ

  • 今日からビルドツール/タスクランナー使い始めるなら
    • gulpいいよ。楽だし早いし。
  • もうGrunt使ってたり、環境整理の機会がもっと先だったら
    • Gruntの未来に注目。gulpっぽくなるかも。
  • 最終的にどっちが残るの?
    • 分かりません。