Swept up in the stream: 5 ways ISL used Gulp to improve our process


ISL recently switched our front-end build system from Grunt to Gulp. Our primary motivations for switching are Gulp’s preference for code over configuration, node streams, and asynchronous tasks by default. After modifying our existing build process to adhere to the Gulp way, we saw a drastic improvement in overall build speed, on the order of several seconds, and near instant rebuilds for live reloading during development.


By adopting Gulp we’re actually scripting and coding our build process, unlike Grunt where we merely configured tasks. For example, if you want to tap into a Gulp stream and transform a file using plain old vanilla JavaScript, you can. It enables developers to customize their build workflow much easier, and the streaming paradigm works well since we’re literally just dealing with text files.

Gulp streams mimic the paradigm of Unix pipelines and redirection that we love so much. Combining this with Gulp’s asynchronous task execution powers fast builds with less IO meaning fewer file writes and less time sunk into waiting for builds to finish. By contrast, although Grunt allows async task implementations, they require more configuration and code resulting in unnecessary overhead for the developer. Also, Grunt async tasks often require writing of intermediate files before passing control to a new module for transformations, resulting in more time spent writing files and less time actually performing transformations.

We did take advantage of run-sequence to better control task execution order (which will be baked into Gulp 4 with series and parallel), but we run all build tasks in parallel and only use this for pre and post-build tasks like cleaning and versioning.

While we probably could have accomplished these changes by refactoring our Grunt workflow, we decided that time would be better spent reevaluating the entire system.


How we switched

First, we converted our Gruntfile, task by task, over to Gulp tasks. We simplified a lot of things along the way, and began converting our projects to a new build process built on Gulp.

After our initial switch to Gulp things still felt a bit Grunty — it was almost a task-by-task replica. We recognized that we needed to refactor to the streaming paradigm to really take advantage of Gulp’s asynchronous task execution. We also wanted to extract the configuration out of the Gulpfile for a few reasons. Since deployment depends on a stable build process, we discourage edits to our base project Gulpfile. Instead, we abstracted all configuration out of the Gulpfile, and require it in a global config variable to be used by all Gulp tasks. This allows our developers to change settings on a project-by-project basis, while still being able to depend on reliable deployments.

Note, we follow a convention of suffixing each path we write in our settings with a forward slash. Following small conventions like these helps later when debugging, writing new tasks, and composing custom paths.

/** N.B. All paths end with / */
var minimist = require('minimist');
var knownOptions = {
  string: 'env',
  default: { env: process.env.NODE_ENV || 'local' }

var options = minimist(process.argv.slice(2), knownOptions);
var src = 'src/';
var dist = 'dist/';
var isCompressing = (options.env === 'staging' || options.env === 'production');
var isDebugging = (options.env !== 'staging' && options.env !== 'production');
var isStyleguiding = true; // change this to (options.env !== '<env>' [&&...]) to disable on specific environments

module.exports = {
  // environment
  compressing: isCompressing,
  debugging: isDebugging,
  styleguiding: isStyleguiding,

  // paths
  src: src,
  dist: dist,
  css: {
    glob: '**/*.css',
    dist: dist + 'css/',
    src: src + 'css/'
  js: {
    src: src + 'js/',
    dist: dist + 'js/',
    glob: '**/*.js'

  // modules
  autoprefixer: {
    browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'Opera 12.1']
  bower: { // aka main-bower-files
    bowerDirectory: src + 'bower_components/'
  nodeSass: {
    errLogToConsole: true,
    includePaths: [
      src + 'bower_components/foundation/scss/'
    sourcemap: isDebugging

We also wanted to simplify the build process since we had a lot of extra tasks from the Gruntfile that made up a complex naming scheme that was difficult to follow. Instead of using different names for the same types of tasks — e.g. compile:js, compile:css, copy:texts, copy:fonts – we now have one task for each type of asset, indicating the type of asset they are responsible for outputting.  We ended up with  clear and concise task names that are obvious and easy to remember:

'clean', 'html', 'css', 'js', 'fonts', 'images', 'extras', 'styleguide', 'rev'

Each task is configured based on the settings file and can perform different operations on streams based on these settings. For example, the rev task, which post-processes assets, can optionally compress files based on the compressing variable in the config file.

Tricks that helped us out

Manipulating these text streams and organizing the assets into their respective directories and switching compressing and linting options wasn’t easy at first. Abstracting the configuration to a separate file helped with these manipulations and there are many other tricks that helped us out: filtering streams, conditionally operating on streams, sending streams through different pipe channels, joining streams, and asynchronously operating on multiple streams. Check out this gulp cheatsheet for a helpful visual guide to some of these.

Note, all gulp plugins are loaded with gulp-load-plugins. You can define any variable name to reference these plugins, and in the following code snippets we use the dollar sign, $, for this. So, no, that is not jQuery in our gulpfile!


Use when: You want to separate configuration from gulp tasks.

Since our configuration is a plain JavaScript object, we can flexibly make settings for all kinds of tasks:

var config = require('./config.js');
var $ = require('gulp-load-plugins')({ lazy: false });

gulp.task('html', function() {
  return gulp.src( [config.src + config.html.glob, '!' + config.bower.bowerDirectory + '**'] )
  .pipe( gulp.dest(config.dist) );

gulp.task('css', function () {
  .pipe( $.sourcemaps.init() )
  .pipe( $.sass(config.nodeSass) )
  .pipe( $.autoprefixer(config.autoprefixer) )
  .pipe( $.sourcemaps.write() )
  .pipe( gulp.dest(config.css.dist) );

Filtering streams

Use when: You need to operate on a subset of a stream based on a glob.

To focus using modules on a subset of files in a stream, we can use a couple of different modules to filter files from Gulp streams.

First up, gulp-if is a module that allows you to conditionally operate on files based on globs. An example of this in use is for filtering font files from all bower components:

return gulp.src( require('main-bower-files')({ paths: config.bower }), { read: false } )
.pipe( $.if(config.fonts.glob, gulp.dest(config.fonts.dist)) );

Next, we use gulp-filter to narrow the scope of a stream, perform a few operations, and return to the original stream’s state when we’re done. We use this to avoid linting vendor files in our source directory — for the inevitable script that we need to include manually for some reason:

var vendorFilter = $.filter(['*', '!' + config.js.src + 'vendor/**']);

return gulp.src( [config.js.src + config.js.glob] )
.pipe( vendorFilter )  // avoid hinting assets/js/vendor files
.pipe( $.jshint() )
.pipe( vendorFilter.restore() ) // restore assets/js/vendor files to be copied to dist
.pipe( gulp.dest(config.js.dist) );

Conditionally operate on streams

Use when: You need to perform operations based on a boolean.

Back up to the plate is gulp-if. Along with filtering by glob, this module is useful for conditionally operating on streams based on a boolean. Since we have a lot of booleans in our configuration variable, we use this all the time to transform our files in different ways based on environment. For example, we may not want source maps to be generated for production deploys, but we do for development. Or we may want to compress asset files only on production to make debugging easier on development:

return gulp.src( config.dist + '**/*' )
.pipe( $.if( config.compressing, compressChannel() ))

Sending streams through different pipe channels

Use when: You need to make an optional pipe to send a stream through.

If the above example looks incomplete, that’s because it’s missing context for what we’re doing with compressChannel. That’s where lazypipe comes in to play. We can save a Gulp stream to a variable using lazypipe in order to conditionally pipe our streams through later. This technique completes the above example to show how we only compress files during post-processing on production environments:

var lazypipe = require('lazypipe');

var compressChannel = lazypipe()
.pipe(function() {
  return $.if(config.js.glob, $.uglify());
.pipe(function() {
  return $.if(config.css.glob, $.minifyCss());
.pipe(function() {
  return $.if(config.html.glob, $.htmlmin(config.htmlmin));

return gulp.src( config.dist + '**/*' )
  .pipe( $.if( config.compressing, compressChannel() ))
  .pipe( gulp.dest(config.dist) );

Joining streams

Use when: You need to add source files to your stream or perform different actions on similar files.

Don’t always jump to using this trick. Usually you can get away with using globbing in gulp.src, and the streams are run synchronously (it is a queue after all), so this is rarely needed. However, we used this to join two separate source streams into a single stream to simplify our fonts task. Instead of using two different tasks to grab fonts included in our bower components and project specific fonts in our source directory, we used StreamQueue to grab the sources individually in one task:

var streamqueue = require('streamqueue');
return streamqueue({ objectMode: true },
  gulp.src( require('main-bower-files')({ paths: config.bower }), { read: false } ),
  gulp.src( [config.fonts.src + config.fonts.glob] )
).pipe( $.if(config.fonts.glob, gulp.dest(config.fonts.dist)) );)

Generally, StreamQueue would be better off being used for performing different actions on similar files. This would allow you to keep all of the operations for a type of file in one task, while still accomplishing everything needed for the build process. For example, if you had different sets of CSS files to operate on using different modules, a good way to use StreamQueue for this would be:

return streamqueue({ objectMode: true }, 
  gulp.src( './css/src/**/*.less' ) 
  .pipe( less() ), 
  gulp.src( './css/src/second.css' ) 
  .pipe( autoprefixer('last 2 versions') )

Asynchronously operating on multiple streams

Use when: You have multiple, (probably) different tasks to accomplish to complete a task.

Since one of our goals was to simplify the build process, keeping all related tasks under a single named task in our Gulpfile was helpful for achieving this. That said, it made it interesting to tackle building our CSS and JS files from different sources. For example, we use browserify for dependency management and bundling of JS files, where we don’t always want to include main bower vendor files that require shimming etc. Furthermore, we wanted to be explicit about which vendor files we were including, and the gulp-useref workflow inspired us a lot here. To build all of our CSS and JS files, we add our vendor files to an assets.json file defining what file names to write and which files to include in them:

  "css": {
    "assets/css/vendor.css": [
  "js": {
    "assets/js/vendor.js": [

With this, we can map the array of files to create an array of Gulp streams to be executed asynchronously, all while streaming:

 * Creates an array of gulp streams for concatenating an array of files
 * @param {object} files - key is destination filename, val is array of files to be concatenated
 * @param {string} dist - destination path
function vendorMap(files, dist) {
  var path = require('path');
  return Object.keys(files).map(function(distFile) {
    var srcFiles = files[distFile].map(function(file) {
      return config.src + file;

    return gulp.src(srcFiles)
    .pipe( $.concat(path.basename(distFile)) )
    .pipe( gulp.dest(dist + path.dirname(distFile) );

And finally, we concatenate these different streams using event-stream’s merge function:

var es  = require('event-stream');
var vendorFiles  = require('./assets.json'));

var appStream = gulp.src( [config.js.src + config.js.glob] )
.pipe( gulp.dest(config.js.dist) );

var vendorStream = vendorMap(vendorFiles.js, config.js.dist);

return es.merge.apply(null, vendorStream.concat(appStream));

In conclusion

Our new Gulp workflow is still maturing but has been happily building projects in production environments for a couple of months now. We’ve made a few changes along the way, but they have been small and quite easy to implement. For example, we ripped out gulp-ruby-sass and replaced it with gulp-sass for native libsass speed improvements. This change was nearly effortless since our config was abstracted out and our tasks have clear separation of concerns. All it took was renaming and adjusting the SASS options in our modules configuration, and adjusting the css task. We didn’t need to touch any paths or other tasks, it just worked.

This level of flexibility is not only desired for current projects, but also helps decrease the barrier to entry for new developers on or joining our team. We don’t have the burden of describing lines of custom code to new devs, instead we have a clear system that is easy to digest and approachable by everyone after a skim of the documentation we’ve written for these few high-level architecture decisions. Future modifications are not hindered by mountains of code changes, and the conventions we follow help simplify complicated infrastructure which lets our devs save time and focus on getting tasks done.

DoubleTree App Redesign: Behind The Scenes

DoubleTree iPad App Design

DoubleTree Hotels first approached us in 2013 to redesign Home & Away, their app that acts as a concierge for business travelers staying at DoubleTree properties. When we initially looked into redesigning the app, we wanted to stay close to the core strategy.

The concept is simple: tell the app which bank, gas station, coffee shop, pharmacy, rental car, etc. that you, as an individual, prefer, and the app will effortlessly map your brand preferences around your hotel.

That core mission has stayed the same, but DoubleTree enlisted us to redesign the app once again this year based on updates to iOS. After research, design, and user testing, we were able to completely revamp the app with added functionality and a greatly improved user experience. The following is a look at the key features we updated.  You can also head over to the app store to check it out.DoubleTree iOS7 iPad App Design

The Map

The original app featured a map with an omnipresent sidebar allowing the user to select their favorite brands for each of the available categories (banks, pharmacies, etc). Once selected, the app will remember your favorite brands no matter which hotel you’re searching from. For the redesign, we wanted to make the map even larger — after testing it was revealed that the main area of interaction was the map, and we were losing a lot of space to the wood-grain texture. We designed a slide-away tray that is accessible from the icon menu, hiding the brand favorites tray when not in use, creating a more immersive experience. We’ve also pulled in search results from Yelp, so if  users are looking for something that’s not featured in one of the sidebar categories, they’re able to search the map for anything they can dream up. DoubleTree iOS7 iPad App Design

The Styling

The wood grain was warm and inviting, but took up a large amount of real estate on the main map screen. During the redesign we were able to take advantage of the advances made in increased interface familiarity, removing the wood texture and skeuomorphic buttons in favor of a minimally-styled side and top bar. We also added a plethora of animations to the app to enhance the character of an otherwise lightly designed interface. To display the favorites bar, the user swipes their finger to the right on an icon. You can check out all of these new features by downloading the app today.

DoubleTree iOS7 Updates from iStrategyLabs on Vimeo.

The Size

One of our client’s goals was to reduce the size of the app, which in its original version featured images of each of their  370+ hotels in 32 countries across 6 continents, leading to a gigantic app. Size is a huge factor in apps, because large apps lead to longer download times. We removed the images from the app, reducing the size from about 300MB to just 30mb. DoubleTree iOS7 iPad App Design

This decision mainly impacted the hotel selector screen, because the first version of the app each hotel was shown with its own photos. The photos looked beautiful, but were not a necessity for the app experience. In the new version, the hotel selector screen has been reworked to allow the content to come to the forefront. DoubleTree iOS7 iPad App Design

The Hotel Selector

The hotel selector screen has been changed dramatically as well. On scroll, the title bar fades away and the hotel tiles take over the screen, featuring a slight color fade as the rows of hotels slide by. Our hypothesis was that users are not interested in photos of the hotel. Presumably, users are already staying at a DoubleTree hotel; when they’re scrolling, they’re looking for a specific hotel location. Listing the hotels alphabetically and offering a pleasing color fade allows the user to scroll at will and without distraction. The app also offers a search based on text or current location. DoubleTree iOS7 iPad App Design

Testing & Iteration

In early rounds of testing, we realized that users were confused about the goal of the app, especially in relation to the DoubleTree brand. We recognized this as a huge problem, and came up with an elegant two-fold solution to teach our users about the benefits of the app. When the app is launched, the user is met with a welcome screen, a short description of the app, and a large button that entices them to “explore” the map. When they enter the map view for the first time, they’ll see a brief three part tutorial, complete with an animation. The tutorial explains how to use the app and then drops off the screen. In our second round of testing, we saw that although the tutorial worked well for some, about half the users swiped through the tutorial without reading it and were still confused. So, we added one last cue to help our eager users — a one-time interface hint that pops up to guide the user through their first interactions in the app. They can tap the bubble to be walked through the action, or perform the action on their own to get rid of the cue. Our last round of testing was highly successful. All of our cues did the trick, and users were having a great time navigating around the app. The animations were a big hit, delivering a surprise dose of fun for people who might otherwise not be so happy (if they’re on a business trip).

Check out the app in the App Store today!

DoubleTree Home and Away App

The Facebook “Mentions Box” Goes Live At The Emmy Awards


Facebook, already the biggest brand in social media, was looking for a better way to bridge the gap between celebrities and fans, specifically at live events. Behold: The Facebook “Mentions Box” which just debuted at the 2014 Emmy’s — allowing celebrities to “shake” the device to surface a fan question (pulling directly from the event’s Facebook Page) and immediately record a video response back.

At the Emmys, the Mentions Box was used by everyone from Jimmy Fallon, to Matthew McConaughey, to Ty Burrell and Jason Biggs — to stars from Game of Thrones, Orange Is the New Black, and HBO’s Silicon Valley (see below!).

Matthew McConaughey - Mentions Box

After many discussions, brainstorms, and rapid prototyping efforts focused on how to best represent the Facebook platform in physical space, we landed on a simple concept drawing inspiration from mediums people already know and enjoy (landing on something between a Magic 8-Ball in function and an Etch-A-Sketch in form).


Ultimately, we settled on a tablet encased in a polycarbonate form factor, with a luxury car finish.



We started the process by concepting potential types of physical devices that talent would be able to easily (and quickly) interact with — using cardboard and simple materials to play around with various form factors.


The admin console (primarily a question moderation tool) sources questions from specific Facebook posts and then pushes approved questions straight to The Facebook Mentions Box.


Leading up to the Emmy’s, The Mentions Box was featured on Access Hollywood by hosts Billy Bush and Shaun Robinson:

Screen Shot 2014-08-21 at 5.51.08 PM



More pics of the Mentions Box in action, straight from the Access Hollywood Facebook Page!






On September 5th, Facebook teamed up with Stand Up To Cancer to utilize the Mentions Box to connect celebrities with donors supporting the live charity event.

su2c_mentions 2

su2c_mentions 1


What It’s Like To Be A Teacher At General Assembly


Teaching is something iStratetyLabs fully supports — nay encourages — for its employees both internally and within the community. We have a multitude of opportunities to both teach and learn from our coworkers. We have “Battle Schools”, which allow team members to teach  a topic they’re passionate about to follow ISL-ers. It’s a great way to learn and a great way to practice public speaking. There are other awesome things we do, but they’re G-14 classified! You’ll have to join the team in order to have access to such knowledge.

A few months ago, Samia Khan wrote the post “What’s it like to be a student at General Assembly”. At the time I was just getting started with my Front End Web Development course. I thought I’d give an alternate account to round out the experience so that potential students as well as instructors have an idea of what to expect from a GA class.

I began a GA 10-week front end web development (FEWD) course back in May. Other than the standard instructor preparation I wasn’t sure what to expect. What I walked away with was much more than a teaching experience. Sure, I created lesson plans, spoke in front of people on a regular basis, critiqued assignments, and all the other responsibilities that come along with being an instructor of any kind, but that’s just the beginning.

Having to teach people what you take for granted every day is an incredibly enlightening experience. I found that concepts/ideas that I thought to be simple and straightforward are actually nuanced and fairly complicated. On the other hand, concepts that I thought I understood well, were brought forth and I was forced to reevaluate my own understanding– this is a humbling experience to say the least.


On the first day of class expectations are set. Students are informed that the course is not designed to create expert front end developers, rather, to provide building blocks and tools that can be used to enrich an individual’s knowledge and experience — “Give a man a fish…” you know the rest. One thing I had to reiterate to my students was that professional developers (and myself by extension) don’t know everything and that asking for help and googling for answers was a big part of the job. I even touched briefly on the myth of the genius programmer.

By far the most rewarding portion of the class was final presentations. There were students who, prior to the course, had never really touched HTML/CSS/JavaScript and whom by the end, presented interactive websites and in some cases the beginnings of web applications! Creating a foundation of working knowledge that empowers students to grow individually is a responsibility I do not take lightly. It was an honor and privilege to take part in that process. Kudos to my TAs Michael Dick and Rami Chowdhury — you guys rock.

Investigating Browersify: The Development Tool That Lets You Write Modular Code For Use In Browser

Screen Shot 2014-06-16 at 3.55.31 PM

A while back, the engineering team adopted AngularJS as the front end JavaScript framework of choice. Not every project we work on requires a framework like Angular, but when we need one — it’s where we go. This post, however isn’t about Angular itself — or why we chose it — it’s about how we manage the multitude of files associated with using such a framework. Every FED (front end developer) knows that simply using a separate script tag to include all files does not scale very well. Most take to simple concatenation, allowing them to use one js file for their app code. This also works, but can get out of hand and also forces you to either pollute the window namespace or create your own namespace in order to access various components outside of their respective files.

Our development team decided that a more modular approach was appropriate to solving this problem, so we turned to Browserify. Browserify brings node.js style require statements to the browser. We can write code that looks like this:

// mainCtrl.js

   module.exports = [‘$scope’, function($scope){
      $scope.theThings = [ ‘thing1’, ‘thing2’, ‘thing3’ ];


// fooCtrl.js

   module.exports = [‘$scope’, function($scope){
      $ = ‘bar’;


// controllers.js

   var controllers = {
      MainCtrl: require( ‘./mainCtrl’),
      FooCtrl: require(‘./fooCtrl’)

   // a utility module to dynamically attach modules
   require( './moduleUtils' )
    .forModule( myApp.controllers' )
    .setType( 'controller' )
    .injectAll( controllers );

// finally in app.js

   angular.module(‘myApp’, [
      // …. and so on


Using a build system, we point Browserify to app.js and that’s it! Another benefit to using browserify is that many node modules are automatically compatible and can be used in the browser. At the end of the day, Browserify is the best tool for allowing us to handle code in different files and keeping it all modular.