アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

Browserify を使ってみる 2 - Source Maps

この間の記事では Browserify による JavaScript の結合を試したが、この状態だとデバッグ対象が巨大な単一ファイルとなり扱いにくい。この問題の対策として今回は SourceMap の生成を試してみる。前回の gulpfile.js にはいくつかバグがあったので、ついでにそれらも修正しておく。

Source Maps

AltJS から JavaScript をコンパイルしたり Minify しても、デバッグ時には変換前の状態で扱いたい。そんな欲求に応えてくれる仕組みとして Source Maps がある。

変換前の情報を既定の書式で JavaScript ないしは別ファイルに記録しておくことで、対応しているツールが変換前後のスクリプトに関連づけてくれる。例えば Firefox や Chrome のインスペクターであれば変換後のスクリプトを実行しつつ、デバッガのブレークポイントは変換元のスクリプト上に設置できる。

Browserify と Souce Map

というわけで Browserify & gulp によるビルドでも Source Maps が出力されるようにしてみよう。

Browserify 単体で Source Maps

Browserify 単体でも Source Maps を出力できる。前回記事の gulpfile.js で対応させる場合、browserify 関数のオプションに debug:true を指定するだけでよい。

gulp.task( 'build', function() {
    var browserify = require( 'browserify' );
    var source     = require( 'vinyl-source-stream' );

    browserify( './src/js/main.js', { debug: true } )
        .bundle()
        .pipe( source( 'app.js' ) )
        .pipe( gulp.dest( './src/js' ));
} );

このタスクを実行すると結合された app.js ファイルの末尾に Source Maps が出力される。つまり単一ファイルで完結するのだがリリース時などに Source Maps を除外したくなった場合 browserify のオプション分岐が必要になり扱いにくい。

分岐するとしても結合ファイルの出力先も分けないと最後に実行したタスクにより同一ファイルで Source Maps の有無が決まるため混乱する。これらを踏まえると別ファイルに出力したほうがよさそう。

Source Maps を単体ファイルとして出力する

gulp 公式リポジトリに公開されている以下の資料を読むと、Source Maps を単体ファイルとして出力する方法が解説されている。

適当なプロジェクトを作成してサンプルを実行すれば結合ファイルと同じディレクトリに同名の .map ファイルが出力される。結合ファイルの末尾には Source Maps の代わりに .map ファイルの URL が指定されていた。

//# sourceMappingURL=1.0.0.test-browserify.min.js.map

サンプルでは Minify も同時におこなっている。

私の前回記事では結合と Minify を別タスクに分けていたのだが、これは結合ファイルでデバッグして Minify はリリース時のタスクで実施する運用を考えていたため。しかし Source Map を出力できるなら元ソースを使ったデバッグが可能なので Minify しても問題ない。

また結合と Minify したスクリプトと Source Maps は別ファイルになっているので、前者をそのままリリースしてもよいだろう。よって gulp-usemin を想定して HTML 側では以下のようにスクリプトを参照していたが、

<!-- build:js js/app.js -->
<script src="js/app.js"></script>
<!-- endbuild -->

開発とリリースで同じファイルとなるので gulp-usemin 用の指定は不要となる。

<script src="js/app.js"></script>

リリース時は単にこのファイルをコピーすればよい。なお、CSS は gulp-usemin を継続利用する。

前回記事の gulpfile.js バグ

以下の問題を見つけたので修正した。

gulp のタスクは基本的に非同期で実行される。そのためタスク間が依存しないように設計して効率的なビルドを狙うのだけど依存も考慮して同期実行もサポートしておいたほうがよい。同期に対応する場合

  • タスクの function にコールバック関数を受け取って呼び出す
  • function の戻り値に gulp ストリームを返す

などの方法がある。

issue #5 で問題となった clean タスクでは del パッケージを利用しているのだが、これの引数に function のコールバック関数を渡すことで対応できていると勘違いしていた。実際には自身でコールバック関数を呼ばねばならない。

build タスクでは browserify パッケージを実行しており、ここからストリームを得られるのでそれを return するようにした。issue #7 は connect パッケージの指定パスが原因。単純ミス。

gulpfile.js

Source Map 対応とバグ修正をおこなった gulpfile.js は以下のようになる。

var gulp = require( 'gulp' );

/**
 * リリース用イメージを削除します。
 *
 * @param {Function} cb コールバック関数。
 */
gulp.task( 'clean', function( cb ) {
    var del = require( 'del' );
    del( [ './dist' ] );
    cb();
} );

/**
 * プロジェクトをビルドして開発用イメージを scr フォルダに生成します。
 */
gulp.task( 'build', function() {
    var browserify = require( 'browserify' );
    var source     = require( 'vinyl-source-stream' );
    var buffer     = require( 'vinyl-buffer' );
    var sourcemaps = require( 'gulp-sourcemaps' );
    var uglify     = require( 'gulp-uglify' );

    return browserify( './src/js/main.js', { debug: true } )
        .bundle()
        .pipe( source( 'app.js' ) )
        .pipe( buffer() )
        .pipe( sourcemaps.init( { loadMaps: true } ) )
        .pipe( uglify() )
        .pipe( sourcemaps.write( './' ) )
        .pipe( gulp.dest( './src/js' ) );
} );

/**
 * ブロジェクトのリリース用イメージを dist フォルダに生成します。
 */
gulp.task( 'release', [ 'clean', 'build' ], function() {
    var minifyCSS = require( 'gulp-minify-css' );
    var usemin    = require( 'gulp-usemin' );

    gulp.src( './src/img/**'    ).pipe( gulp.dest( './dist/img'  ) );
    gulp.src( './src/js/app.js' ).pipe( gulp.dest( './dist/js'  ) );

    gulp.src( './src/*.html' )
        .pipe( usemin( {
            css: [ minifyCSS() ]
        } ) )
        .pipe( gulp.dest( './dist' ) );
} );

/**
 * 開発用リソースの変更を監視して、必要ならビルドを実行します。
 */
gulp.task( 'watch', [ 'build' ], function () {
    gulp.watch( [ './src/js/*.js', '!./src/js/app.js' ], [ 'build' ]);
} );

/**
 * プロジェクト フォルダをルートにして HTTP サーバーを起動します。
 */
gulp.task( 'server', [ 'watch' ], function () {
    var connect     = require( 'connect' );
    var serveStatic = require( 'serve-static' );

    var app = connect();
    app.use( serveStatic( __dirname ) );
    app.listen( 8080 );
} );

/**
 * gulp の既定タスクです。
 */
gulp.task( 'default', [ 'build' ] );

サンプル

前回のサンプルを変更して test-browserify-sourcemap タグを貼った。

clone して README.md の Installation に書かれた手順を実行するとビルドを試せる。