Learning React: Extending our React development environment

Part two of a continuing series of adventures into the world of React…

Last time on “Learning React”…

So in my last post, we got as far as creating what is probably the absolute bare minimum development environment, where we can lash together some basic React code and see it in action.

But start to play with your example React code, and you’ll see a handful of immediate problems:

  • After making any changes, you have to run the default Gulp task in order to re-compile your code into a runnable form.
  • If you haven’t got the page already open in a web browser, you’ll have to locate it in your operating system’s file explorer and open it manually.
  • Once you’ve got the page running in a browser, you still have to reload the page manually after making any code changes.

All of these things are going to get really annoying if we’re going to spend any amount of time working with React, so let’s see what we can do about fixing them.

Automatic recompilation

We can use Gulp’s built-in “watch” command to detect when files have changed, and trigger a re-build.

Let’s change our “default” task and rename it “build”, which is more descriptive. Then we add a “watch” task as follows:

gulp.task('watch', function () {
gulp.watch('./source/**/*.js', ['build']);
});

This tells Gulp to observe any file with a “.js” suffix anywhere in the /source folder, or any of its sub-folders, and if any of these change, trigger the “build” task.

Now from the command line, we can run:

gulp watch

and now if you make a minor edit to app.js, you’ll see Gulp re-compiling our application. Magic!

Error notifications

One thing that might cause us problems later down the line is build errors i.e. problems that occur when our “build” task fails. Now that we’ve got builds happening automatically when we change our code, we’ll probably want to minimise our console window to leave more room on our screens for more important things. But this means that if a compilation error occurs, we might not see it…

Well, as it happens this is an easy fix, so let’s deal with that now.

node-notifier is a cross-platform Node.js module for displaying system notifications. With a minor amount of effort, we can intercept errors in the build process, and display a notification if and when something goes wrong, so that it doesn’t go unnoticed.

First of all, let’s install the Node module. From the command line, run the following:

npm install --save-dev node-notifier

Next let’s add this to our list of dependencies in gulpfile.js:

var browserify = require('browserify'),
babelify = require('babelify'),
notifier = require('node-notifier'),
source = require('vinyl-source-stream');

And finally, we’ll add an error-handling step to flag up any problems with the build, which leaves our “build” task looking like this:

gulp.task('build', function () {
return browserify('./source/app.js')
.transform(babelify)
.bundle()
.on('error', function (err) {
console.log(err.stack);
notifier.notify({
title: 'Build Error',
message: err.message
});
})
.pipe(source('app.js'))
.pipe(gulp.dest('./build/'));
});

Stop the currently-running “watch” task (i.e. hit Ctrl + C a couple of times) and restart it:

gulp watch

Then edit add.js and deliberately add a syntax error that will make the compilation fail. Save the file, and see what happens — a little notification pops up (the location and appearance will depend on your operating system) alerting you to the fact that your code has an error.

Hurrah! So let’s fix the error, hit save, and… oh. Take a look at your console window. The error you previously inserted has hopefully been written (by the console.log statement), but then everything seems to have been stuck, and no further rebuild has been triggered, even though we’ve saved our file after removing the error.

As you’d expect, there’s a solution to this problem. Now, don’t ask me to explain the details of the problem — it’s something to do with streams, but all I know about streams is that you’re not meant to cross them because otherwise all life as we know it will stop instantaneously and every molecule in our bodies will explode at the speed of light. (This is assuming that Ghostbusters is scientifically accurate in that respect…)

To start with, let’s move our error handling into a separate function — this means we can share it between tasks, and stops our “build” task getting unwieldy:

function handleError(err) {
console.log(err.stack);
notifier.notify({
title: 'Build Error',
message: err.message
});
}

Then the error-handing bit of our “build” task becomes:

.on('error', handleError)

And here’s the magic bit we need to add to the end of the error handler function, which stops a compile error (or any other type of error for that matter) from breaking the “watch” task:

this.emit('end');

Just to make sure we’re all on the same page after so much tinkering, your gulpfile.js should now look like this:

var gulp = require('gulp');
var browserify = require('browserify'),
babelify = require('babelify'),
notifier = require('node-notifier'),
source = require('vinyl-source-stream');
gulp.task('build', function () {
return browserify('./source/app.js')
.transform(babelify)
.bundle()
.on('error', handleError)
.pipe(source('app.js'))
.pipe(gulp.dest('./build/'));
});
gulp.task('watch', function () {
gulp.watch('./source/**/*.js', ['build']);
});
function handleError(err) {
console.log(err.stack);
notifier.notify({
title: 'Build Error',
message: err.message
});
this.emit('end');
}

Splendid. So now our code will magically be re-compiled if we make any changes, plus we’ll get a nice popup message should something go wrong with the compilation process. What’s next?

Local web server

At the moment, we’re loading our page by opening index.html directly with a web browser, but that’s not an ideal solution.

Now, it may well be that you’ve already got a web server running locally on your development machine, and if you’re happy configuring that to serve your React projects, that’s fine, you can skip ahead to the next section.

But if you want a quick and easy way of spinning up a temporary local web server — perhaps, like me, you don’t relish the thought of poking around in your Apache config files trying to remember the syntax for adding a virtual host; or indeed, again like me, you struggle to remember where your config files actually live — then read on…

http-server is the Node module we’re going to use to fire up a simple local web server. How easy is it to use, I hear you ask? Well, here’s your answer:

npm install --global http-server
http-server

Wow. That easy! And then just fire up a browser window and go to http://localhost:8080 to see your React application in action. (Admittedly there isn’t much in the way of action at the moment, but we’ll get to this in future posts…)

Quick note — if you’re already serving pages locally on port 8080, you can easily get http-server to use a different port:

http-server -p [PORT]

See the NPM page for details of other command-line options.

Live reload

So, our code is automatically re-compiled when we make any changes — but at the moment, we’re still having to manually refresh our browser once the compilation is complete. Which is problematic for two reasons:

  1. We have to wait until the compilation is complete, if we refresh while the build is still underway, we won’t get the desired results.
  2. We’re developers and we don’t like expending unnecessary effort, such as having to reach all the way up our keyboards to press F5.

By now, it won’t come as any surprise to find out that somebody has already done all of the hard work for us — this time, it’s the turn of the live-react-reload project to come to our rescue.

Now I’m going to be honest here and admit to a couple of things:

  1. What follows next is mostly ripped from the live-react-reload project’s gulpfile.js example.
  2. I don’t completely understand how or why some of it works.

So onwards we go, albeit in a somewhat blundering fashion…

Firstly we’re going to need another handful of Node modules — “vinyl-buffer” (which is one of the ones I’m not quite clear on), “livereactload” which is the clever bit that magically lets our React application know when it has to refresh itself, and “watchify” which replaces the built-in Gulp watch method (because it works better with Browserify, or something).

npm install --save-dev vinyl-buffer livereactload watchify

As ever, we need to declare those dependencies at the top of our gulpfile.js:

var browserify = require('browserify'),
babelify = require('babelify'),
buffer = require('vinyl-buffer'),
notifier = require('node-notifier'),
reload = require('livereactload'),
source = require('vinyl-source-stream'),
watchify = require('watchify');

Next we’re going to split out the Browserify bit into a separate function so that it can be used with or without the live reload plugin (we would want to use it without that plugin to do a single one-off build, for example):

function createBundler(useWatchify) {
return browserify({
entries: ['./source/app.js'],
transform: [ [babelify, {}] ],
plugin: !useWatchify ? [] : [ reload ],
cache: {},
packageCache: {}
});
}

Then we re-write our “watch” task to use the Watchify module:

gulp.task('watch', function () {
var bundler = createBundler(true);
var watcher = watchify(bundler);
rebundle();
return watcher
.on('error', handleError)
.on('update', rebundle);
  function rebundle() {
watcher
.bundle()
.on('error', handleError)
.pipe(source('app.js'))
.pipe(buffer())
.pipe(gulp.dest('./build/'));
}
});

And then we’ll add our standalone “bundle” task:

gulp.task('bundle', function () {
var bundler = createBundler(false);
bundler
.bundle()
.on('error', handleError)
.pipe(source('app.js'))
.pipe(gulp.dest('./build/'));
});

And that’s it (phew). Run “gulp watch” from a command prompt, “http-server” from another command prompt, go to http://localhost:8080 in your browser… and if all has gone according to plan, you’ll be able to make changes to your React code and see those changes magically reflected in the browser mere seconds later.

What’s next?

Well, it can’t have escaped anybody’s attention that we haven’t actually written any React code yet, other than a very minimal “Hello World” example, so I suppose next time we should address that.

But what we do have (thanks to a bunch of people way more talented than me, and despite my lack of understanding of much of it!) is a self-reloading development environment which means we don’t having to think about any of the tedious things like compiling stuff and reloading pages, and instead means we can concentrate on the fun bit — writing code!

A single golf clap? Or a long standing ovation?

By clapping more or less, you can signal to us which stories really stand out.