どうでもいい話ですが、けっこうはまったので。
最近のWebフロントエンド開発ではタスクランナー系の話題がわりと多く、僕もgruntからgulpに乗り換えて数ヶ月という感じなのですが、Browserifyを触っておいた方がいいのかなと思い、直近の趣味プロジェクトで採用してみました。
Browserifyってなに?
そもそもなのですが、Browserifyというのはnodeのrequire('hogehoe')っていう依存関係解決機能をブラウザにも持ち込もうという試みですね。
たとえばjQueryなんかでいうと、こんな感じになります。
$ = require('jquery');
$(document).ready(function(){
// 何かする
});
requireを実行したところにjQueryが読み込まれ、以降のダラー関数が有効になります。
このrequireなのですが、元々はnodeのインクルード機能なので、独特のお作法があります。Browserifyの公式ページにはこんな感じのサンプルが載っているのですが……
var unique = require('uniq');
var data = [1, 2, 2, 3, 4, 5, 5, 5, 6];
console.log(unique(data));
このuniqというモジュールがどう書かれているかはともかく、普通のWebで使うJSとちょっと違うのは、requireした返り値を利用するような形式になっているところですね。詳しくはNode.js : exports と module.exports の違い(解説編)とかを読んでください。
実際のところいま必要かと言われれば僕は必要ないのですが、requireとか書いてあるJSライブラリも増えてきていますし、自分がやるかどうかはともかく、オープンソースの資源を使うときに必要だろうなと思って採用した次第です。
Gulpでこれまでやっていた方法
さて、Gulpを使い始めたのはつい最近なのですが、こんな方法を取っていました。
// Load libraries
var gulp = require('gulp'),
$ = require('gulp-load-plugins')();
// JSHint and minify
gulp.task('js', function() {
return gulp.src(['js/src/**/*.js', '!js/src/**/*.min.js'])
.pipe($.jshint())
.pipe($.jshint.reporter('jshint-stylish'))
.pipe($.uglify())
.pipe($.sourcemaps.init())
.pipe($.concat('app.min.js'))
.pipe($.sourcemaps.write())
.pipe(gulp.dest('js'));
});
JSHintをかましてから圧縮してソースマップののち連結ですね。Uglifyは後の方がいいのかもしれませんが、まあこれでとりあえずオッケーだったわけです。
この方法は最終的にapp.min.jsさえあればよいという考え方です。
Browserifyで実現したかった方法
今回Browserifyを採用した趣味プロジェクトが「子供のためにブラウザゲームを作る」というものだったので、こんな感じの構成を目指しました。
js ├ src (元のソースを入れる) │ ├ lib (自作ライブラリを入れる) │ ├ dist (リリース用JSが入る) │ └ vendor(外部ライブラリを入れる)
外部ライブラリっていうのは、CreateJSとかBox2DWebのことですね。自作ライブラリは作るかどうかわかりませんが、なんらかの処理をまとめたもので、src/*.jsからrequireされることを想定します。
たとえば、src/app.jsというファイルを作成したら、それにBrowserifyかまして圧縮&JSHintをかけて、dist/app.jsを生成するという次第です。もしパックマンを作りたくなったらsrc/packman.jsを作成して、それがdist/packman.jsとなります。上で紹介した例では、最終的にapp.min.js一つにしかなりませんでしたが、幾つ追加しても大丈夫というパターンです。
はまりポイント1. gulp-browserifyはNG
さて、上で紹介したコードと同じようにやるとすると、npm install gulp-browserify --save-dev とした上で$.browserify()で終わりかなーと思うのですが、なんとgulp-browserifyはブラックリスト入りしています。
理由はよく知りませんが、Gulpのプラグインであるgulp-browserifyは更新されないので、Browserifyはnodeのやつを使えということですね。
では、普通にBrowserifyを使うことにしましょう。
npm install browserify --save-dev
はまりポイント2. browserify().bundl()がなにか異なる
で、サンプルを見てみると……
gulp = require 'gulp'
browserify = require 'browserify'
source = require 'vinyl-source-stream'
gulp.task 'script', ->
browserify
entries: ['./js/src/main.coffee']
extensions: ['.coffee'] # CoffeeScriptも使えるように
.bundle()
.pipe source 'main.js' # 出力ファイル名を指定
.pipe gulp.dest "./js/" # 出力ディレクトリを指定
という感じになっています。coffeescriptですが、基本は同じです。が、どうもbundle()というメソッドが返す値はgulp.src()が返す値と異なるようで、そのまま.pipe(gulp.dest('js/dist'))とできないようです。
そこでこのサンプルではvinyl-source-streamというモジュールでgulpが扱える型(バイナルなんとか)に変換しているようなのですが、このときに名前を指定しています。
僕がやりたいことはgulp.src('js/src/**/*.js')とまとめて取得して、元の名前を保ったままjs/distフォルダに書き出したかったんですね。なので、このやり方はダメ。
はまりポイント3. transformしてbrowserifyに渡さないとダメ
いろいろググりたおした結果、次のようなことがわかりました。
- 普通に
gulp.src()〜gulp.dest()でパイプする - bowserifyの処理は専用の関数を作って、バイナルなんとか→文字列→browserify→バイナルなんとかに変換する処理を隠蔽する
なお、gulp + browserify, the gulp-y way というエントリーが特に参考になりました。
で、できたのがこんな感じ。
var gulp = require('gulp'),
$ = require('gulp-load-plugins')(),
browserSync = require('browser-sync'),
browserify = require('browserify'),
transform = require('vinyl-transform');
// Browserify
gulp.task('js', function(){
// srcから受け取ったファイルをbrowserifyして
// 返す関数を定義
var browserified = transform(function(filename) {
var b = browserify(filename);
b.add(filename);
return b.bundle();
});
return gulp.src('./js/src/*.js')
.pipe(browserified) // ここで指定する
.pipe($.uglify())
.pipe($.sourcemaps.init({loadMaps: true}))
.pipe($.sourcemaps.write('./map'))
.pipe(gulp.dest('./js/dist/'));
});
sourcemapの位置がおかしいかもしれませんが、とりあえず目的は達成しました。
まとめ
こうした設定系のお仕事というのが日に日に複雑になっているように感じます。もう設定が仕事で、コード書くのはおまけなんじゃないかと思うこともよくありますね。
以前増田で見かけて感銘を受けた言葉に「はてぶ見てみろや。あいつら永遠にVimの設定やってるぞ」というものがありましたが、こうやって設定ばっかりしているうちに一行もコード書かずに死んでいくプログラマーもいるのかと思うと、自動化の闇は深いですな。終わり。
価格¥1,111
順位270,183位
著吉田 拳
発行技術評論社
発売日2014 年 10 月 23 日
Excelでさえ1日で即戦力になれるというのに、Browserifyときたら……
