Debugging Node.js Libraries

AuthorMáximo Mussini
·4 min read
This guide was extracted from the Debugging section I wrote for Vite Ruby.

In this post I'm going to cover a few techniques that you can use when debugging library-related issues in your Node.js applications.

I'll be using Vite.js as an example, as it provides an opportunity to cover some specifics related to debugging TypeScript packages and mono repos, but you can apply these techniques to debug any library.

Debug Output 📜

It's common for node.js packages to use the debug library to log information about their configuration, or operations they perform.

To enable logging for these libraries, set the DEBUG environment variable:

  • DEBUG=* Enable all debug output
  • DEBUG=vite:* Enable output for Vite.js core plugins
  • DEBUG=vite-plugin-ruby:* Enable output for a specific plugin

Debug Output

Using a Debugger 🎯

Sometimes debug output is not enough to figure out what's the problem. Fortunately, there are many ways to start a debugging session.

Using node --inspect-brk will break execution until a debugger client is attached. You must provide a JS executable as an argument, for example:

  • node --inspect-brk "$(npm bin)/vite"
  • node --inspect-brk $(yarn bin vite)

Add an inspect shortcut in your scripts if you need to debug often.

I like to use the Node Inspector Manager extension, which will automatically attach the browser's DevTools to the node process.

Debugging with Chrome DevTools

DevTools provide a great debugging environment, with visual breakpoints, watchers, file navigation, and more. Visual Studio Code is another great option.

It's useful to ignore Node.js internals to avoid stepping into the framework.

Attaching a Client to a Debugger Session

Opening Packages 📖

In order to learn more and understand which files you should be debugging, you can run npm edit to open a package in your favorite text editor.

The EDITOR environment variable will be used to infer the preferred text editor. I like using Sublime Text or Visual Studio Code to navigate the file tree.

For example, running npm edit vite will open the copy of the vite package that is installed in the node_modules for the current directory.

Unlike the previous sections, this is something you can leverage for client-side packages as well.

Editing In-Place ✍️

Because JavaScript is a dynamic language, you can tweak the code in-place, restart the process, and your changes will be picked up.

Disclaimer: This has many caveats: bugs that only happen in your local, or the opposite, code that only seems to work in your local.

Skip to the next section for a better approach.

(I use it all the time though, so convenient 😅)

You can add console.log as needed, or even modify the behavior of the library, which can be useful to track down bugs.

In TypeScript packages (and some JS packages), make sure to edit the transpiled file instead of the sources. This is often dist/index.js but that depends on the library and target environment.

For example, in Vite.js the entrypoint is dist/node/index.js.

Using a Local Library 🔗

When fixing a bug or implementing features, a better flow is to clone the library, and then point to your local copy by using the npm link or yarn link commands.

Let's see a step by step example with vite:

# 1. Clone the repo
git clone 
cd vite/packages/vite # Location of package.json
pnpm install

# 2. Make the local package available for linking
npm link # skip if using pnpm

# 3. Start compilation in "watch" mode
pnpm dev

In TypeScript or compiled JS libraries, it's important to build the library in order for changes to the source to be reflected in the linked project.

By convention, libraries define a build script to perform compilation, and a dev script that starts compilation in watch mode—file changes will start a new build.

Finally, link the library in the project you would like to test the changes:

# 4a. Link the library by name to use the local copy
npm link vite

# 4b. If using pnpm, link it by path instead
pnpm link ../vite/packages/vite

Just as in the previous section, it's important to restart the process you intend to debug after making changes.

Why is it necessary to restart? 🤔

Most processes will load the required libraries into memory, and then use these cached versions during their entire running time.

Restarting the relevant process, such as the Vite.js development server, ensures the updated version of the library is loaded.

Additional Resources 📖

I hope this summary has been useful! A similar guide for Ruby is available 😃

Please refer to the following documentation for more information: