Debugging Javascript applications with Neovim
Tags: debugging, javascript, neovim
Published: 2023-06-22 19:13:47

## Introduction
If you're a software developer you will have to debug code at one point or the other and the truth is that debugging in Neovim for the first time can be a daunting task. 

The current status of debugging within Neovim requires lots of configuration compared to `VS Code`

The best way to debug code in my experience is using the `Debug Adapter Protocol (DAP)` with a tool called `nvim-dap`, if you want to know more about the details of how this works check the [official information](https://microsoft.github.io/debug-adapter-protocol/) or [my blog post](https://miguelcrespo.co/how-to-debug-like-a-pro-using-neovim).

In this blog post, we will see how to configure this tool to:

- Debug node applications (Servers, files)

- Debug web applications using Chrome

- How to keep the session state across multiple debugging

- Supporting `.vscode/launch.json` files

**I will assume you already know how to:**

- Install packages in Neovim

- Some basic understanding of Lua

## What is possible?
Let's see what is currently possible with Neovim

[Debugging Javascript applications with Neovim video](https://videos.ctfassets.net/fduzn3bxxb8f/1ZOIT8b6395Ut94vm2xogN/940848251650e3533c9578c1d676a9b1/Mein_Film.mp4)
## My experience so far
In order to get to this level I sadly had to expend a lot of time because it was not a plug and play experience, the journey looked a bit like this:

- Found out that debugging was possible using `nvim-dap`, previously the only thing I was using VS Code for was to debug applications

- Read the `nvim-dap` documentation and understand nothing because most of the documentation assumes knowledge about DAP

- Read the DAP documentation and begin to understand a bit :D

- Look for `adapters` to debug Javascript, typescript and node applications in general

- Find `nvim-dap-vscode-js` and read the documentation and understand nothing cause most of the documentation is leveraging the official [https://github.com/microsoft/vscode-js-debug](https://github.com/microsoft/vscode-js-debug) documentation which only speaks about VS Code

- Look on the internet until finally get something to work

- Be happy (But tired and sad that I needed to spent so much time in this)

The next steps in this post will show you how to configure your Neovim to do the same

## Install [nvim-dap](https://github.com/mfussenegger/nvim-dap)
We will need to install `nvim-dap`, this tool is basically a DAP client in Neovim. In order to install it, you only need to add the following line in your favorite package manager


```text
'mfussenegger/nvim-dap'
```

Then we will load the plugin in our Neovim configuration and set some keymaps to better use DAP


```lua
local dap = require("dap")

-- Set keymaps to control the debugger
vim.keymap.set('n', '<F5>', require 'dap'.continue)
vim.keymap.set('n', '<F10>', require 'dap'.step_over)
vim.keymap.set('n', '<F11>', require 'dap'.step_into)
vim.keymap.set('n', '<F12>', require 'dap'.step_out)
vim.keymap.set('n', '<leader>b', require 'dap'.toggle_breakpoint)
vim.keymap.set('n', '<leader>B', function()
  require 'dap'.set_breakpoint(vim.fn.input('Breakpoint condition: '))
end)
```

In the previous step, we set up some keymaps to interact with `nvim-dap` like:

- `F5` to start a new debugging session

- `F10, F11, F12` to move though the code once you're in a debugging session

- `leader b` to toggle breakpoints

- `leader B` to set conditional breakpoints

## Install and configure nvim-dap-vscode-js
Next we need to install [`nvim-dap-vscode-js`](https://github.com/mxsdev/nvim-dap-vscode-js) this is the adapter that allows Neovim (through the nvim-dap) to communicate with JavaScript and interestingly, it's the same adapter used in VS Code.

- Again, install the package using your preferred plugin manager


```bash
mxsdev/nvim-dap-vscode-js
```

Then copy this into your configuration file, this lines of code was taken from the [Official Github repository](https://github.com/mxsdev/nvim-dap-vscode-js)


```lua
require("dap-vscode-js").setup({
  -- node_path = "node", -- Path of node executable. Defaults to $NODE_PATH, and then "node"
  debugger_path = "(runtimedir)/site/pack/packer/opt/vscode-js-debug", -- Path to vscode-js-debug installation.
  -- debugger_cmd = { "extension" }, -- Command to use to launch the debug server. Takes precedence over `node_path` and `debugger_path`.
  adapters = { 'chrome', 'pwa-node', 'pwa-chrome', 'pwa-msedge', 'node-terminal', 'pwa-extensionHost', 'node', 'chrome' }, -- which adapters to register in nvim-dap
  -- log_file_path = "(stdpath cache)/dap_vscode_js.log" -- Path for file logging
  -- log_file_level = false -- Logging level for output to file. Set to false to disable file logging.
  -- log_console_level = vim.log.levels.ERROR -- Logging level for output to console. Set to false to disable console output.
})
```

### Some of the problems you might have with the previous configuration:
- `debugger_path`

If you're using anything else than `packer` to install plugins you need to modify the `debugger_path` because the path might change depending on your plugin manager, for `lazy`, you need to change the `debugger_path` to be `vim.fn.stdpath('data') .. "/lazy/vscode-js-debug"`

- adapters

I needed to add `chrome` and `node` to the list of default adapters because I wanted to be able to use the configuration I have in `VSCode` in my Neovim, and VS Code refers to this adapter as just `node` and `chrome` instead of `node-pwa` and `chrome-pwa`

### Configuring the DAP Adapter
In this step we will add some default configuration for our adapters, we will tell the adapters in what kind of languages we want to active it.

This configuration is based on the [launch configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) from VS Code


```lua
local js_based_languages = { "typescript", "javascript", "typescriptreact" }

for _, language in ipairs(js_based_languages) do
  require("dap").configurations[language] = {
    {
      type = "pwa-node",
      request = "launch",
      name = "Launch file",
      program = "${file}",
      cwd = "${workspaceFolder}",
    },
    {
      type = "pwa-node",
      request = "attach",
      name = "Attach",
      processId = require 'dap.utils'.pick_process,
      cwd = "${workspaceFolder}",
    },
    {
      type = "pwa-chrome",
      request = "launch",
      name = "Start Chrome with \"localhost\"",
      url = "http://localhost:3000",
      webRoot = "${workspaceFolder}",
      userDataDir = "${workspaceFolder}/.vscode/vscode-chrome-debug-userdatadir"
    }
  }
end
```

- The first entry in the lua table will help you debug single `Node.js files`

- The second will help you debug node processes like `express` applications

- The last entry will help you debug `web applications` 

- The line `userDataDir` will let you save your Chrome profile in a file

Now with this configuration you will be able to start debugging Javascript applications, but let's add another plugin to make the interface easier to use

## Install and configure [dapui](https://github.com/rcarriga/nvim-dap-ui)
To make the UI better, let's install `dapui`

![Dap UI javascript debugging](https://images.ctfassets.net/fduzn3bxxb8f/7vOfsDabRuOrypTzilqu3M/a03a215104108425e6c918d9b0cc8190/grafik.png)
With the following code, you will set up `dapui` and make the UI automatically open once a debugging session has been created, and also close the UI once the debugging sessions have finished


```lua
use { "rcarriga/nvim-dap-ui", requires = {"mfussenegger/nvim-dap"} }
```


```lua
require("dapui").setup()

local dap, dapui = require("dap"), require("dapui")

dap.listeners.after.event_initialized["dapui_config"] = function()
  dapui.open({})
end
dap.listeners.before.event_terminated["dapui_config"] = function()
  dapui.close({})
end
dap.listeners.before.event_exited["dapui_config"] = function()
  dapui.close({})
end

vim.keymap.set('n', '<leader>ui', require 'dapui'.toggle)
```

## Debugging your first code
### Debugging single Javascript files
Open the file in your Neovim and press `F5` and select `Launch File`, the dapui UI should appear and let you debug the code similarly to VS Code

If you copied the previous configurations, you will be able to go to a

### Debugging server applications like `express`
Start the `node` process in any other tab in your terminal with the `--inspect` flag, e.g:


```bash
node --inspect ./app.js
```

Now press `F5` and select `Attach`

### Debugging a browser application
Open a JS file from the project and select `Start Chrome with Localhost`, the application must be running on port `3000` but you can change this in the previous configuration file

## Sharing launch configuration with VS Code fans
This step is helpful if you're planning to debug a project in which there's already a `launch.json` file generated by VS Code users, for this we will need `json5` because most of the `launch.json` that VS Code generates has comments inside and these are not valid JSON files

### If you have a Mac
You would need to have installed `rust`


```bash
brew install rustup-init
```

Then, you need to add the following line to your `cargo config`, usually it's located in `.cargo/config.toml` `.cargo/config.toml`


```toml
[target.x86_64-apple-darwin]
rustflags = [
    "-C", "link-arg=-undefined",
    "-C", "link-arg=dynamic_lookup",
]

[target.aarch64-apple-darwin]
rustflags = [
    "-C", "link-arg=-undefined",
    "-C", "link-arg=dynamic_lookup",
]
```

Then install [json5](https://github.com/Joakker/lua-json5) plugin

- Packer


```lua
use {
    'Joakker/lua-json5',
    run = './install.sh'
}
```

- Lazy


```lua
{
    'Joakker/lua-json5',
    build = './install.sh'
}
```

You will also need to add this line to your lua config, to be able to load the `json5` plugin successfully


```lua
-- https://github.com/neovim/neovim/issues/21749#issuecomment-1378720864
-- Fix loading of json5
table.insert(vim._so_trails, "/?.dylib")
```


```lua
require('dap.ext.vscode').load_launchjs(nil,
  { ['pwa-node'] = js_based_languages,
    ['node'] = js_based_languages,
    ['chrome'] = js_based_languages,
    ['pwa-chrome'] = js_based_languages }
)
```

From now on, you should be able to debug applications in Neovim!

Find some bugs!

![Some bugs](https://images.ctfassets.net/fduzn3bxxb8f/5f3GeGornIfavKnSI5kjUo/34c8891d692ac0928ae98102e7de0ac3/maddy-weiss-oohJZjd3IQU-unsplash.jpg)