gulpはgruntに比べて記述がわかりやすいです。ですが、情報量はgruntの方が多い感じがしました。gulpは外部モジュールのオプションに何があるのか、どう使えばいいのかがわからなかったのですがgithubのリポジトリを見ればちゃんと書いてありました。今回は違いのわからなかった、browserfiyとwatchifyを実際に動かしながら確認していきます。これでgulpもただのタスクを管理すための外部モジュールであることがわかります。
browserifyをターミナルで使って、複数のjsファイルを一つにまとめる
nodojeでrequireという関数で外部ファイルをモジュールとして読み込むことができます。これをクライアントでも使えるようにするためにするのがbrowserifyです。実際にサンプルを見ていきましょう。次のmain.jsとsub.jsを用意します。
console.log('main');
require('./sub');
console.log('sub');
次はコマンドを打ちます。実際に新しいjsファイルにまとめられている(バンドル)ことを確認するために、元のmain.jsとsub.jsを削除してからbundle.jsを実行してみるといいかもしれません。この後にも元のファイルを使います。このように、browserifyを使えば、requireで依存関係を解決しながら複数のjsファイルを1つにまとめることができます。
$ npm install browserify
$ browserify main.js -o bundle.js
# $ rm main.js sub.js
$ node bundle.js
main
sub
main.jsをターゲットに指定したので、requireが実行されて1つにまとまった、つまりbundleされたものをbundle.jsというファイルに出力しています。sub.jsをターゲットにした場合は、requireは記述していないので、main.jsの処理は追加されません。
watchifyをスクリプトファイルから実行すればgulpが少しわかる
次はコマンドではなくスクリプトファイルからbrowserifyを実行してみます。nodejsで実行するために、次のファイルを用意してください。ファイル名は自由です。nodeで実行するのでcoffeeではなくjavascriptで記述します。
var browserify = require('browserify');
var b = browserify();
b.add('./main.js');
b.bundle().pipe(process.stdout);
これをnodeで実行すると下記のようにbundleしたものが保存はされずに、ターミナル(標準出力)に表示されます。gulpコマンドを使う訳ではないので、gulpfiles.jsに記述する必要もなく、gulp.task()も入りません。
$ node combine.js
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
console.log('main')
require('./sub')
},{"./sub":2}],2:[function(require,module,exports){
console.log('sub')
},{}]},{},[1]);
browserifyをgulpで実行できるようにする
次はgulpで同じbrowserifyを実行させてみます。新しくvinyl-source-streamというモジュールを追加しています。これはb.bundle().pipe()
でpipeにprocess.stdoutではなくgulp.destというgulpのメソッドに渡す場合にb.bundle().pipe(srouce('bundle.js'))
を実行してからでないとエラーになってしまいます。gulp用にstreamを変換させます。
gulp = require 'gulp'
source = require 'vinyl-source-stream'
browserify = require('browserify')
# コマンド:gulp js-bundleで実行できます。
gulp.task 'js-bundle', ->
b = browserify()
b.add('./main.js')
b.bundle()
#.pipe process.stdout
.pipe source 'bundle.js'
.pipe gulp.dest('./')
# gulpコマンドで引数がない場合は、js-bundleタスクを実行
gulp.task 'default', ['js-bundle']
あとは下記のコマンドを叩いてください。
$ npm install gulp
$ npm install vinyl-source-stream
$ coffee -c gulpfile.coffee
$ gulp
[02:23:04] Using gulpfile ~/Desktop/work/node/gulpfile.js
[02:23:04] Starting 'js-bundle'...
[02:23:04] Finished 'js-bundle' after 90 ms
[02:23:04] Starting 'default'...
[02:23:04] Finished 'default' after 21 μs
$ node bundle.js
main
sub
これでbrowserifyの使い方もわかり、gulpもただのnodejsを使ったスクリプトであることがわかりました。自分で用意した処理をタスクとして管理するためにgulpを使うわけです。
browserifyでbundleはできるのに、watchifyは何のために使うのか?
gulpにはwatchというモジュールを使って、特定のファイルの変更を検知して好きなタスクを実行させることができます。今回だとjsファイルを編集して保存したら、勝手にタスクjs-bundleが走りbundleされるわけです。 監視させるファイルですが、1ずつ登録させるのは面倒なのでjs/以下のファイルの全てなど大まかに指定すると思います。この場合、すべてのjsファイルがbundleされるため数が多いと時間がかかります。このbundleの時間を短縮させるためにWatchifyを使います。
watchifyでwatchとbrowserifyの機能が使えます。jsのファイルの監視とbundleが行えるわけです。だからと言ってbrowserifyがいらないわけではありません。watchifyはbrowserifyのラッパーです。browserfiyを引数(パラメータ)として受け取り、watchifyを生成するのです。ラッパーとは包むものです。watchifyがbrowserfiyを包んで、+αで好きな機能を追加してるわけです。
watchifyコマンドを使い、browserifyとの違いを見る
watchifyはbrowserifyのラッパーで、機能を+αしただけなのでコマンドのオプションは同じだと思ってもいいです。一つ違うのは今回の場合は、watchifyコマンドを実行後にプロンプト($ドルマーク)が表示されず入力を受け付けていません。稼働し続けているのでctrl + cで終了させる必要があります。
$ rm bundle.js
$ watchify main.js -o bundle.js
# ctrl + cで中止して戻る。
$ node bundle.js
main
sub
これだけだとctrl + cの操作が増えて、不便になっています。そこで今度はctrl + cで中止させずに、新しいターミナルを起動してください。macの標準ターミナルを使っているならctrl + tで新規タブで開きます。そして、元のjsファイルを修正します。
$ node bundle.js
main
sub
$ watchify main.js -o bundle.js
# 新規ターミナル開いて作業する
# sub.jsファイルの末尾にechoの出力を追記する。
$ echo "console.log('add');" >> sub.js
$ node bundle.js
main
sub
add
新しく修正をした後に、watchifyコマンドを叩いていないのにbundle結果が変わっています。これは自動でファイルの更新を検知して、bundleしてくれたということです。browserfiyだと、ファイルを修正する度に再度をコマンドを叩く必要が有りますが、watchifyだとその必要がなくなります。
gulpでwatchifyを使う
browserifyでもgulpでwatchに登録すれば自動でコマンドを実行させることができます。これだとwatchifyと同じですね。ただし、先ほど説明しましたがbundleの処理時間がwatchifyの方が早いです。全てをbundleするのではなく、更新された部分だけ差分修正をしてくれます。
gulp = require 'gulp'
source = require 'vinyl-source-stream'
browserify = require('browserify')
watchify = require('watchify')
gulp.task 'js-bundle', ->
b = browserify {cache: {}, packageCache: {}}
w = watchify b
w.add('./main.js')
w.bundle()
.pipe source 'bundle.js'
.pipe gulp.dest('./')
gulp.task 'default', ['js-bundle']
watchifyのインスタンスを生成するにはコンストラクタにbrowserifyのインスタンスを渡します。このbrowserifyを生成するときには、cacheとpackageCacheにオブジェクトを渡さないといけないといけません。今回は設定する必要がないのでからオブジェクトを渡しました。このことはgithubに書いてあるのですが、browserifyに何も渡さなくても問題は起きませんでした。使い込むとなるとエラーが出るのかな。
ファイルを用意したら次のコマンドを叩いて確認です。
$ coffee -c gulpfile.coffee
$ rm bundle.js
$ gulp
[03:38:36] Using gulpfile ~/Desktop/work/node/gulpfile.js
[03:38:36] Starting 'js-bundle'...
[03:38:36] Finished 'js-bundle' after 99 ms
[03:38:36] Starting 'default'...
[03:38:36] Finished 'default' after 18 μs
# 新しいターミナルを起動
$ node bundle.js
main
sub
add
$ node bundle.js
main
sub
add
あれ?コマンドのときと違いjsを編集してもファイルをbundleし直してくれませんね。監視できていないのでしょうか?実はイベントハンドラを設定する必要が有ります。
watchifyをgulpで監視させるようにする
watchifyにはupdate,bytes,time,logというイベントがあり、ファイルの更新の際にはupdateというイベントが発火します。なので、updateにイベントハンドラを設定します。updateの度にbundle()以降を何度も実行して欲しいので、`w.bundle()'以降を関数にします。変数wをイベントハンドラbundle()の外に出しているので、関数を実行後も変数wの中身は保持されます。これはクロージャという機能です。
最後に一度bundle()を実行していますが、これをしないとupdateが起きる前に一度もbundleしてくれません。gulpを実行した際にまず一度bundleするようにします。
gulp = require 'gulp'
source = require 'vinyl-source-stream'
browserify = require('browserify')
watchify = require('watchify')
gulp.task 'js-bundle', ->
b = browserify(cache: {}, packageCache: {})
w = watchify b
w.add('./main.js')
bundle = ->
return w.bundle()
.pipe source 'bundle.js'
.pipe gulp.dest('./')
w.on 'update', bundle
return bundle()
gulp.task 'default', ['js-bundle']
$ coffee -c gulpfile.coffee
$ rm bundle.js
$ gulp
[03:34:07] Using gulpfile ~/Desktop/work/node/gulpfile.js
[03:34:07] Starting 'js-bundle'...
[03:34:07] Finished 'js-bundle' after 103 ms
[03:34:07] Starting 'default'...
[03:34:07] Finished 'default' after 18 μs
# 新しいターミナルを起動
$ node bundle.js
main
sub
add
$ echo "console.log('add for watchify')" >> sub.js
$ node bundle.js
main
sub
add
add for watchify
他のサイトでwatchifyをgulp.taskではなく自作関数で使用していたのですが...
gitリポジトリにcoffeeファイルはあるけど、jsファイルがない。動作確認するためにコンパイルだけしたいとします。編集ではなくコンパイルだけをするのです。そういうときのために、browserifyとwatchifyをboolen変数で切り替えれるようにします。このことを踏まえると下記の記事で紹介されているgulpfileが何をしているかわかると思います。