Make JavaScript Easier to Swallow with Gulp

By Sterling Heibeck On October 28, 2014

Make JavaScript Easier to Swallow with Gulp

JavaScript frameworks are really in vogue in recent years. With the amount of computing power in the average home, developers have been trying to get processing off of servers and back into the hands of the all those untapped cycles.

But, this comes with another price: Mainly SEO issues and “compiling” your JavaScript for your production environment. I’m going to focus on the latter issue.

Yes, I know that you don’t really compile JavaScript. But, if you’re properly modularizing your code to make it easier to manage, develop, and test, you have a lot of files. Browsers are notoriously unforgiving when it comes to loading that many files without making your user wait … and users hate to wait.

By way of example, here’s a quick view of the difference between many small files and one bigger file (note that browsers have a limit on the number of concurrent downloads from the same domain.)  I’ll let you do the math…

Gulp

Enter Gulp. Built on NodeJS, Gulp is a build-system that lets you streamline your code before you deploy it. The best part is, if you know JavaScript, then you know how to configure Gulp.

After installing Gulp you’ll create a Gulpfile.js and use this file to tell Gulp how to “compile” your code and what the final output should look like. In this example, the goal is a single JS file that has everything you need to use in your website.

Let’s take a look at the Gulpfile.js and break it down.

Initialize

var gulp = require('gulp'),
    gutil = require('gulp-util'),
    uglify = require('gulp-uglify'),
    browserify = require('gulp-browserify'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    rimraf = require('gulp-rimraf');

var basedir = "./CMS/_framework";
var distdir = "./CMS/_framework/dist";
var tempdir = "./CMS/_framework/tmp";

This section sets up our environment with the types of actions you want to perform. Gulp has tons of plugins for everything you need. You’ll be combining files, minifying files, and renaming them as you’ll see below.

Bundle

// Browserify task
gulp.task('browserify', function() {
  // Single point of entry (make sure not to src ALL your files, browserify will figure it out for you)
    return gulp.src([basedir + '/js/app.js'], { read: false })
      .pipe(browserify({
          insertGlobals: true,
          debug: true,
          shim: {
              'jquery': { path: './CMS/_framework/js/lib/jquery-1.10.2.min.js', exports: '$' },
              'angular': { path: './CMS/_framework/js/lib/angular.js', exports: 'angular' },             
              'angularRoute': { path: './CMS/_framework/js/lib/angular-route.min.js', exports: 'ngRoute', depends: { angular: 'angular' } },
              'angularResource': { path: './CMS/_framework/js/lib/angular-resource.min.js', exports: 'ngResource', depends: { angular: 'angular' } },
              'angularCookies': { path: './CMS/_framework/js/lib/angular-cookies.min.js', exports: 'ngCookies', depends: { angular: 'angular' } },
              'angularSanitize': { path: './CMS/_framework/js/lib/angular-sanitize.min.js', exports: 'ngSanitize', depends: { angular: 'angular' } },
              'bootstrap': { path: './CMS/_framework/js/lib/bootstrap.min.js', exports: 'bootstrap', depends: { angular: 'angular', jquery: 'jquery' } },
              'ui.bootstrap': { path: './CMS/_framework/js/lib/ui-bootstrap-tpls-0.11.0.min.js', exports: 'uiBootstrap', depends: { angular: 'angular' } 
          }
      }))
      // Bundle to a single file
      .pipe(concat('app.js'))
      .pipe(gulp.dest(tempdir))
});

Browserify is a framework that allows you to build modules and then specify dependencies so that modules know what other modules they need to operate. This is similar to RequireJS, but a little easier to wrap your head around.

There is a handy Gulp plugin that will handle Browserify modules. I’m not going to go too deep into this in this post, but this Gulp task will search through all your JavaScript modules and bundle everything up into a single file. You can see that it supports frameworks that aren’t inherently compatible by allowing you to shim them, which is a fancy way to say that this file depends on some other file.

First, name the task:

            gulp.task('browserify', function()

You can see here that our project uses jQuery, Angular, and Bootstrap. The first part of the task shows that you want Gulp to look at the app.js file to start with.

            gulp.src([basedir + '/js/app.js']

Next, take all the dependent files (defined by Browserify) and the app.js file and smash them together into one file, which will still be called app.js:

            .pipe(concat('app.js'))

Then put the output into a specific destination directory:

            .pipe(gulp.dest(tempdir))

This is the meat and potatoes of Gulp. You pass streams down the chain, modifying them with tasks as you go.

Combine and Minify

//combine files into our final output
gulp.task('combine', ['browserify'], function (cb) {   
    return gulp.src([tempdir + '/*.js'])    
    .pipe(concat('bundle.js'))
    .pipe(gulp.dest(distdir + '/js'))
    .pipe(uglify({ mangle: false }))
    .pipe(rename({ suffix: '.min' }))
    .pipe(gulp.dest(distdir + '/js'));    
});

Now that there is a single bundled file in the destination directory, let’s manipulate that file into the final form — a single, minified file.

Name the task, then tell Gulp that a task is dependent on another task; the browserify task will be run before the combine task is run.

            gulp.task('combine', ['browserify']

Start in our temporary directory where all of the files that have been manipulated have been put:

            gulp.src([tempdir + '/*.js'])

Take all the files found here and make one big file and name it bundle.js:

            .pipe(concat('bundle.js'))

Next, copy this file to our distribution folder (this is the location where the website include files will point to):

            .pipe(gulp.dest(distdir + '/js'))

Lastly, make a minified version of the file and make sure it gets put in the distribution directory as well:

            .pipe(uglify({ mangle: false }))

            .pipe(rename({ suffix: '.min' }))

            .pipe(gulp.dest(distdir + '/js'));

Now there are two files in the distribution directory: bundle.js and bundle.min.js.

Run and Watch

gulp.task('default', ['combine'], function () {
  // Watch our scripts
  gulp.watch([basedir+'/js/**/*.js'],[  
    'combine'
  ]);
});

I haven’t mentioned it up to this point, but to run Gulp you open a command window from the location of the Gulpfile.js and you run “gulp,” and it’ll process the Gulp file once. But, you don’t want to constantly have to go to your command window and rerun the command every time you make a change to your JavaScript. Gulp has a handy watch command that will watch folder then execute specific Gulp tasks whenever files in those folder change.

Gulp always needs a default task so it knows where to start. When Gulp runs it will start by running the combine task.

            gulp.task('default', ['combine'], function () {

Next, tell Gulp to watch any files in the base directory/js folder, and if any of them change run the combine task.

            gulp.watch([basedir+'/js/**/*.js'],[ 

                'combine'

              ]);

The End

There’s a lot to digest here, but here’s a few things of note. Most modern browsers support source maps (.map) files, so even though you end up with one big JavaScript file, when you want to debug your code the map files will let you debug against your original, modularized, JavaScript files.

Get used to the command line and NodeJS (come on, you know it makes you feel like a hacker!).

Here’s the final Gulpfile.js so you can see it all together:

//Install dependencies command
//npm install --save-dev gulp gulp-util gulp-uglify gulp-browserify gulp-rename gulp-concat gulp-minify-html gulp-rimraf

var gulp = require('gulp'),
    gutil = require('gulp-util'),
    uglify = require('gulp-uglify'),
    browserify = require('gulp-browserify'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    rimraf = require('gulp-rimraf');

var basedir = "./CMS/_framework";
var distdir = "./CMS/_framework/dist";
var tempdir = "./CMS/_framework/tmp";

// Browserify task
gulp.task('browserify', function() {
  // Single point of entry (make sure not to src ALL your files, browserify will figure it out for you)
    return gulp.src([basedir + '/js/app.js'], { read: false })
      .pipe(browserify({
          insertGlobals: true,
          debug: true,
          shim: {
              'jquery': { path: './CMS/_framework/js/lib/jquery-1.10.2.min.js', exports: '$' },
              'angular': { path: './CMS/_framework/js/lib/angular.js', exports: 'angular' },              
              'angularRoute': { path: './CMS/_framework/js/lib/angular-route.min.js', exports: 'ngRoute', depends: { angular: 'angular' } },
              'angularResource': { path: './CMS/_framework/js/lib/angular-resource.min.js', exports: 'ngResource', depends: { angular: 'angular' } },
              'angularCookies': { path: './CMS/_framework/js/lib/angular-cookies.min.js', exports: 'ngCookies', depends: { angular: 'angular' } },
              'angularSanitize': { path: './CMS/_framework/js/lib/angular-sanitize.min.js', exports: 'ngSanitize', depends: { angular: 'angular' } },
              'bootstrap': { path: './CMS/_framework/js/lib/bootstrap.min.js', exports: 'bootstrap', depends: { angular: 'angular', jquery: 'jquery' } },              
              'ui.bootstrap': { path: './CMS/_framework/js/lib/ui-bootstrap-tpls-0.11.0.min.js', exports: 'uiBootstrap', depends: { angular: 'angular' } }                          
          }
      }))
      // Bundle to a single file
      .pipe(concat('app.js'))
      .pipe(gulp.dest(tempdir))
});

//combine files into our final output
gulp.task('combine', ['browserify'], function (cb) {   
    return gulp.src([tempdir + '/*.js'])    
    .pipe(concat('bundle.js'))
    .pipe(gulp.dest(distdir + '/js'))
    .pipe(uglify({ mangle: false }))
    .pipe(rename({ suffix: '.min' }))
    .pipe(gulp.dest(distdir + '/js'));    
});

gulp.task('default', ['lint', 'combine'], function () {
  // Watch our scripts
  gulp.watch([basedir+'/js/**/*.js'],[  
    'combine'
  ]);
});
Click here to read more Tutorials posts
Start a Project with Us
Photo of the author, Sterling Heibeck

About the author

Sterling Heibeck has been slinging code since 1995. His role at BizStream isn't only about raising the median age of the group, but he brings a multitude of talent to the table when it comes to architecture, design, development, and all around awesomeness. To Sterling, development is 95% problem solving and 5% Google. His answer is always "yes" when asked the question "Can we do [insert anything here]?" He's also proud dad of Kaiyah, Bennet and Morgan.

View other posts by Sterling

Subscribe to Updates

Stay up to date on what BizStream is doing and keep in the loop on the latest with Kentico.