gulp-watchify を試す

2015年5月20日 1 開発 , , ,

これまで Browserify/watchify 関連の gulp タスクは greypants/gulp-starter を参考に自前で定義していたのだが、処理の長さゆえ複数ファイルに分割せざるを得ず見通しがよくないと感じていた。

そこで Browserify/watchify 周りの機能を gulp プラグイン化した gulp-watchify を試してみる。

基本的な使い方

gulp-watchify のサンプル コードは GitHub リポジトリ内の examples ディレクトリに公開されている。

Transform は setup 関数内で実行、Uglify は pipe 上で処理しているようだ。しかし Browserify オプションについては触れられていない。Source Map 出力などはどうすればよいのだろう?

高度?な使い方

Browserify を npm として直に利用する場合、関数の第二引数にオプションを指定することで、Transform や Source Map 出力などを設定できた。

また、watchify を実行している場合はコンソールに開始時刻しか出力されず、コンパイル時間がわからない。概算でよいから、この情報を出力できないものだろうか。

以上の要望をかなえるため、まずは gulp-watchify の仕組みを調べることにした。ソースを読んでみると index.js に以下のような処理があった。

function getBundle(file, opt) {
    // ...前略

    var bundle = browserify(opt)
    if (opt.watch !== false) {
        bundle = watchify(bundle, opt) // modifies bundle to emit update events
        cache[path] = bundle
        bundle.on('update', function() {
            bundle.updateStatus = 'updated'
            taskCallback(plugin)
        })
    }

    // ... 後略
}

gulp-watchify に指定したオプションはそのまま Browserify に渡されるようだ。それを拡張するように gulp-watchify 独自のプロパティを追加する設計となっている。つまり Browserify オプションのスーパーセットになるため、Browserify の指定をそのままおこなえばよさそう。

次にコンパイル時間だが、これを出力するオプションは無さそうなので、ストリームの end イベントをハンドリングして gulp-util の log 関数を実行しておく。

これらを踏まえてタスクを実装すると、以下のようになる。

var gulp = require( 'gulp' );
var $    = require( 'gulp-load-plugins' )();

// 共通タスク設定
var common = {
  src:        './src',
  dest:       './dist',
  isWatchify: false,
  isUglify:   false
};

// js タスクのファイル監視モード実行フラグを有効化
gulp.task( 'js-enable-watchfy', function() { common.isWatchify = true; } );

// js タスクの圧縮・最適化モード実行フラグを有効化
gulp.task( 'js-enable-uglify', function() { common.isUglify = true; } );

// JavaScript 間の依存解決とコンパイルを実行し、その結果を単一のファイルとして出力
gulp.task( 'js', $.watchify( function( watchify ) {
  var buffer    = require( 'vinyl-buffer' );
  var formatter = require( 'pretty-hrtime' );
  var time      = process.hrtime();

  return gulp.src( [ common.src + '/js/App.js' ] )
    .pipe( $.plumber() )
    .pipe( watchify( {
      watch: common.isWatchify,
      basedir:   './',
      debug:     true,
      transform: [ 'babelify' ]
    } ) )
    .pipe( buffer() )
    .pipe( $.sourcemaps.init( { loadMaps: true } ) )
    .pipe( $.if( common.isUglify, $.uglify() ) )
    .pipe( $.rename( 'bundle.js' ) )
    .pipe( $.sourcemaps.write( './' ) )
    .pipe( gulp.dest( common.src ) )
    .on( 'end', function() {
      var taskTime = formatter( process.hrtime( time ) );
      $.util.log( 'Bundled', $.util.colors.green( 'bundle.js' ), 'in', $.util.colors.magenta( taskTime ) );
    } );
} ) );

// ファイル監視
gulp.task( 'watch', [ 'js-enable-watchfy', 'js' ], function () {
  // js 以外の gulp.watch
} );

このタスクで利用している npm は以下。

npm 用途
gulp gulp 本体。
gulp-if gulp の pipe 内で条件分岐する。
gulp-load-plugins gulp プラグインの読み込みを一括で実行し、キャメル ケースで呼び出す。
gulp-rename gulp の pipe 内で対象をリネームする。
gulp-plumber gulp タスクを watch モードで実行している際、エラーが発生しても中断せずに watch を継続させる。
gulp-sourcemaps JavaScript と CSS の Source Map 出力用。
gulp-uglify JavaScript の圧縮と最適化。
gulp-util gulp 関連のユーティリティ。今回は JavaScript コンパイル時間の出力に使用。
gulp-watchify Browserify/watchify を gulp ストリーム化して実行する。
browserify gulp-watchify 参照用。
watchify gulp-watchify 参照用。
babelify ES6 から 5 に変換するための Browserify プラグイン。
pretty-hrtime JavaScript コンパイル時間を文字列化するために使用。
vinyl-buffer ストリーム変換用。Source Map 出力などで必要。

素の Browserify/watchify から gulp-watchify に移行しても、これらの npm は必要なので注意する。削除すると gulp-watchify を実行したときにエラーが起きる。

gulp-watchify のインストールで Browserify/watchify もセットでついてきた。gulp-watchify 内の node_modules にインストールされると思っていたが、そうしない理由は不明。

gulp タスクの処理分岐ハックについて

サンプルの js-enable-watchfy と js-enable-uglify は gulp-watchify のサンプルを参考にしたハックである。gulp タスクにはオプションがないため、グローバル変数にフラグを定義し、それを変更するタスクを実装して依存指定することで、タスクの処理分岐を実現している。

watch タスクではこれを利用し、js-enable-watchfy で watchify を有効化したあと js タスクが実行されることを期待している。

// ファイル監視
gulp.task( 'watch', [ 'js-enable-watchfy', 'js' ], function () {
  // js 以外の gulp.watch
} );

しかし gulp タスクは標準で並列実行されるため、この方法で大丈夫なのか心配になる。例えば以下のように定義すれば main は sub の後に実行される。

gulp.task( 'main', [ 'sub' ], function() {
  // 処理
} )

しかし以下の定義だと sub1 と sub2 は並列実行され、その後に main が実行される。

gulp.task( 'main', [ 'sub1', 'sub2' ], function() {
  // 処理
} )
```js

main はよいとして、sub1 と sub2 に依存関係がある場合は、個別に依存タスクを定義して順番保証することになる。

フラグを操作する程度であれば、次のタスクを起動する前に終了しそうだから問題ないのかもしれない。とはいえ、明示的に順番保証できていないのは気になる。

これを解決するには、関数化された gulp-watchify 処理を個別タスクで呼び分けるとよいだろう。gulp-starter のサンプルではそのようにしている。

```js
function compile( isWatchfy, isUglify ) {
  // gulp-watchify による処理
}

gulp.task( 'js',          function() { return compile(); } );
gulp.task( 'js-watchify', function() { return compile( true ); } );
gulp.task( 'js-release',  function() { return compile( true, true ); } );

タスク外に関数を定義するのがイマイチなので、本当に問題が起きた時だけ、この方法を採用する予定。


COMMENTS

  • 2015年10月22日 10:16 PM 返信

    > フラグを操作する程度であれば、次のタスクを起動する前に終了しそうだから問題ないのかもしれない。とはいえ、明示的に順番保証できていないのは気になる。

    JavaScriptはシングルスレッドかつ ‘js-enable-watchfy’ は同期的に `common.isWatching` を書き換えているので(複数の依存タスクを指定した時にgulpが左から順に実行していくことを保証しているなら)必ず正しく動くのではないかと思いました。

REPLY

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です