Fig. 10 A Guide for Developers

React.js From Scratch: Part 3 — Task Runners

A modern web development workflow needs a new set of tools. Let's take a quick detour to talk about task runners.

Written by Andy Rossi February 04, 2016

It’s time for Part 3!

Part 1 can be found here, and Part 2 is over here.


Building a web page in 2016 is a bit more difficult than it used to be. Back in the day (like, in 1857 or something), you could make a web page using normal HTML, some images in a folder, and some CSS document somewhere.

In today’s world of modern web application development, the HTML is typically an entry point for a decent amount of scripting. Taking resources such as CSS files, JS files, and images, then packaging them into a little box full of static assets for deployment is where a task runner comes into play.

What does a task runner do? Whatever you tell it to. But typically, common workflows include tasks such as (but not limited to) minification, transpilation, CSS auto-prefixing, linting (syntax and error checking), image optimizing, template compiling, throwing errors in your face, deploying, and even running some kind of local development server.

Task runners can do a lot of things, eh?

Grunt and Gulp

Let’s discuss a couple of popular task runners: Grunt and Gulp. These two ‘runners are relatively simple when it comes to setup, but currently (as of this writing) operate tasks using two different workflows. A Grunt task runs independent of any other task. That’s because of how Grunt works with files. Most tasks require an input and output. For example, let’s look at a sample Sass compilation task:

// Gruntfile.js
module.exports = function(grunt) {
  grunt.initConfig({
    sass: {
      dist: {
        files: {
          'main.css': 'main.scss'
        }
      }
    },
    someOtherTask: { ... } // another task independent of any other
  });

  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('some-other-task');
  grunt.registerTask('default', ['sass']);
}

There are a couple things going on here. First, a task called sass is being configured. It’s taking in a main.scss file and compiling it out to main.css in the same directory. Input, output. At the bottom of the file, there’s a load function to add the grunt-contrib-sass module, and a register method to tell Grunt which tasks are available to run. Super basic. Running the grunt terminal command in the same directory as your project will run anything listed under the default task. In this case, the sass task runs.

The important part here is the input/output methodology. Grunt takes the input file, writes a temporary duplicate file, runs the task on it, then writes an output file to the name/location you specified in the configuration.

Any additional task added runs the same way. Input-copy-transform-output.

Eventually, a default Grunt task registration could look like this:

grunt.registerTask('default', ['sass','autoprefix','concat','serve']);

What’s happening here is that the sass task runs first: read input, write temporary duplicate, perform task, write output. Then, the next task in line is autoprefix. It follows the same procedure. After the autoprefix task completes, concat runs. Then a development server instance may start up. If all these reads and writes are happening on a single file that is opened and closed multiple times, then you can assume that this may be a little slower than it should be. It’s not that it doesn’t work well (Grunt is fantastic), but it could be more efficient.

Streams

Gulp takes care of these issues using a method called streaming. A file stream will take an input file, perform a task on it, then hand it off to the next task. Once all the tasks are done, Gulp writes the file directly to the filesystem without some intermediate temporary file. This is more efficient and extremely fast. The tradeoff is that a task may be a little bit more complex to write.

Grunt is extremely simple. Gulp takes some getting used to. With Grunt, you are writing configuration for multiple tasks. With Gulp, you are actually coding your task workflow from scratch.

Here’s a Sass Gulp task for comparison:

var gulp = require('gulp'); // require() is a Node.js module loading syntax
var sass = require('gulp-sass');
var sometask = require('some-task'); // just a sample task to be used below

gulp.task('sass', function() {
  gulp.src('main.scss')
    .pipe(sass()) // the sass task runs on the source file
    .pipe(sometask()) // we can pass the same file along to any additional task
    .pipe(gulp.dest('./css')); // all done. spit the new file out here
});

gulp.task('default', ['sass']);

The pipe() function is used to pass the src input file along the chain. It goes from input (in this case any file with an .scss extension in the ./sass directory), to the sass() function, which then passes the file to the sometask() example task. Then, the altered file is passed to the dest (destination) folder and outputted.

Whew.

See how instead of building individual tasks, a task can be scripted to do more than one thing? That’s the noticeable difference between Grunt and Gulp.

Either of these two task runners is a great build tool for most web projects. However, there’s a new wrinkle.

Webpack

I can’t talk about task runners and build tools without bringing up Webpack. What is Webpack? It’s difficult to explain what it is without taking a step back. To start, I’ll say that Webpack is a module bundler. “What’s a module?” Glad you asked!

Modules, Babel, and Browserify

Modules are a new feature of JavaScript ES6. It enables a developer to write reusable functions and variables into a separate file, and import it for easy use in another file. A simple module looks like this:

// math.js
let math = {
  multiply: function(x,y) {
    return x * y;
  }
}

module.exports = math;

In a separate math.js file, we wrote a function multiply() that returns the product of two numbers x and y. Now, to use this function anywhere else, we need to import it.

// main.js
// import the external function here
import math from "./math";

// use the function
let multiply = math.multiply(3,4);

console.log(multiply); // spits out 12, yo

That’s a basic ES6 module. The only problem here is that most browsers do not support this syntax out of the box. To generate JavaScript that the browser can read, some additional tools and plugins need to be introduced: Babel and Browserify. Babel is a JavaScript compiler that can convert (transpile) the new ES6 syntax down to ubiquitous ES5. This means that you can write using the latest ES6 methodologies without worrying about browser support. If ES5 is the end result, writing in ES6 should not be a compatibility concern. It’s safe to use new ES6 features today!

For example, in ES6, a developer can use template strings to write out a dynamic sentence.

// ES6
let date = new Date();
let greeting = `Today's date is ${date}`;

(Remember template strings from Part 2? Huh? Huh!?)

Also, note the let keyword above. This is an ES6 feature too. Here’s an explanation from MDN.

let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.

In other words, for example, if a let variable is defined inside a function, it cannot be used outside of it. An error will be thrown. Again, many browsers do not yet support let natively.

Babel’s job is to convert this funky new code to the browser-supported version we all know and love. let variables are converted to var as well.

// Babel's output
var date = new Date();
var greeting = "Today's date is " + date;

However, even with ES6 to ES5 transpilation, some browsers will not understand the module syntax. Browserify fixes this issue by collecting all the JS code in your project and bundling it together in one file. Interestingly enough, a Node.js module named babelify combines these two steps into one.

Even more interesting, Babel can use a React preset to handle JSX transpilation. We were talking about JSX last time, and now we have a way to actually work with this code in a way that a browser understands. Woo!

Okay, now back to Webpack for a second.

Babel and Browserify (or just Babelify) need to be a part of your tooling. These can be added to Grunt or Gulp, or you can completely ignore the past couple paragraphs and start using Webpack.

Webpack essentially replaces the traditional task runner. It takes care of all of the module loading as well as static asset compilation out of the box. This includes CSS compilation, JS compilation, linting, image encoding, and a bunch of other super cool things. Webpack is a one-size-fits-all solution since it offers most common task runner use cases up front. Grunt and Gulp are of the add-it-as-you-need-it mindset.

Unfortunately, Webpack has a steep learning curve, and their own documentation is rather dense. Because of this (and the fact that this series is a guide for beginners), a dedicated article will be necessary to better explain this tool. For this guide, we will stick with Gulp and Babelify, which satisfies the requirement for task running, transpilation, and module loading.

But there’s more. Lot’s more.

Next time...

Coming up super soon, we will get into building your basic React development environment (YAY ACTUAL CODE!). This starter kit of sorts will include Gulp tasks that we will script ourselves, a list of super helpful plugins that can be added to your favorite text editor (mine’s Atom right now), and some browser plugins to assist with debugging.

We’re so close!

Pssst. Continue onward!


Further Reading