アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

gulp で uglify-es を利用する

タスク ランナーは npm-scripts 派なのだけど akabekobeko/examples-web-app に公開している front-end-starter には gulp 版も用意してある。かつて gulp を利用していたこと、世間では現在もそれなりに Web フロントエンド開発で gulp が採用されていることから動向チェック用にメンテナンスしている。

front-end-starter は Electron も含む Web フロントエンド開発環境の変更を気が向いたときに反映しているのだが、そういえば uglify-es を gulp で利用したくなったらどうするのだろう?というのが気になったので試してみた。

gulp-uglify

gulp で uglify-js を利用する場合は gulp wrapper となる gulp-uglify を採用するのが一般的だろう。では uglify-es はどうなのか。uglify-js 本家が uglify-es を独立したパッケージとしているように gulp-uglify-es があるとか?と予想したが gulp-uglify として js/es を切り替えられるようになっていた。

README の Using a Different UglifyJS から引用する。

By default, gulp-uglify uses the version of UglifyJS installed as a dependency. It's possible to configure the use of a different version using the "composer" entry point.

標準では dependency としてインストールされた uglify-js を使用するが、composer を利用することで js/es を分岐できるとのこと。以下は README に併記されているサンプル コード。

var uglifyjs = require('uglify-js'); // can be a git checkout
                                     // or another module (such as `uglify-es` for ES6 support)
var composer = require('gulp-uglify/composer');
var pump = require('pump');

var minify = composer(uglifyjs, console);

gulp.task('compress', function (cb) {
  // the same options as described above
  var options = {};

  pump([
      gulp.src('lib/*.js'),
      minify(options),
      gulp.dest('dist')
    ],
    cb
  );
});

gulp ストリーム (pipe) をそのまま使用せず pump で wrap している点を除けば、割りと簡単に切り替えられる。

gulp タスク修正

gulp-uglify としては pump を推奨しているようだ。しかし以下のように gulp-watchify と組み合わせていると書き換えが多くなるので、

// ...import や gulp-load-plugins の設定など

gulp.task('js', $.watchify((watchify) => {
  const time = process.hrtime()

  return gulp.src([ config.dir.js + 'App.js' ])
    .pipe($.plumber())
    .pipe(watchify({
      watch: config.isWatchify,
      basedir: './',
      debug: true,
      transform: [ 'babelify' ]
    }))
    .pipe(VinylBuffer())
    .pipe($.if(!(config.isRelease), $.sourcemaps.init({ loadMaps: true })))
    .pipe($.if(config.isRelease, $.uglify()))
    .pipe($.rename('bundle.js'))
    .pipe($.if(!(config.isRelease), $.sourcemaps.write('./')))
    .pipe($.if(config.isRelease, gulp.dest(config.dir.dist), gulp.dest(config.dir.assets)))
    .on('end', () => {
      const taskTime = PrettyHRTime(process.hrtime(time))
      $.util.log('Bundled', $.util.colors.green('bundle.js'), 'in', $.util.colors.magenta(taskTime))
    })
}))

gulp ストリームのまま移行する。

// ...import や gulp-load-plugins の設定など

// 追加
import UglifyES from 'uglify-es'
import UglifyComposer from 'gulp-uglify/composer'

gulp.task('js', $.watchify((watchify) => {
  const time = process.hrtime()

  // uglify-es で minify するための関数設定
  const minify = UglifyComposer(UglifyES, console)

  return gulp.src([ config.dir.js + 'App.js' ])
    .pipe($.plumber())
    .pipe(watchify({
      watch: config.isWatchify,
      basedir: './',
      debug: true,
      transform: [ 'babelify' ]
    }))
    .pipe(VinylBuffer())
    .pipe($.if(!(config.isRelease), $.sourcemaps.init({ loadMaps: true })))
    // uglify-es で minify
    .pipe($.if(config.isRelease, minify({})))
    .pipe($.rename('bundle.js'))
    .pipe($.if(!(config.isRelease), $.sourcemaps.write('./')))
    .pipe($.if(config.isRelease, gulp.dest(config.dir.dist), gulp.dest(config.dir.assets)))
    .on('end', () => {
      const taskTime = PrettyHRTime(process.hrtime(time))
      $.util.log('Bundled', $.util.colors.green('bundle.js'), 'in', $.util.colors.magenta(taskTime))
    })
}))

このサンプルには掲載していないが、別のリリース用イメージ生成タスクで config.isReleasetrue にしてから js タスクを実行することで uglify-es による minify 処理が走ることを確認できた。

まとめ

Electron や Chrome (Chromium) のように先進的な環境を決め打ちで開発したり、babel-preset-env で環境を抽象化しつつ変換を最小におさえると minify 対象となるコードに ES.next 部分があらわれる。その際は minify ツール側も ES.next 対応が求められるため uglify-es や babili を利用することになるだろう。gulp に関しては本記事のように gulp-uglify の機能として uglify-es を採用するとよい。

ところで gulp-uglify の設計は gulp wrapper における問題へのよき回答にもなっており、感心した。

gulp を想定していない npm を gulp ストリームにのせる場合、vinyl-buffer などで頑張るか、そのような処理で wrap した gulp プラグインとする必要がある。前者はエンド ユーザー負担が大きすぎるので後者を選ぶことが多いのだけど、こちらだと gulp プラグインの dependency で wrap 対象 npm の更新へ追従しなければならない。

gulp-uglify/composer 方式なら自身の dependency が古びても、エンド ユーザー側から任意バージョンの npm を指定できる。つまり gulp 化の恩恵を享受しつつ最新 npm に置き換えられるのだ。対象 npm のインターフェースが維持されていることは前提となるが、うまい方法である。自身の dependency + 同一インターフェース npm をサポートできる。

私がタスク ランナーを gulp から npm-scripts へ移行した理由のひとつは gulp プラグインの陳腐化。しかしこの設計が普及すれば問題は概ね解決しそうである。gulp プラグインのガイドラインに含めてもよいのでは?とも思う。

この記事を書いた後に気付いたのだが babili の gulp 版として gulp-babili が提供されている。こちらは Babel/babili 本家なので、Babel ファミリーを好むなら uglify-es の代りに採用してもよいだろう。

Copyright © 2009 - 2023 akabeko.me All Rights Reserved.