gulp を用いたフロントエンドのタスク自動化レシピ (1)

browserify でのトランスパイルや uglify といったフロントエンド用のタスクを gulp で自動化するレシピを Github に上げ始めました.

github.com

browserify でトランスパイルを行い,1つのファイルにまとめるタスクを書いたので,簡単な解説を残します.

browserify-single-bundle

このレシピでは,ES2015 で書かれた JS ファイルをトランスパイルし,1つにまとめるタスクを gulp で書いています. また,ファイル監視による自動トランスパイル,ソースマップ付きファイル圧縮を行うかをオプションで指定できるようになっています.

本レシピで使用している主なモジュールは以下の通りです.

  • browserify
    • babelify
    • watchify
    • licensify
  • gulp-sourcemaps
  • gulp-uglify
  • gulp-util

追記(7/24)

licensifyを追加しました.

bundleJS(isWatch, isUglify)の解説

このレシピの要はbundleJS(isWatch, isUglify)で,引数をtrue, falseで変えることでファイル監視,ファイル圧縮を切り替えます.

isWatchtrueの場合,browserify に watchify がプラグインとして指定され,ファイル監視が有効になります.
isUglifytrueの場合,bundle()で gulp-sourcemaps, gulp-uglify でのインラインソースマップの生成とファイル圧縮が有効になります.

browserify は,debug: trueを指定するとインラインソースマップを生成します. gulp-sourcemaps は,browserify が生成したインラインソースマップを読み込み,ファイル圧縮後のソースマップと対応づけてくれます. 通常,ファイル圧縮後のdest/js/common.jsのソースマップは browserify でトランスパイル後のファイルと対応づけがされてしまい,トランスパイル前のファイルへとジャンプできませんが,gulp-sourcemaps を利用することで,ファイル圧縮後のdest/js/common.jsからsrc/js/app.jsへジャンプ可能となります.

bundle()pipe()処理中に,isUglifyで処理を切り替えています. isUglify === falseの場合,pipe($.sourcemaps.init({loadMaps: true})), pipe($.uglify())の代わりにpipe($.util.noop())が実行されます. $.util.noop()は gulp-util のnoop()であり,何もしない Stream を生成します.
noop()を用いると,このように特定の条件のときのみpipe()で処理を行うといった記述が可能になるため,状況によっては便利だと思います.

const bundleJS = (isWatch, isUglify) => {
  const src = 'src/js/app.js';
  const bundler = browserify(src, {
    debug: true,
    cache: {},
    packageCache: {},
  });

  bundler.transform(babelify);
  bundler.plugin(licensify);

  const bundle = () => {
    return bundler.bundle()
      .on('error', (err) => {
        $.util.log('browserify error', err);
      })
      .pipe(source('common.js'))
      .pipe(buffer())
      .pipe(isUglify ? $.sourcemaps.init({loadMaps: true}) : $.util.noop())
      .pipe(isUglify ? $.uglify({ preserveComments: 'license' }) : $.util.noop())
      .pipe(isUglify ? $.sourcemaps.write() : $.util.noop())
      .pipe(gulp.dest('dest/js'));
  };

  if (isWatch) {
    bundler.plugin(watchify);
    bundler.on('update', bundle);
  }

  bundler.on('log', $.util.log);

  return bundle();
};

gulp, browserify, uglify の話 ( vinyl-source-stream と vinyl-buffer について )

gulp, browserify, uglify でトランスパイルをする場合、以下のようなタスクを書くことになります。

import browserify from 'browserify';
import gulp from 'gulp';
import source from 'vinyl-source-stream';
import buffer from 'vinyl-buffer';
import uglify from 'gulp-uglify';

gulp.task('js', () => {
  const b = browserify({
    entries: './entry.js',
  });

  return b.bundle()
    .pipe(source('app.js'))
    .pipe(buffer())
    .pipe(uglify())
    .pipe(gulp.dest('./dist/js/'));
});

ここで用いられている vinyl-source-stream と vinyl-buffer について少し調べたことをメモとして残します。

vinyl-source-stream と vinyl-buffer

GitHub - substack/node-browserify: browser-side require() the node.js way によると、browserify().bundle() はreadable streamを返すと書いてあります。

gulp は、vinyl というオブジェクトを用いるため、vinyl-source-stream で変換する必要があります。 この辺りのことは以下のページに解説があります。

umai-bow.hateblo.jp

私が気になったのは、gulp-uglify の直前に vinyl-buffer で更に変換をかけている部分です。 gulp-uglify は、gulp のプラグインであるため、vinyl-source-stream で vinyl に変換した後、直接渡してあげることが出来ないのは何故なのか疑問に思っていました。 以下のタスクのように、gulp.src() から直接 gulp-uglify に渡すことが出来るため、vinyl-buffer は無くても良いのではないかと。

import gulp from 'gulp';
import uglify from 'gulp-uglify';

gulp.task('compress', () => {
  return gulp.src('lib/*.js')
    .pipe(uglify())
    .pipe(gulp.dest('dist'));
});

vinyl-buffer が必要な理由を調べていると以下の issue を見つけました。 gulp-uglify の内部で用いている UglifyJS2 が streaming をサポートしていないと書いてあります。

github.com

vinyl/README.md at master · gulpjs/vinyl · GitHub によると、pipe() 時には file.contents が Buffer と Stream (と null) の場合で処理が変わるようです。

gulp/API.md at master · gulpjs/gulp · GitHub によると、gulp.src() は options をとり、options.bufferfalse に指定すると、file.contents が Stream になります。 そこで、以下のように gulp.src() に options を指定して gulp-uglify に渡そうとするとエラーになりました。

import gulp from 'gulp';
import uglify from 'gulp-uglify';

gulp.task('compress', () => {
  return gulp.src('lib/*.js', {buffer: false})
    .pipe(uglify())
    .pipe(gulp.dest('dist'));
});

vinyl-buffer は、GitHub - hughsk/vinyl-buffer: Convert streaming vinyl files to use buffers にある通り、Stream を Buffer に変換するため、以下のように gulp-uglify の前で pipe() してあげると、正常に動作しました。

import gulp from 'gulp';
import uglify from 'gulp-uglify';
import buffer from 'vinyl-buffer';

gulp.task('compress', () => {
  return gulp.src('lib/*.js', {buffer: false})
    .pipe(buffer())
    .pipe(uglify())
    .pipe(gulp.dest('dist'));
});

まとめ

vinyl-source-stream は、readable stream を vinyl オブジェクト (Stream) に変換し、vinyl-buffer は vinyl オブジェクトを Stream から Buffer に変換する。