gulp と browserify と vinyl の話

browserify の API が変わったので、この記事のコードのままでは動きません!

gulp で browserify しようとしたんだけど、
色々ハマったので、
ひとまずまとめておきたい。

登場人物

gulp ちゃん

フロントエンドのビルドエンジン。
grunt を馬鹿にしてる。

browserify ちゃん

CommonJS スタイルの require をフロントエンドでも使えるようにする npm モジュール。
調子に乗ってるけど webpack がきになる。

vinyl ちゃん

npm 全般で使うオブジェクトの名前。モナドを感じる。
俺がさっき知った。

gulp の仕組み

そもそも gulp って、確かに grunt より短く書けるけど、
具体的になにしてるのかよくわかんねーよ!
って思ったので、
まず、この短いコードから考えていきたい。

var gulp = require("gulp");
var jade = require("gulp-jade");

gulp.task("jade", function(){
  gulp.src("jade/*.jade")
      .pipe(jade())
      .pipe(gulp.dest("dest"));
});

gulp.task とかは task の名前を決めてるだけだから飛ばして、
まずはこれ

  gulp.src("jade/*.jade")

gulp.src は、 vinyl オブジェクト を作って返す関数である。
vinyl とは、いろんな環境で使える仮想ファイルオブジェクトである。
gulp では、この vinyl を使って、ファイルをあれこれ処理する。

次にこれ

      .pipe(jade())

pipe は、引数には streams.Transform という形式のオブジェクトが入る。
そして、このオブジェクトを使って、それぞれのファイルに変形が適用される。

多分色々ややこしいんだけど、 through2 というモジュールを使うと簡単に作れるっぽい。
実際に gulp-xxx 系のソースを見ると結構短い。

最後にこれ

      .pipe(gulp.dest("dest"));

これは書き出してるだけ。
vinyl は、ソースのファイル名の情報とかも持ってるから、
出力するファイル名には、それとかが適用される。

Browserify するには

browserify は、コマンドラインからだけでなく、
コードからも、api を使ってコンパイルが出来る。
bundle というメソッドを使う。

var browserify = require("browserify");  
var bs = browserify("app.js").bundle();

bundle が返すのは、ファイルストリームなので、
別のファイルに pipe できる。

var fs = require("fs");
var f = fs.createWriteStream('./output.js');
bs.pipe(f);

これで output.js にbrowserify されたコードが書き込まれる。

gulp で Browserify するには

browserify には、 gulp-browserify というモジュールがあるんだけど、
これは宗教上の理由からブラックリスト入りしてるらしいので、
ちゃんと生の browserify モジュールを使う。

そこで
「bundle が返したオブジェクトを普通に pipe すればいいじゃん!」
と思ってこんなことをしてしまうと、エラーが出る。

browserify("app.js")
    .bundle()
    .pipe(gulp.dest("js/"));

何故か?
答えは、
「bundle が返すオブジェクトは vinyl じゃないから」

われわれが browserify を gulp に通すには、
bundle の返したファイルストリームを vinyl に変換する必要がある。
ここで、 vinyl-source-stream というモジュールを使う。

var source = require("vinyl-source-stream");
browserify("app.js")
    .bundle()
    .pipe(source("dummyName.js")) // <- ここで vinyl になる!
    .pipe(gulp.dest("js/"));

これで万事解決である。

ここで一つ重要なのは、
source に渡す引数は、 "ソースのファイル名とするダミーの文字列" だということ
上で書いたように、 vinyl はソースのファイル名も持ってるから、
そいつを指定してやる必要がある。

他にも vinyl-transform のようなモジュールもある。
これを使うと、 vinyl のファイル名を引数にとって、
中身のコンテンツを変形できる。
browserify で複数のファイルをコンパイルするのに便利。

gulp.src("src/js/*.js", {base: "src/js"})
      .pipe(transform(function(filename){
        return browserify(filename).bundle();
      }))
      .pipe(gulp.dest("dest/js"));

まだ頭のなかで整理ついてないけど、
理解してしまえば、コードベースで書ける分、 grunt より gulp のほうが楽しそう