During Socrates Conference 2015 we decided that it’s the right time to jump in ES6 to developer a green field project that our customer is starting. Given that ES6 is already the stable and latest version of JavaScript, it does not make sense to start a new project with ES5, an already old version of the language. With the kind of functional style that we use when coding in JavaScript, the changes are not too big anyway as we are not going to use classes anyway. But we can leverage from block scope with “let” and “const” avoiding the use of “var” from now on. Configuring the tools has taken more time than I thought as the tooling and the ecosystem is changing really fast. You read a recipe from a blog post which is 6 months old and it turns out that most of the stuff described is no longer working. Some npm packages don’t work anymore or the behavior at some point is very different from a previous version… apparently things are even more obscure on Windows which is the platform we have to use for this project.

As an example, I installed karma via npm the latest version. But when running gulp from the command line it stopped working with no error message at all, just a line break and back to the prompt. I commented all the lines in the gulpfile.js and then uncommented lines one by one executing gulp each time to discover that “require(‘karma’)” was the reason. So I got into the node repl and type this myself:

var k = require(‘karma’)

The result was the same, node repl exited silently getting me back to the command line prompt. I couldn’t find a single way to catch the error although I tried try-catch, signal capturing, domains… and none of that worked. Then I started downgrading the version of the karma package until it worked for me. Version 0.13.3 works but 0.13.4 doesn’t. It must be a very specific problem on my machine but I couldn’t find any other solution. Eventually we are not using karma for now, we are using jasmine stand alone version and mocha.

This is the simplest gulpfile I was able to get working:

var gulp = require('gulp');
var sourcemaps = require("gulp-sourcemaps");
var mainBowerFiles = require('main-bower-files');
var browserify = require('browserify');
var vinylSource = require('vinyl-source-stream');
var glob = require('glob');
var vinylBuffer = require('vinyl-buffer');
var babel = require('babelify');

var globalNamespace = 'mynamespace';
var launcher = './Scripts/src/main.js';
var sourceFiles = 'Scripts/src/*.js';
var specFiles = 'Scripts/spec/*.js';
var libFiles = 'Scripts/dist/vendor/*.js';
var distFolder = "Scripts/dist";
var allFiles = [libFiles, sourceFiles, specFiles];

gulp.task('package-src', function () {
    var filenames = glob.sync(sourceFiles); // generate array of filenames
    return browserify({
        entries: filenames,
        debug: true
    })
    .require(launcher, {expose: globalNamespace}) // publish 
    .on('error', function (err) { 
                  console.error(err); this.emit('end'); 
     })
    .transform(babelify)
    .bundle()
    .pipe(vinylSource('all.js')) // generated output file
    .pipe(vinylBuffer())         // required for sourcemaps
    .pipe(sourcemaps.init())
    .pipe(sourcemaps.write("."))
    .pipe(gulp.dest(distFolder));
});

gulp.task('package-tests', function (done) {
    var filenames = glob.sync(specFiles);
    return browserify({
        entries: filenames,
        debug: true
    })
    .on('error', function (err) { 
                 console.error(err); 
                 this.emit('end'); 
     })
    .transform(babelify)
    .bundle()
    .pipe(vinylSource('specs.js'))
    .pipe(vinylBuffer())
    .pipe(sourcemaps.init())
    .pipe(sourcemaps.write("."))
    .pipe(gulp.dest(distFolder));
});

gulp.task('package-vendor', function () {
    return gulp.src(mainBowerFiles({filter: '**/*.js'}))
        .pipe(gulp.dest(distFolder + '/vendor/'));
});

gulp.task('default', [
            'package-src', 
            'package-vendor', 
            'package-tests'
]);

The generated package is “all.js” which I include in the html page. The application’s entry point is on main.js with exposes a function called startApp.

App starts up at the bottom of the html page:

main.js:

import viewModel from "./viewModel";
import restClient from "./restClient";

export function startApp() {
    let rc = restClient();
    let vm = viewModel(rc);
    ko.applyBindings(vm);
};

In order to run the tests the most simple choice was Jasmine stand alone, including the generated “specs.js” file in the SpecRunner.html page. As the tests include the production code, the generated file “specs.js” already include all the production code.

tests:

import restClient from "../src/restClient";
import viewModel from "../src/viewModel";

describe("make an vehicle order", ()=> {
   ...

The next step was to include “watchify” in order to rebundle everytime a file is saved.

gulpfile.js:

var gulp = require('gulp');
var sourcemaps = require("gulp-sourcemaps");
var mainBowerFiles = require('main-bower-files');
var browserify = require('browserify');
var vinylSource = require('vinyl-source-stream');
var glob = require('glob');
var vinylBuffer = require('vinyl-buffer');
var watchify = require('watchify');
var babelify = require('babelify');

var launcher = './Scripts/src/main.js';
var globalNamespace = 'mynamespace';
var sourceFiles = 'Scripts/src/**/*.js';
var sourceBundle = 'all.js';
var specFiles = 'Scripts/spec/**/*.js';
var specBundle = 'specs.js';
var libFiles = 'Scripts/dist/vendor/*.js';
var distFolder = 'Scripts/dist';
var allFiles = [libFiles, sourceFiles, specFiles];

gulp.task('package-src-dev', function() {
    bundleWatchify(sourceFiles, sourceBundle);
});

gulp.task('package-src', function() {
    bundle(sourceFiles, sourceBundle);
});

gulp.task('test-dev', function () {
    bundleWatchify(specFiles, specBundle);
});

gulp.task('package-vendor', function () {
    return gulp.src(mainBowerFiles({filter: '**/*.js'}))
        .pipe(gulp.dest(distFolder + '/vendor/'));
});

gulp.task('default', ['package-src', 'package-vendor', 'test-dev']);
gulp.task('package-dist', ['package-src', 'package-vendor']);

function bundleWatchify(sources, output) {
    var watchified = watchify(doBrowserify(sources))
                    .on('update', function (filenames) {
                        console.log('rebuilding -> ', filenames[0]);
                        rebundle(watchified, output);
                    });
    return rebundle(watchified, output);
}


function bundle(sources, output) {
    return rebundle(doBrowserify(sources), output);
}

function doBrowserify(sources) {
    var filenames = glob.sync(sources);
    var browserified = browserify({
        entries: filenames,
        debug: true
    });
    return browserified;
}

function rebundle(b, output) {
    return b
        .require(launcher, { expose: globalNamespace })
        .on('error', function (err) {
            console.error(err);
            this.emit('end');
        })
        .transform(babelify)
        .bundle()
        .pipe(vinylSource(output))
        .pipe(vinylBuffer()) // required for sourcemaps
        .pipe(sourcemaps.init())
        .pipe(sourcemaps.write("."))
        .pipe(gulp.dest(distFolder));
}

This post has been written on September 14th 2015, if you try to use any of the snippets posted a few months later they probably won’t work for you. Versions used:

package.json:

{
  "name": "VehicleOrders",
  "version": "1.0.0",
  "description": "",
  "main": "gulpfile.js",
  "devDependencies": {
    "babel": "5.8.23",
    "babelify": "6.3.0",
    "browserify": "11.0.1",
    "glob": "5.0.14",
    "gulp": "3.9.0",
    "gulp-babel": "5.2.1",
    "gulp-concat": "2.4.1",
    "gulp-sourcemaps": "1.5.2",
    "main-bower-files": "2.8.0",
    "vinyl-buffer": "1.0.0",
    "vinyl-source-stream": "1.1.0",
    "vinyl-transform": "1.0.0",
    "watchify": "3.4.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "AIDA",
  "license": "ISC"
}