読者です 読者をやめる 読者になる 読者になる

かもメモ

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

Gulp JSHintでエラー/ワーニングが出たら それ以上タスクを実行させない。かつ gulp.watchが停止しないようにしたい。

Gulp node.js
  1. Gulpでjavascriptのファイルを監視
  2. JSHintでエラーなどをチェック
  3. 問題がなければconcatで1つにまとめて、min化
  4. browser-syncでリロード

という処理を行わせようと思い、Google Web Starter Kitを参考にGulpのタスクを作っていたのですが、下記の問題に当たってしまいました。

  1. JSHintでエラー/ワーニングがあっても次のタスクが実行されてしまう
  2. JSHintでエラー/ワーニングがあるとjavascriptファイルを変更しても一切タスクが実行されなくなる (watchされなくなる?)

問題があった元のGulpタスク

使っているプラグインのバージョン

// package.json
  "devDependencies": {
    "browser-sync": "^2.9.1",
    "gulp": "^3.9.0",
    "gulp-concat": "^2.6.0",
    "gulp-if": "^1.2.5",
    "gulp-jshint": "^1.11.2",
    "gulp-load-plugins": "^0.10.0",
    "gulp-uglify": "^1.4.0",
    "jshint-stylish": "^2.0.1",
    "main-bower-files": "^2.9.0",
    "run-sequence": "^1.1.2"
  }

Gulpのタスク

// gulpfile.js
var gulp        = require("gulp"),
    $           = require('gulp-load-plugins')(),
    browserSync = require('browser-sync'),
    runSequence = require('run-sequence'),
    concat      = require("gulp-concat"),
    uglify       = require("gulp-uglify"),
    jshint      = require("gulp-jshint"),
    reload      = browserSync.reload;

// js lint
gulp.task('jshint', function () {
  return gulp.src(devDir + '/js/*.js')
    .pipe( jshint() )
    .pipe( jshint.reporter('jshint-stylish') )
    .pipe( $.if(!browserSync.active, jshint.reporter('fail')) );
});

// concat js
gulp.task('concat.js', function(cb) {
  return gulp.src(devDir + '/js/*.js')
    .pipe( concat('main.js') )
    .pipe( gulp.dest( destDir + '/js' ) );
});

// min js
gulp.task('uglify', function() {
  return gulp.src(destDir + '/js/main.js')
    .pipe(uglify({
      preserveComments: 'some'
    }))
    .pipe(rename({
      extname: '.min.js'
    }))
    .pipe(gulp.dest( destDir + '/js' ));
});

// Javascript Task
gulp.task('js', function(cb) {
  runSequence('jshint', 'concat.js', 'uglify', reload);
  // runSequence('jshint', 'concat.js', 'uglify', reload, cb);
  // ↑ 最後に cb を追加するとエラーで実行できない
  // browser-syncとrun-sequenceの相性の問題っぽい。
});

// watch
gulp.task('default', function() {
  gulp.watch([devDir + '/js/*.js'], ['js']);
});

このタスクの書き方だと、javascriptファイルを変更してJSHintでerror/warningsがあった時も、 次のconcat.jsuglifyreloadが実行されてしまい、エラーのあるjsファイルが読み込まれた状態で表示されてしまいます。
その上、JSHintでerror/warningsがあると、次にjavascriptファイルを修正・保存してもナゼかjavascriptのwatchだけが効かなくなってしまいます。
(他にcssやhtmlをwatchしているものは正常に動作し続けます。何故ゆえにぃ!?

とりあえず、監視されなくなってしまうのが一番困るのでまずは
b. JSHintでエラー/ワーニングがあるとjavascriptファイルを変更しても一切タスクが実行されなくなる (watchされなくなる?)」の問題を解決していきたいと思います。

browserSync.reload のさせ方に問題があった!

色々試してみた結果どうやらbrowserSync.reload()だけで実行するのが良くないっぽいという事がわかりました。
どちらのオプションが必要なのか今少し理解できていないのですが、{stream: true, once: true}というオプションを与えて、別途gulp.taskに登録してリロードさせればJSHintでエラー/ワーニングがあってもファイルのwatchがされなくなるという現象を解決することができました。

gulpfile.jsを編集します

var gulp        = require("gulp"),
    $           = require('gulp-load-plugins')(),
    browserSync = require('browser-sync'),
    runSequence = require('run-sequence'),
    concat      = require("gulp-concat"),
    uglify       = require("gulp-uglify"),
    jshint      = require("gulp-jshint");

// js lint
gulp.task('jshint', function () {
  return gulp.src(devDir + '/js/*.js')
    .pipe( jshint() )
    .pipe( jshint.reporter('jshint-stylish') )
    .pipe( $.if(!browserSync.active, jshint.reporter('fail')) );
});

// concat js
gulp.task('concat.js', function(cb) {
  return gulp.src(devDir + '/js/*.js')
    .pipe( concat('main.js') )
    .pipe( gulp.dest( destDir + '/js' ) );
});

// min js
gulp.task('uglify', function() {
  return gulp.src(destDir + '/js/main.js')
    .pipe(uglify({
      preserveComments: 'some'
    }))
    .pipe(rename({
      extname: '.min.js'
    }))
    .pipe(gulp.dest( destDir + '/js' ));
});

// reload を別タスクにする
gulp.task('reload', function() {
  return gulp.src('/')
    .pipe( browserSync.reload({stream: true, once: true}) );
});

// Javascript Task
gulp.task('js', function(cb) {
  // reloadを別タスクにして実行させる
  // 別タスクにすると、最後に callback を追加してもエラーにならない!
  runSequence('jshint', 'concat.js', 'uglify', 'reload', cb);
});

// watch
gulp.task('default', function() {
  gulp.watch([devDir + '/js/*.js'], ['js']);
});

リロードのタスクの実効のさせ方がちょっとキモチワルイのですが、これでJSHintで問題があってもjavascriptの監視が切れてしまうという問題は解消されました。
もしかしたらrunSequenceの最後にcallbackを追加できたのが良かったのかもしれません。ちょっと原因がハッキリしていないのでアレなのですが...

次に「a. JSHintでエラー/ワーニングがあっても次のタスクが実行されてしまう」の問題にとりかかります。

JSHintの最後の.pipe()の書き方が違った!

Google Web Starter Kitを参考にして書いていたJSHintのタスク ↓

// js lint
gulp.task('jshint', function () {
  return gulp.src(devDir + '/js/*.js')
    .pipe( jshint() )
    .pipe( jshint.reporter('jshint-stylish') )
    .pipe( $.if(!browserSync.active, jshint.reporter('fail')) );
});

最後の部分は !browserSync.active なら jshint.reporter('fail') を実行するようになっています。
参考にした記事などで.pipe( jshint.reporter('fail') )とするとJSHintにかかった時にStreamが止まってしまう (gulp コマンドで実行していたwatchなどが終了してしまう) とあったのをそのまま鵜呑みにしていました。
しかし"gulp-jshint": "^1.11.2"では、どうやら?この問題は解決されているっぽく、タスクの最後の部分を純粋に.pipe( jshint.reporter('fail') )とするだけでJSHintで問題があった時だけ次のタスクを実行しないようになっていました!

gulpfile.jsを編集します。

var gulp        = require("gulp"),
    browserSync = require('browser-sync'),
    runSequence = require('run-sequence'),
    concat      = require("gulp-concat"),
    uglify       = require("gulp-uglify"),
    jshint      = require("gulp-jshint");

// js lint
gulp.task('jshint', function () {
  return gulp.src(devDir + '/js/*.js')
    .pipe( jshint() )
    .pipe( jshint.reporter('jshint-stylish') )
    .pipe( jshint.reporter('fail') ); // ← 変更 
});

// concat js
gulp.task('concat.js', function(cb) {
  return gulp.src(devDir + '/js/*.js')
    .pipe( concat('main.js') )
    .pipe( gulp.dest( destDir + '/js' ) );
});

// min js
gulp.task('uglify', function() {
  return gulp.src(destDir + '/js/main.js')
    .pipe(uglify({
      preserveComments: 'some'
    }))
    .pipe(rename({
      extname: '.min.js'
    }))
    .pipe(gulp.dest( destDir + '/js' ));
});

// reload
gulp.task('reload', function() {
  return gulp.src('/')
    .pipe( browserSync.reload({stream: true, once: true}) );
});

// Javascript Task
gulp.task('js', function(cb) {
  runSequence('jshint', 'concat.js', 'uglify', 'reload', cb);
});

// watch
gulp.task('default', function() {
  gulp.watch([devDir + '/js/*.js'], ['js']);
});

これで、JSHintでエラー/ワーニングがあると次のタスクは実行せず、javascriptのwatchが切れたり、Streamが停止したしすることがないgulpタスクを作成することができました!!! 今回はすごくハマってしまいました。

!注意すること

javascript以外の監視しているファイルのタスクでオプションの無いbrowserSync.reload()が実行されていると、JSHintでエラーがあってもリロードだけが実行されてしまうようになってしまいます。謎です!!

EX: gulpfile.js

// watch
gulp.task('default', function() {
  gulp.watch([devDir + '/js/*.js'], ['js']);
  // ↓ jadeファイルを変更すると browserSync.reload() が実行される様になっている
  gulp.watch([devDir + '/jade/**/*.jade', devDir + '/jade/*.jade'], ['jade', browserSync.reload]);
});

一度でもjadeを変更してタスクが実行されてしまうと、それ以降javascriptの変更時にJSHintで問題があった時、次のタスクは実行されないのですがナゼか画面のリロードだけが実行されてしまうようになってしまいます。
リロードは全て別に作成したタスクでオプション付きで実行させるようにすれば、この問題は発生しません。

// watch
gulp.task('default', function() {
  gulp.watch([devDir + '/js/*.js'], ['js']);
  // これなら問題は発生しない
  gulp.watch([devDir + '/jade/**/*.jade', devDir + '/jade/*.jade'], ['jade', 'reload']);
});

感想

ただ、gitHubのチケット上の解決したコメントは1 Jul 2014で、問題があったってblogの日時は2014-07-05なので、どうなんだろう?その後さらにアップデートされて問題が解決したのかな?
少し謎が残っています。。。

もしかして、個別にjaHintでチェックするよりconcatで1つにまとめてJSHintでチェックする方が処理速い?  

[参考]

TVアニメ/データカードダス「アイカツ!」3rdシーズン挿入歌ミニアルバム2「Colorful Smile」

TVアニメ/データカードダス「アイカツ!」3rdシーズン挿入歌ミニアルバム2「Colorful Smile」

  • アーティスト: AIKATSU☆STARS!,るか・もな from AIKATSU☆STARS!,みほ・みき from AIKATSU☆STARS!,みき・みほ from AIKATSU☆STARS!,れみ from STAR☆ANIS,えり from STAR☆ANIS,もな・るか from AIKATSU☆STARS!,るか・もな・みき from AIKATSU☆STARS!,るか・もな・みき・みほ・ななせ・かな from AIKATSU☆STARS!,空谷泉身,tzk
  • 出版社/メーカー: ランティス
  • 発売日: 2015/08/26
  • メディア: CD
  • この商品を含むブログ (3件) を見る