So I’m putting together this killer new JS front-end development stack called Brunch with Panache, which uses Jake tasks to kick off a custom set of commands for building and managing the app, and I hit a bug while code was running in Node.js. So what now?
The problem is the stack I’m working with is complex enough a small backtrace would be about as effective as a probe into Tesla’s recent automobile fires. Nevertheless, I want running Testlas [sic] so it’s time to jump into some Node.js step debugging. Follow along if you’d like to learn how to get started debugging Node.js applications.
Getting setup
Mac and Linux users, carry on.
Windows users looking to give this a try should be able to adapt these instructions after running through the steps to configure the development environment to get your box ready for debugging inside a VM using Vagrant.
Find something to debug
Clone Brunch with Panache, but make sure you get v0.7.1 to be able to recreate the error:
git clone https://codeberg.org/vhs/brunch-with-panache/commit/044bcd8bee9751af9ef235116bc61643ade44389
cd brunch-with-panache
From within the Panache directory, install the application dependencies detailed in the package.json
(NPM) and bower.json
(Bower) files:
npm install && bower install
--no-bin-links
flag for the NPM command when using Vagrant with synced folders enabled to avoid the creation of symlinks (not supported by the NTFS filesystem).
Then install Jake globally to allow use of the application’s task library:
npm install -g jake
Once installed, run jake gen:codetest name=smoulder
followed by jake test:code
to stub out a unit test and ignite the Karma test runner. The end result should be a crash and burn:
INFO [karma]: Karma v0.10.4 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.2 (Mac OS X)]: Connected on socket 2gRJXM0iyq5MQyNS0KM1
PhantomJS 1.9.2 (Mac OS X): Executed 0 of 0 ERROR (0.086 secs / 0 secs)
jake aborted.
Error
at api.fail (/usr/local/share/npm/lib/node_modules/jake/lib/api.js:340:13)
at null._onTimeout (/usr/local/share/npm/lib/node_modules/jake/lib/task/task.js:213:19)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
Womp womp. What happened? Unless you’re a psychic, you’re not gonna be able to tell from that stack trace. So let’s do some debugging.
Install and start the Node Inspector
Thankfully due to a well placed question at a Chicago Node.js meetup meetup earlier this year I didn’t have to spend any time figuring out how to get started with debugging Node. The Node.js gurus at Groupon suggested node-inspector for step debugging.
Update 2016-11-16: Paul Irish introduced Node debugging in Chrome Dev Tools at the Chrome Dev Summit 2016. So rather than using the Node Inspector you can no debug Node applications directly within the browser. To learn more watch the clip on YouTube.
Install the node-inspector globally from NPM:
npm install -g node-inspector
Then start the node-inspector and background the process (use fg
to bring it back to the foreground and kill %1
to stop it):
node-inspector &
Start and connect to the debugger
Once the inspector is installed, give it something useful to debug. From the application root directory, issue the following command to use the Jake CLI to run and debug the failing task in Node:
node --debug-brk node_modules/jake/bin/cli.js test:code
Then connect to the inspector from your local loopback by visiting http://127.0.0.1:8080/debug?port=5858 in your browser. If all goes well, you’ll see some developer tools (pictured below) from which to begin debugging.
Note: Checkout the sources panel keyboard shortcuts for a list if things you can do without touching your mouse or trackpad.
Tracking down the error
The bug is being caused by test:code
, so the place to start looking is test.jake
, which isn’t available immediately after the debugger starts. To get access to test.jake
from the debugger and get us closer to the error set a conditional breakpoint on the first line of the run
method in task.js
with the following criteria:
this.fullName === 'build:dev'
Once set, resume script execution to arrive at the conditional breakpoint. From here, set another breakpoint on the line which says spawn within the execute method of jakelib/lib/index.js
and resume execution. Once arriving at the next breakpoint press Esc to pull up the console and type command
. The result should be "node_modules/.bin/karma"
. If it doesn’t, resume execution and wait for the next call to execute
and check again. Turns out that node_modules/.bin/karma
couldn’t be opened, resulting in the error.
Fixing the error
Now that we know which file can’t be opened (node_modules/.bin/karma
) let’s take a look at the .bin
directory. Exit the node inspector and run ls -al node_modules/.bin/
from the terminal. Notice a number of symlinks are shown, but karma isn’t one of them. To fix that simply create a symlink from node_modules/.bin/karma
to node_modules/karma/bin/karma
. If done properly the jake test:code
task will no longer fail, instead it’ll just get stuck. It’s getting stuck because test.jake
is running karma without the expected config. To fix that simply pipe in another argument when executing karma, e.g.
jake.Task['build:dev'].addListener('complete', function() {
args.push('test/karma.conf.coffee'); // add argument pointing to your config here
args.push('--single-run');
resolve(karma.execute(args));
}).execute();
The next run of jake test:code
will not only not bomb, it’ll run with the expected karma config and execute tests as expected. And there you have it, a little node debugging to solve a complex problem. The
npm-folders doc suggests the contents of node_modules/.bin
are created based off the bin
property set in package.json
, which was not set in the node_modules/karma/package.json
file it turns out. I’ve submitted
karma/issues/1131 to this end.