When I build a Single Page Application (SPA), I need to somehow concatenate, minify and compress all my JS, CSS and Image files for production while keeping them as they oringinally are for development. Also, I’d like my app to continually redeploy on each change I do while I’m coding.

Before Grunt existed, I thought that doing this in an easy and manteinable way was impossible. That’s why I thought Grunt was the holly grail for build tools. But then, Gulp appeared. Now I could have all the pipeline of transformations that happens to certain type of files in only one place. It was so much easier to understand than Grunt. And then again, now I thought Gulp was the holly grail.

But what if none of this complex build tools are needed and we can just do the same with less code making it more manteinable and easier to use? This is what we’re going to be talking about in this post!

Let’s start from the begining. A few months back, I built a seed project for creating Single Page Apps (SPAs) without any framework at all. For this project, I created what I thought was a simple Gulpfile.js.

// Uses browserify node.js package
function bundle(browserified, env) {
  browserified
    .bundle()
    .pipe(source('build.js'))
    .pipe(gulp.dest('./build/'));
}


function browserifyTask(env) {
  return function() {
    var file = path.resolve('index.js');
    var browserified = browserify(watchify.args);

    if (env === 'prod') {
      browserified.transform({global: true}, 'uglifyify');
    }
    if (env === 'dev') {
       browserified = watchify(browserified);
       browserified.on('update', function(){
        bundle(browserified, env);
      });
    }

    browserified.transform(stringify(['.html']));
    bundle(browserified.add(file), env);
  }
}

// Uses Rework node.js package
gulp.task('reworkcss', function() {
  var file = path.resolve('index.css');
  var source = path.relative(__dirname, file);
  var output = fs.createWriteStream('build/build.css');
  var contents = fs.readFileSync(file, {encoding: 'utf8'});

  // Initialize and pluginize `rework`
  var css = rework(contents);
  css.use(npmRework());

  // write result
  output.write(css.toString())
  output.end();
});

// Calls browserify function
gulp.task('browserify-dev', browserifyTask('dev'));

// Uses gulp-serve to serve the directory
gulp.task('serve', serve(__dirname));

// Calls browserify, rework and then serves
gulp.task('watch', ['browserify-dev', 'reworkcss', 'serve']);

Basically, it had the watch task which took care of calling browserify and apply the needed transformations as well as using rework for CSS requires and finally serving the build/ folder.

Gulp in its core is really simple!. It just provides a way of creating a stream from several source files and then pipe it through different transformations. Usually, those transformations are either just regular node.js packages like browserify, rework and watchify or simple wrappers that makes regular node.js packages work with stream like gulp-serve which is just a wrapper on top of serve.

I was really happy about what I had implemented since my Gulpfile was just 82 lines of code and the code was really clear. I didn’t have to be in the lookout for all of those baseDir and dest folders to understand what was going on like I used to do with Grunt.

This week, I decided to update that seed project to make it work with React for templating instead of Ripple. While doing so, I decided to try out using the package.json to accomplish the same tasks I was doing with Gulp as well as a few more.

Side note: I’ll be talking about the seed project in an upcoming post.

I want to focus now on the scripts section of the package.json. Notice first that it’s just 9 (yes 9!) lines of code! And I thought that 82 LOC from Gulp were awesome!

"scripts": {
  "start": "npm run build-js && npm run build-css && serve .",
  "watch": "npm run watch-js & npm run watch-css & serve .",
  
  "build-css": "rework-npm index.css | cleancss -o build/build.css",
  "build-js": "browserify --extension=.jsx --extension=.js index.js | uglifyjs > build/build.js",
  
  "watch-js": "watchify --extension=.jsx --extension=.js index.js -o build/build.js --debug --verbose",
  "watch-css": "nodemon -e css --ignore build/build.css --exec 'rework-npm index.css -o build/build.css'"
}

Let’s analyze what we’re doing there! The equivalent of the previous watch function is this line which basically just calls simultaneously (notice it’s doing & and not &&) 2 other tasks from the package.json and the serve function which is installed as a dev dependency.

Most node.js utilities we use with Gulp usually provide a CLI interface as well. serve is one of those cases. When we install it as a dev dependency, the CLI executable is put into node_modules/bin which can be just run by calling it from the package.json’s’ scripts (like we’re doing in the watch task).

Now, let’s checkout the watch-js and watch-css tasks. They both just call the cli for watchify, nodemon and rework which were installed as dev dependencies as well.

Finally, let’s take a look at the build-js task. We can see that it’s using a cool feature that most node.js CLIs provide: If you don’t set an output file, you can just pipe the response to another command, just like you regularly do with any regular UNIX command. In this case, we’re first using browserify to handle all the requires and output one concatenated file and then pass its output to uglify to minify this. It’s basically the same as what we’re doing with Gulp when we call the .pipe() method, but using UNIX command line features instead.

What does this all mean?

Suming up, if we take a look at our package.json, we’re just calling CLI interfaces for node.js packages which we installed as dev dependencies and then piping the content of calling one of them to another. We accomplished the same we did with Gulp but with less, much simpler and easier to mantain code.

I’m not saying Gulp is never going to be needed. But for most cases that we use it (which are quite simple), we can just use the package.json or even a Makefile.

What do you guys think about it? Are you willing to try this aproach out?