Gulp and AngularJS, A tune up | NetEngine

Gulp and AngularJS, A tune up

Brad Tuesday, 5 August 2014

Back in March we made the switch to Gulp for building our AngularJS apps. We’ve continued to work on the process and decided an update was in order.

What we’ve moved to now is influenced by the wonderful github.com/greypants/gulp-starter

Firstly: Split it all up

As in the above example we now seperate each task out into its own file which only looks after one piece of the build process. It just makes so much sense. Our gulpfile is now essentially this:

var fs          = require('fs');
var onlyScripts = require('./utils/script_filter');
var tasks       = fs.readdirSync('./gulp/tasks/').filter(onlyScripts);

tasks.forEach(function(task) {
  require('./tasks/' + task);
});

And each task file looks like this:

var gulp    = require('gulp');
var config  = require('../config');
var clean   = require('../utils/clean_files');
var plugins = require('gulp-load-plugins')();
var q       = require('q');

gulp.task('images', function () {
  var deferred = q.defer();
  clean('/images').then(function(){
    plugins.util.log('Minifying images');

    gulp.src('app/images/**/*.*')
      .pipe(plugins.imagemin())
      .pipe(plugins.size({ showFiles: true }))
      .pipe(gulp.dest(config.tmpDir + '/images'))
      .pipe(gulp.dest(config.publicDir + '/images'))
      .on('error', plugins.util.log)
      .on('end', deferred.resolve);
  });
  return deferred.promise;
});

Horay!

Secondly: Oh my yes… promises

We’d like to pass arguments to some utility tasks, for example clean. This means we’re unable to only use the dependancy feature of Gulp’s task runner Orchestrator. It’d also be nice to have generic versions of some tasks which can be reused.

gulp.task('some-task', ['clean'], function () { 
  // Clean is done... but what did I clean? Did I have to clean all of that?
  // Do I write multiple versions of clean which do esstenially the same thing?
});

Fortunately Orchestrator can accept both streams and promises as return values.

var gulp      = require("gulp");
var plugins   = require('gulp-load-plugins')();
var config    = require('../config');
var q         = require('q');

function clean(relativePath) {
  var deferred = q.defer();

  gulp
    .src([(config.publicDir + relativePath), (config.tmpDir + relativePath)], {read: false})
    .pipe(plugins.clean({force: true}))
    .on('error', plugins.util.log)
    .on('end', deferred.resolve);

  return deferred.promise;
};

module.exports = clean;

Now when we define our task we can define a new promise, resolve it in clean’s then and return it.

function styles(fileToClean, outputFile, includeRevision){
  var deferred = q.defer();
  clean(fileToClean).then(function(){

    var stream = gulp.src(outputFile)
      .pipe(plugins.plumber())
      .pipe(plugins.rubySass())
      .pipe(gulp.dest(config.tmpDir + '/styles'))
      .pipe(plugins.minifyCss());

    if (includeRevision){ stream.pipe(plugins.streamify(plugins.rev())) }

    stream.pipe(plugins.size({ showFiles: true }))
     .pipe(gulp.dest(config.publicDir + '/styles'))
     .on('error', plugins.util.log)
     .on('end', deferred.resolve);
  });

  return deferred.promise;
};

gulp.task('styles', function () {
  return styles('/styles/app*.css', 'app/styles/sass/app.scss', true).then(indexhtml);
});

gulp.task('editable_styles', function () {
  return styles('/styles/editable_styles.css', 'app/styles/sass/editable_styles.scss', false);
});

Great, now we can pass arguments to utility and generic tasks. We have one version of the style task here which revs the output file and makes sure to update our index.html, and one version which doesn’t.

Both tasks return Orchestrator-able promises such that we can still use task dependency when it makes sense:

gulp.task('serve', ['styles', 'editable_styles', 'scripts', ...], function(){
  //...
});

Neat!

Thirdly, push it

The last piece of the puzzle is figuring out a tidy deployment strategy for the built app. For apps which are deployed to S3 this isn’t really a problem, for Apps which are being served from the Rails public directory it is. We currently check the built app into the repo to ease deployment, however this introduces conflict hell. The conflicts are so easily resolved that we haven’t made fixing it a priority, however it does need to be tackled.

Aaand we’re done

We’re loving working with Gulp, it’s fast, functional and super flexible. We’re always working on how we build and deploy our AngularJS applications, so … stay tuned!

comments powered by Disqus