31 August 2013

Background

Motivation

I’m currently working on a simple network multiplayer game as a bit of a learning exercise. I’m using Javascript since the client is browser-based (I’ve been wanting to get some canvas/WebGL experience for ages) and I’ve found node.js to be great for small, focused server applications such as this.

Also, a multiplayer game is a really strong use case for sharing code between the client and server, as you need to run identical simulations on both (there are loads of great sources on this, but Gabriel Gambetta’s Fast-paced Multiplayer series is an excellent overview). This makes the combination of node.js and client-side JavaScript particularly appealing.

Sharing code

I’m using browserify to share code between the server and client, by allowing node-style CommonJS imports on the client side (as well as providing browser stand-ins for many the node.js core libraries). Getting this up-and-running with grunt was very quick and easy.

Grunt is a node.js-based task runner (similar to jake, or Ruby’s rake), with lots of 3rd-party plugins for performing common tasks. To set up browserify with grunt, I just had to install grunt-cli and grunt-browserify via npm, and create a Gruntfile.js for my project like this:

> npm install -g grunt-cli
> npm install grunt-browserify --save-dev
Gruntfile.js
module.exports = function(grunt) {
grunt.initConfig({
clean: [ 'build' ],
browserify: {
main: {
src: ['client/main.js'],
dest: 'build/client/bundle.js'
}
},
copy: {
main: {
files: [
{ src: ['client/static/*'], dest: 'build/client/', expand: true, flatten: true },
{ src: ['client/lib/*'], dest: 'build/client/lib/', expand: true, flatten: true },
{ src: ['server/**'], dest: 'build/' },
{ src: ['shared/**'], dest: 'build/' }
]
}
}
});

grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-clean');

grunt.registerTask('default', ['clean', 'browserify', 'copy']);
};

I simply point browserify at a single bootstrapping client-side JavaScript file, and it recursively follows any require calls to pull in the whole set of necessary files, then packages them all into a single bundle.

I also added a grunt-contrib-copy task (to bring all the other files that aren’t part of the bundle across to the output directory), and a grunt-contrib-clean task (to wipe out the build directory before recreating it).

The problem

The above worked great, but introduced a build step into my client-side code. Up until this point I’d been able to quickly see changes in the browser by refreshing, as I was keeping http-server running in my client source directory serving up the script files and other static assets.

Now, I had to stop the http-server (to release the lock on the output directory), run grunt (to create the browserify script bundle) and restart the server. This is only a few quick commands, but made a big difference when I was doing things like obsessively tweaking my rendering logic for drawing cartoon purple bunny rabbits. Also, as I began to work on more server-side code I’d need to restart my actual application server anyway, not just a dumb static asset http-server.

The solution

I do really like node.js and the whole Javascript ecosystem. However, since node.js and npm appeared, there’s been an overwhelming proliferation of Javascript libraries and tools, many of them individually evolving at breakneck speed, and it’s often difficult to know where to begin.

In the end, I needed to make use of the following:

  • grunt-contrib-watch for re-running the build process whenever my files changed
  • nodemon for running and restarting the server (I assume it’s pronounced node-daemon rather than node-mon)
  • LiveReload for getting the browser to automatically refresh (actually an improvement on the original situation)

Putting it all together

Install

> npm install grunt-contrib-watch --save-dev
> npm install grunt-nodemon --save-dev
> npm install grunt-concurrent --save-dev

grunt-concurrent is an extra bit of glue allowing me to run watch and nodemon simultaneously.

Also install the relevant browser extension for livereload.

Add grunt tasks

grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-nodemon');
grunt.loadNpmTasks('grunt-concurrent');

Configure grunt tasks

  • First watch, to run the browserify bundling process (and copy the output files to the right place)
  • Then nodemon, to run my server start script
    • Note that I’ve actually configured nodemon to monitor the build directory, so it’s effectively downstream of the browserify step
    • There are various other options for choosing which files to monitor or exclude, but this was the neatest one for me and avoided any duplication with the watch step
    • The delayTime makes nodemon wait a moment after first spotting a change in case there are any other changes very shortly after
  • Finally concurrent, to run both processes and combine their command-line output
watch: {
files: ['client/**/*.js', 'shared/**/*.js', 'client/**/*.html', 'client/**/*.css'],
tasks: ['browserify', 'copy'],
options: {
livereload: true
}
},
nodemon: {
dev: {
options: {
file: 'start.js',
watchedFolders: ['build'],
delayTime: 1
}
}
},
concurrent: {
target: {
tasks: ['nodemon', 'watch'],
options: {
logConcurrentOutput: true
}
}
}

So now, whenever I make a change to client- or server-side files, I just have to wait a second before the server restarts and the browser automatically refreshes. Perfect!

If only I’d managed to sort this out before last weekend’s Ludum Dare I might have ended up submitting an entry. Unfortunately I ran out of time (not just due to faffing with client/server build process). I’m very pleased to have got it working since though, and have carried on development of the game that I started over the Ludum Dare weekend. You can view the working result of all of the above in the project’s github repo.