7

I'm adding watchify to our build process but I want to put a precondition to watchify running, and that is that the file(s) that changed pass our linting step (which is using ESLint).

I was thinking of doing this:

function runBrowserify(watch){
  var babel = babelify.configure({
      optional: ['es7.objectRestSpread']
  });
  var b = browserify({
    entries: './app/main.js',
    debug: true,
    extensions: ['.jsx', '.js'],
    cache: {},
    packageCache: {},
    fullPaths: true
  })
  .transform(babel);

  if(watch) {
    // if watch is enable, wrap this bundle inside watchify
    b = watchify(b);
    b.on('update', function(ids) {
      //run the linting step
      lint(ids);

      //run the watchify bundle step
      gutil.log(gutil.colors.blue('watchify'), 'Started');
      bundleShare(b);
    });
    b.on('time', function (time) {
      gutil.log(gutil.colors.blue('watchify'), 'Finished', 'after', gutil.colors.magenta(time), gutil.colors.magenta('ms'));
    });
  }

  bundleShare(b);
}

function bundleShare(b) {
  b.bundle()
    .pipe(source('main.min.js'))
    .pipe(gulp.dest('./dist'));
}

function lint(glob) {
  return gulp.src(glob)
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failOnError());
}

The problem is that the linting step is async so it doesn't finish before the bundling would be done (it also throws so I probably need to use plumber to stop it from terminating the watch step).

So how would I make a precondition before I call bundleShared?

Aaron Powell
  • 24,927
  • 18
  • 98
  • 150

1 Answers1

2

I was able to do this using the closure method I mentioned above. I also moved my Browserify and Watchify code into helper functions that each build could take advantage of.

gulpfile.js (partial)

gulp.task('build:dev', buildDev); 
gulp.task('lint:js', lintJS);

function lintJS(callback) {
  return gulp.src(['src/**/*.js', 'src/**/*.jsx', '!src/js/vendor/**/*.*',])
      .pipe(eslint())
      .pipe(eslint.format())
      .pipe(eslint.failAfterError());
}

function buildDev(callback) {
  var bundler = getBundler('src/js/app.jsx', { debug: true }, callback);
  var watcher = getWatcher(bundler, rebundle);

  function rebundle() {
    lintJS(callback);

    return watcher.bundle()
      .pipe(source('bundle.min.js'))
      .pipe(buffer())
      .pipe(gulp.dest('dist/js'));
  }

  rebundle();
  // Call watch methods here, i.e.: watchHTML()
  return callback();
}

/****************************** Helper functions ******************************/

/**
 * Gets the default Browserify bundler used by all builds.
 *
 *
 * @param path A string representing where Browserify should start from
 * @param options An Object containing options for the bundler
 * @param callback The Gulp callback function from the calling task
 * @return A basically configured Browserify bundler
 */
function getBundler(path, options, callback) {
  var bundler = browserify(path, { debug: options.debug, cache: {}, packageCache: {} });
  bundler.transform(babelify);
  bundler.on('log', gutil.log);
  bundler.on('error', gutil.log.bind(gutil.colors.red, 'Browserify Error'));

  return bundler;
}

/**
 * Gets the default Watchify watcher used by dev builds. By default, the watcher
 * will rebundle the Browserify package when an update occurs.
 *
 * @param bundle The Browserify bundler object
 * @param rebundle A function to perform when Watchify detects a code update
 * @return A basically configured Watchify watcher
 */
function getWatcher(bundle, rebundle) {
  var watcher = watchify(bundle);
  watcher.on('update', rebundle);

  return watcher;
}

For my test and prod builds, I don't use Watchify (and thus have no rebundle() method) so I keep the 'lint:js' task as a dependency:

gulp.task('build:test', ['lint:js'], buildTest);
gulp.task('build:prod', ['lint:js'], buildProd);
Matthew Herbst
  • 29,477
  • 23
  • 85
  • 128
  • Thanks for this! If I'd like to run a live-reload server along with the watch feature, where would that go in your config? – Olivier Lance Sep 07 '15 at 17:11
  • Immediately after the call to `rebundle()` in `buildDev`. What you really want to do is to create a watch task on your `dist/` folder. That way whenever `dist/` changes, your watch task will detect it and run the server-reload code. I updated the code with a comment showing where it goes. – Matthew Herbst Sep 07 '15 at 17:49
  • Yup, my current watch task already watches `dist` :) Thanks for the clarification! I like your solution, although I'd rather have Watchify run a task instead of calling a function so that dependencies & all could still be managed with Gulp instead of by calling methods here and there (just like you did with `lintJS`) :| – Olivier Lance Sep 07 '15 at 21:33
  • You could easily break my function into several tasks that depend on each other. Then just call the root task and you should have no problems – Matthew Herbst Sep 07 '15 at 21:40
  • Well for the first call yes that would work, but then when a file changes it would just call the given `rebundle()` function, not a task and its dependencies... I'm not sure I'm clear? :\ – Olivier Lance Sep 08 '15 at 00:39
  • @OlivierLance you could make `rebundle` a task. When your watch detects a change in `dist/`, run the `rebundle` task – Matthew Herbst Sep 08 '15 at 00:45
  • Hmm I can't seem to get my head around this right now (3AM here...) and this is not a chat room.... so thanks for your help @MatthewHerbst, I'll give that another thought later! – Olivier Lance Sep 08 '15 at 00:56
  • I am working on a similar setup, but find adding the `eslint.failAfterError()` to the pipe will terminate gulp entirely - killing any watchers and livereloads. – plong0 Apr 07 '17 at 18:24