かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Gulp concatでファイルをまとめて、uglifyでmin化してファイル名を変更して出力したい。

https://raw.githubusercontent.com/gulpjs/artwork/master/gulp-2x.png

最近gruntよりgulpの方が熱い!という噂を聴いてバリバリgrunt派だったのですがチャレンジしてみました。
インストールとかは同じnode関連なのでnpm使えば簡単にできるので、この辺りgruntと大差なく導入できるなぁ〜って印象でした。 gruntで言うところのwatchがgulp自体に含まれてるっぽいので、gruntより最初の段階でnpm install する量は少なくて済みますね。ハラショー!!

インストール関係はこちらを参考にしました。 liginc.co.jp

いつもやっている、javascriptの結合とmin化をするタスクを書いてみました。

プロジェクトのpackage.jsonを作る。

node.jsが必要なので、インストールされてない場合は下記リンクなどを参考にインストールしておいてください。
Macにnvm + Node.jsをインストールする - Qiita

プロジェクトのpackage.jsonを生成します。

$ npm init

gulpと必要なプラグインをインストールする

$ npm install --save-dev gulp

結合するプラグインgulp-concatをインストー

$ npm install --save-dev gulp-concat

min化するプラグインgulp-uglifyをインストー

$ npm install --save-dev gulp-uglify

エラーが起こってもタスクが停止しないようにするプラグインgulp-plumberをインストー

$ npm install --save-dev gulp-plumber

 

gulpfile.jsを作成

gruntで言うところのgruntfile.jsですね。このファイル作成してタスクを書いていきます。

$ touch gulpfile.js
// glup・使用する各プラグインを最初に読み込む
var gulp = require("gulp"),
    uglify = require("gulp-uglify"),
    concat = require("gulp-concat"),
    plumber = require("gulp-plumber");

// それぞれのプラグインで行う処理を書いていく
gulp.task('js.concat', function() {
  return gulp.src('dev/js/*.js')
    .pipe(plumber())
    .pipe(concat('main.js'))
    .pipe(gulp.dest('app/webroot/js'));
});
gulp.task('js.uglify', function() {
  return gulp.src('app/webroot/js/main.js')
    .pipe(plumber())
    .pipe(uglify('main.min.js'))
    .pipe(gulp.dest('app/webroot/js/'));
});

// 監視して処理するのをひとまとめにしておく。
gulp.task('js', ['js.concat', 'js.uglify']);

// ファイルを監視して実行させる
gulp.task('watch', function() {
  gulp.watch(['dev/js/*.js'], ['js']);
});

こんな感じでしょうか。とりあえず実行してみます。

$ gulp
[16:30:44] Using gulpfile ~/gulpfile.js
[16:30:44] Task 'default' is not in your gulpfile
[16:30:44] Please check the documentation for proper gulpfile formatting

defaultって名前のタスクが無いとダメみたいです。
何をまず実行するのかが解らないからですよね。よくよく考えればそりゃそうだ!でした。
gulpfile.jsを編集します。まずはファイルの監視を動作させたいので、watchしているタスク名をdefaultに変更します。

/* 略 */

// ファイルを監視して実行させる
// gulpコマンドで実行できるように、タスク名をdefaultに変更
gulp.task('default', function() {
  gulp.watch(['dev/js/*.js'], ['js']);
});

再試行します。

$ gulp

今度はエラーにならず監視が上手く動作しているようです。  
 

gulp-uglify ではmin化したファイルを別ファイルにできないっぽい?

gulpで監視した状態で、dev/js/ 内にあるjavascriptファイルを編集すると下の用なエラーが表示されました。

/node_modules/gulp-uglify/node_modules/deap/lib/deap.js:56
        Object.keys(b).forEach(function(p) {
              ^
TypeError: Object.keys called on non-object
...

どうやらgulp-uglifyは引数に出力するファイル名を指定できるわけではないようです。
調べてみた結果、gulp-uglifyだけではどうやら別のファイル名にして出力できないっぽいと言うことがわかりました。
Allow piping to the same directory with a different filename · Issue #9 · terinjokes/gulp-uglify · GitHub

gulp-renameというプラグインを使ってファイル名を変更してあげるのが良いみたいです。
gulp-renameをインストールします。

$ npm install --save-dev gulp-rename

gulpfile.jsを編集します。 合わせて gulp-uglifyで特定のコメントを残したままJavaScriptをminifyする - Qiita こちらの記事を参考に/*! */ で始まるコメントがmin化した時に消えないようにします。

// glup・使用する各プラグインを最初に読み込む
var gulp = require("gulp"),
    uglify = require("gulp-uglify"),
    concat = require("gulp-concat"),
    rename = require('gulp-rename'), // ファイル名を変更するプラグインを追加
    plumber = require("gulp-plumber");

// それぞれのプラグインで行う処理を書いていく
gulp.task('js.concat', function() {
  return gulp.src('dev/js/*.js')
    .pipe(plumber())
    .pipe(concat('main.js'))
    .pipe(gulp.dest('app/webroot/js'));
});
gulp.task('js.uglify', function() {
  return gulp.src('app/webroot/js/main.js')
    .pipe(plumber())
    .pipe(uglify({
      // ※ gulp-uglify v3.0.0 以前の場合 ↓
      preserveComments: 'some' // ! から始まるコメントを残すオプションを
    }))
    .pipe(rename('main.min.js'))  // 出力するファイル名を変更
    .pipe(gulp.dest('app/webroot/js/'));
});

// 監視して処理するのをひとまとめにしておく。
gulp.task('js', ['js.concat', 'js.uglify']);

// ファイルを監視して実行させる
gulp.task('watch', function() {
  gulp.watch(['dev/js/*.js'], ['js']);
});

追記 2017-06-30
gulp-uglify v3.0.0 以降ではライセンスのコメントを残す preserveComments オプションが廃止になっていてエラーになってしまします。

gulp-uglify v3.0.0 以上の場合はjs.uglifyのタスクを次のように変更してください。

gulp.task('js.uglify', function() {
  return gulp.src('app/webroot/js/main.js')
    .pipe(plumber())
    .pipe(uglify({
      // ※ gulp-uglify v3.0.0 以上↓
      output:{
        comments: /^!/   // 正規表現で /*! //! で始まるコメントを残す
      }
    }))
    .pipe(rename('main.min.js'))  // 出力するファイル名を変更
    .pipe(gulp.dest('app/webroot/js/'));
});

▲ 追記ココまで ▲

gulpコマンドで再度監視を開始して、javascriptファイルを変更した所、ファイルが結合され、min化されていました。
これでgruntと同じようにタスクファイルが作れたぞ!と思ったのですが…  
 

gulpは高速化のためにタスクを並列処理しているので、必ずしも結合してからmin化されている訳ではなかった!

実行した時のコンソールを見ていると時々

[17:15:52] Using gulpfile ~/gulpfile.js
[17:15:52] Starting 'default'...
[17:15:52] Finished 'default' after 9.54 ms
[17:15:53] Starting 'js.concat'...
[17:15:53] Starting 'js.uglify'...
[17:15:53] Finished 'js.uglify' after 47 ms
[17:15:53] Finished 'js.concat' after 60 ms
[17:15:53] Starting 'js'...
[17:15:53] Finished 'js' after 14 μs

な感じで、処理の完了順がタスクの実行順になってないないことがありました。

どうやらgulpのタスクは非同期処理(並行処理)がデフォルトなので、実行順が決まっている場合は同期処理になるように書いてあげないとダメなようです。
gulpのタスクを同期的に実行する方法 - Qiita
gulp入門その3―こまごま便利なこと | アライドアーキテクツのクリエイターブログ
こちらの記事を参考に、
今回はお手軽そうなgulp.taskの第2引数にどの処理の後に行うかを指定する方法を試してみます。
js.uglifyjs.concatの後に行いたいので、再びgulpfile.jsを編集します。

/* 略 */

// 第2引数に先に実行して欲しい js.concat を指定する
gulp.task('js.uglify', ['js.concat'], function() { 
  return gulp.src('app/webroot/js/main.js')
    .pipe(plumber())
    .pipe(uglify({preserveComments: 'some'}))
    .pipe(rename('main.min.js'))
    .pipe(gulp.dest('app/webroot/js/'));
});

/* 略 */

これで、どうやら意図した順番にタスクが実行できるようになったみたいです。
タスクが複雑になってくると run-sequence プラグインを使用する方法が見通しが良さそうかなーと思いました。

 

感想

gruntと実行ファイルの書き方や同じような名前のプラグインでもできる事が少し違っていたりするので初めは戸惑う部分もありましたが慣れればどちらでも大差なく使っていくことができそうだなーと感じました。


[参考]