Michele Titolo | Blog | Speaking

Nodemon + Babel + VSCode

I switched to VSCode as my primary editor almost a year ago. One of the primary reasons being that I could never get a Node debugger working quite right in Atom. Once I installed VSCode and figured out Launch Configurations it worked perfectly. Most of the tutorials for setting up node apps for debugging in VSCode either assume you are 1. not using nodemon or 2. not using babel. I use both, and want both to be a part of my workflow in addition to being able to use the built-in debugger.

After hours of tinkering, I figured out how to get Nodemon + Babel + VSCode Launch configurations to work. There are two parts to this setup: both package.json and .vscode/launch.json need to be updated.

package.json

{
  "devDependencies": {
    "babel-preset-es2015": "^6.24.1",
    "babel-register": "^6.26.0"
  },
  "babel": {
    "presets": [
      "es2015"
    ],
    "sourceMaps": true
  }
}

Firstly, your project has to be setup to use babel either with a .babelrc or a babel key in the package.json. Babel is going to be automatically invoked in this setup, which is why this is required.

Next, if you are only using the babel compiler before now, you’ll need to add babel-register to your dev dependencies so compilation can happen on the fly. Why does compilation need to happen on the fly? This is so you can actually use breakpoints in your code. The compiled files will be mapped in memory, so you’ll never have to set breakpoints in a compiled file (which is a problem with some other solutions). Think of these as the minimum required settings since you’ll probably have more in your project.

launch.json

{
  "version": "0.2.0",
  "configurations": [
      {
          "type": "node",
          "request": "launch",
          "name": "Nodemon",
          "runtimeExecutable": "nodemon",
          "args": ["${workspaceRoot}/index.js"],
          "restart": true,
          "protocol": "inspector",
          "stopOnEntry": false,
          "runtimeArgs": [
            "--nolazy",
            "--require",
            "babel-register"
          ],
          "sourceMaps": true
      },
  ]
}

Breaking it down

There’s a lot going on here, and I won’t cover the basics of working with launch configurations in VSCode but will point out the most important things to know.

There are four attributes that make this work:

  • runtimeExecutable - This is the binary or script actually launched by VSCode. In this case it’s nodemon instead of your application. If you don’t have nodemon globally installed you can reference the relative path with ${workspaceRoot}/node_modules/nodemon/bin/nodemon.js.
  • args - This represents the last argument passed to nodemon, so it should be the main file of your application. VSCode launch configs can’t inspect the package.json so this has to be hardcoded. If your application takes other arguments when it is launching, add those here after the main file, just like you would if you were calling node ...
  • runtimeArgs - This is the glue that holds all of this together. These arguments get passed in the order you specify to nodemon before args. Order is important, and these settings won’t work if they are in args. The --require and babel-register need to be separate because arguments in this list cannot have spaces; you’ll get an error if you try to do that.
  • sourceMaps - VSCode needs to know about source maps. If this setting, or the one in package.json, is missing then you’ll get an error.

There’s one catch. There always is. When you stop via the stop button in VSCode, nodemon will crash. If you are using the integratedTerminal instead of the debug console, you’ll need to CTRL+C to exit. In older versions of VSCode (I’m running 1.19.3) the nodemon process would continue forever and eventually freeze VSCode, making it unusable. This isn’t ideal, but is better than it used to be and easy to work around.

Debugging

One of the things I realized early was that debugging this setup is non-trivial. Thankfully there is a setting you can add to the root of your launch.json file that will print a lot of useful information: "diagnosticLogging": true.

{
  "version": "0.2.0",
  "diagnosticLogging": true,
  "configurations": [
    ...
  ]
}

Once set, this will output extra information about what’s going on. Mostly we’re interested in the actual nodemon command that’s being run:

/usr/local/bin/nodemon --nolazy --require babel-register --inspect=42894 --debug-brk index.js 
Debugger listening on ws://127.0.0.1:42894/cbdd92cd-4841-4b70-ad8c-99b8d5f0bbd0
Debugger attached.

If you’re having trouble getting your app to start, this is an invaluable tool. Note also how runtimeArgs are inserted in the middle of the command, in the order in which you added them. This is how I figured out how to inject babel-register, since those arguments are always added in order.

This VSCode setup has helped me immensely, and I hope it helps you too!

© 2023 Michele Titolo