pmuellr is Patrick Mueller

other pmuellr thangs: home page, twitter, flickr, github

Monday, March 14, 2016

watching your projects

I'm a big fan of having an "automated build step" as part of the development workflow for a project. Figured I'd share what I'm currently doing with my Node.js projects.

What is an "automated build step"? For me, this mean some "shell" code that gets run when I save files while developing. It may not actually be "building" anything - often I'm just running tests. But the idea is that as I save files in my editor, things are run against the code that I just saved.

So there's basically two concepts here:

  • file watchers - watching for updates to files
  • code runners - when files are updated, run some code

file watchers

I have some history with file watchers - wr, jbuild, cakex. The problem with file watching is that it's not easy. The file watching bits in jbuild and cakex are also bundled with other build-y things, which is good and bad - basically bad because you have to buy in to using jbuild and cakex exclusively. wr is a little better in that it's just a file watcher/runner, but I ended finding a replacement for that with nodemon. The advantage to using nodemon is that it's already an accepted tool, and I don't have to support it. :-) Thanks Remy!

code runners

There are many options for code runners; make, cake, grunt, gulp, npm scripts, etc. I'm currently using npm scripts, because they're the easiest thing to do, for non-complex "build steps".

For example, here are the scripts defined in the package.json of the nsolid-statsd package.

"scripts": {
  "start": "node daemon",
  "utest": "node test/index.js | tap-spec",
  "test": "npm run utest && standard",
  "watch": "nodemon --exec 'npm test' --ext js,json"

Everything's kicked off by the watch script. It watches for file changes - in this case, changes to .js and .json files, and runs npm test when file changes occur. npm test is defined to run the test script, and for this package, that means running npm run utest and then standard. utest is the final leg of the scripts here, which runs my tape tests rooted at test/index.js, piping them through tap-spec for colorization, etc.

So the basic flow will be this, when running the watch script:

  • run node test/index.js, piping through tap-spec
  • if that passes, run standard
  • wait for a .js or .json file to change
  • when one of those files changes, start over from the beginning

Just run the watch script in a dedicated terminal window, perhaps beside your editor, and start editing and saving files.

Note that nodemon, standard and tap-spec are available in the local node_modules directory, because they are devDependencies in the package itself (tape is used when running test/index.js):

"devDependencies": {
  "nodemon": "~1.8.1",
  "tape": "~4.2.0",
  "tap-spec": "~4.1.1",
  "standard": "~5.4.1"

As such, I can use their 'binaries' directly in the npm scripts.

command line usage

You would then kick this all off by running npm run watch. But since it turns out I've been adding a watch script to projects for a long time, all of which do basically the same thing (maybe with different tools), I instead wrote a bash script a while back called watch:

npm run watch

So now I just always run watch in the project directory to start my automated build step.

Here's what the complete workflow looks like:

  • open a terminal in the project directory
  • open my editor (Atom)
  • run watch at the command-line
  • see the tests runs successfully
  • make a change by adding a blank line that standard will provide a diagnostic for
  • save file
  • see tests run, see message from standard
  • remove blank line, save file
  • see tests run, no message from standard

(open image in a new window)

It's basically an IDE using the command-line and your flavorite editor, and I love it.

As projects get more complex, I'll start using some additional tooling like make, cake, etc, or just invoke stand-alone bash or node scripts available in a tools directory. Those would just get added as new npm scripts, and injected into the script flow with nodemon. For instance, you might do something like this:

"scripts": {
  "start": "node daemon",
  "utest": "node test/index.js | tap-spec",
  "test": "npm run utest && standard",
  "build": "tools/",
  "build-test": "npm run build && npm test",
  "watch": "nodemon --exec 'npm run build-test' --ext js,json"

Now when I watch, tools/ will be run before the tests. You can also run each of these steps individually, via npm run build, npm test, npm run utest, etc.