-
Notifications
You must be signed in to change notification settings - Fork 0
Debugging JavaScript Frameworks in Neovim #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Very nice! I’ve got a setup that works in neovim but often times if I have to debug I use VSCode 😂 |
I've just found your article in Google and I must say - it is so well written and clear, and I really enjoyed putting it all together in my nvim! Thanks! |
Came here to say thanks a lot for this, was struggling getting this setup as well. Also wanted to mention this great additional plugin that puts the value of variables into virtual text when stopped at a breakpoint. i.e. https://github.com/theHamsta/nvim-dap-virtual-text EDIT: After rereading I see you had actually mentioned it already anyway haha |
thank so much for the guide. as a recovering vscoder it's really hard to get into nvim. this has helped. A LOT! |
This was very helpful in helping set mine up. Here's an improvement for debugging typescript files: {
type = "pwa-node",
request = "launch",
name = "Launch (Node)",
program = "${file}",
cwd = "${workspaceFolder}",
runtimeExecutable = "npx",
runtimeArgs = { "tsx" },
},
|
Fantastic guide it is so well written actually taking the time to step through all the components like that. Much appreciated 👏 |
Very nice. Thanks a million |
Much appreciated. Very much liked the writing sytle. Gave me a few smiles during the rather rough journey to neovim. |
Worked for me for debugging Vitest on AstroNvim (naturally should work since Astro uses Lazy). The UI still isn't the best compared to VSCode or WebStorm but it works enough for me to not have to break out VSCode unless I'm very stuck debugging something complex. It helps me spend a little more time in Neovim. One big caveat though is make sure you have installed gulp. I didn't have it installed and kept getting errors about the entrypoint not existing. It turns out that the build script won't build but also won't tell you that it failed, so the only thing you see is that the entrypoint can't be found. |
Thank you for the nice article. |
Works perfect! but I have to compile the .ts and run the debugger for the .js file, is there no way to run the .ts directly? |
description: A guide on how to setup debugging for JavaScript projects in neovim. Learn how to debug svelte, react or vue without having to leave your editor.
tags: ["code","nvim","svelte","javascript","guide"]
Configuring Neovim for Debugging JavaScript Frameworks
I have to make a confession...
When I have no clue whether or not the spaghetti code I wrote is al dente, I don't reach for a fancy JavaScript debugger like a proper Engineer, but instead arbitrarily disperse
console.log
statements throughout my code like the script kiddy I am.Sometimes, when I don't feel like a bunch of
[object Object]
s are gonna take me where I wanna go, I use the full extend of my Engineering knowledge and skillfullyJSON.stringify
the application's current state onto the page.I do realize however, that this technique is not likely to lead me to the 500k USD base compensation role I so badly need to offset my sushi consumption. So I decided that I had to expand my skillset and leave
console.log
behind me and learn debugger-jutsu to tackle mysterious stack overflows going forward.Only Problem: Since I'm a show-off, I use Neovim - and how on earth do you debug JavaScript from a terminal based text editor?
The Objective
Before we dive into the config files, let us first establish what we are trying to accomplish here: Extending Neovim, so that we can debug code from (modern) JavaScript frameworks without leaving the editor.

Since I mostly develop in Svelte.js, the settings shown here are only tested to work with Svelte and SvelteKit projects.
I don’t see a reason however, why this shouldn’t work for other JavaScript frameworks, such as React or Vue and I’ll highlight where I write svelte specific configuration, so you can adjust it to accommodate your JavaScript Flavor of choice. (Or give svelte.js a try and use the config as is ❤️)
One more caveat: In order to have my neovim boot up blazingly fast, I use folke’s lazy.nvim as a package manager.
If you use Packer or some other tool, you’ll have to change the lazy specific parts of my config. If you get stuck, check out the lazy.nvim migration guide that shows how lazy.nvim APIs map to the APIs of other package managers.
Getting Neovim to DAP
Since vscode is a resource hungry, slow starting, evil corporation backed electron app I’m probably going to editor hell for saying this, but Microsoft really did an amazing job with its API design.
Just like how their LSP (Language Server Protocol) became the de-facto standard for communicating a language server’s diagnostics, types and completions to the editor, their DAP (Debug Adapter Protocol) also standardized an Interface for brokering debugging related information between debug adapter and debug client.
These interoperable standards allow neovim to tap into the work others have done for vscode targetting language-tools and provide language integrations without having to reinvent the wheel.
However, other than with the LSP, neovim does not natively support the DAP (yet). Lucky for us, neovim core team member mfussenegger publishes a plugin that provides an implementation of the Debug Adapter Protocol: mfussenegger/nvim-dap.
Since my neckbeard is not yet developed enough for me to use this plugin as is, we do as n00bs have done for thousands of years and use a GUI to debug our stuff.
While I had some issues with the UI’s layout, rcarriga/nvim-dap-ui is a fantastic tool to interact with the debug adapter, although depending on your needs, you might also want to look into theHamsta/nvim-dap-virtual-text to display the debug adapter’s information right inside your code.
Now, let’s see what we need to do to make Neovim
dabDAP!Wiring up
nvim-dap
andnvim-dap-ui
First, we’ll write a lazy.nvim plugin spec to install the two dependencies and configure everything we need in one place.
The plugin spec’s lazy property can be set to true, because you rarely debug something immediately after starting your editor. That that way the plugins don’t add to our startup time as they are lazily loaded once we call their keybinding.
For sake of simplicity, I only setup keybindings for toggling a breakpoint and “continuing”, that is launching debug sessions, here. You’ll probably also want to configure keybindings for
require('dap').step_over()
andrequire('dap').step_into()
.Finally, we’ll add a config function, that will be called whenever lazy.nvim loads this plugin spec. For now, the callback only executes nvim-dap-ui’s setup function and then calls it a day.
The error suggests that we’re pretty close and just missing a configuration, but if you took a look at the scrollbar position to the right, you might have realized that this won’t be as easy as simply adding a config.
Missing Pieces: Config, Adapter and Debugger
So what even are we configuring? Neovim now supports the Debug Adapter Protocol but we are still missing the actual Debug Adapter that implements the interface.
Looking at nvim-dap’s Debug Adapter installation wiki, we learn that there usually is one debug adapter per language, and they are added to
nvim-dap
like so:The above mock code shows that a
Debug Adapter
for a given language is nothing more than a function that takes two arguments: a callback and a config. The adapter then calls the callback function, passing along some settings based on the values of the config.While I won’t go into details about how adapters work under the hood (read: I have no clue - probably magic!), let’s have a brief look at the config that’s passed to the adapter: the config object has a
request
property that can either beattach
orlaunch
.In the context of debug adapters, this means we can either debug an already running process by attaching to it or launch a new process altogether for our debugging purposes.
But how do we actually attach to a process, how does
nvim-dap
communicate with node or chrome? We are missing one final piece of the puzzle: the debugger itself.For JavaScript, microsoft/vscode-js-debug is the debugger of choice, as it combines debuggers for
node.js
, Chrome and more in one, actively maintained package. There even exists mxsdev/nvim-dap-vscode-js, a neovim plugin that provides a debug adapter for this debugger, so we don’t have to get into the weeds of handcrafting one ourselves.Draw the Rest of the Owl
Debugging JavaScript in Neovim (6 Servings)
nvim-dap-ui
nvim-dap
nvim-dap-vscode-js
vscode-js-debug
So now that we have identified all the ingredients for our debugging soup, let’s work our way up this list and first get our hands on a debugger.
Building the JavaScript Debugger from Source
We could manually download and extract the latest release of
vscode-js-debug
from their "releases" page, but we wouldn’t be engineers if we didn’t programmatically build the debugger from source. Since the repo for the debugger is hosted on github, we can leveragelazy.nvim
to automatically do this for by just adding a nested lazy spec for a new dependency to our original lazy spec.Let’s go over what this lazy spec does:
By specifying
version = "1.x"
we make sure that we only rebuild the debugger on new releases for version 1. That way, we don’t do the expensive rebuilding for every commit to the main branch or risk unknowingly running into breaking changes from a new major release. I set the version to"1.x"
to still stay up to date, but you might as well specify a full release version here if you don’t want to automatically upgrade →"1.78.0"
is the latest stable release at the time of writing this article.So whenever a new release is published and we open neovim, we fetch the corresponding commit of the repository. However, since the debugger is written in typescript, we can’t use it as is, but need to compile it to JavaScript first. The
build
property in lazy specs allows us to specify commands for building a package. Here we first install the package’s dependencies withnpm i
and then run it’s compile script usingnpm run compile vsDebugServerBundle
. Finally, we have to rename thedist
folder where the compile output was written to, toout
, as that’s the path that our debug adapter plugin expects.Setting up our Debug Adapter:
nvim-dap-vscode-js
Next, we’ll configure the debug adapter plugin
nvim-dap-vscode-js
.The above lazy spec adds
mxsdev/nvim-dap-vscode-js
as a dependency and calls the debug adapter’s setup function in the config callback.Here we pass it the path of the debugger we just built and specify which adapters we want it to register with
nvim-dap
.For the
debugger_path
we just append neovim’s standard data path with/lazy/vscode-js-debug
as the rest of the path to our debugger (/out/src/dapDebugServer.js
) is hardcoded in the plugin.I am telling
nvim-dap-vscode-js
to add all it’s available adapters tonvim-dap
here, but you might as well just specify the adapters you need.If we hit the keybind to continue/launch a debug session now, we will actually encounter the same error as earlier. This is because, while we have set up a debug adapter and debugger, we have yet to tell neovim which debug adapter to use for which language.
Adding Language Specific Configs to
nvim-dap
For this,
nvim-dap
exposes aconfiguration
setting, where we can configure debugging for the languages we need.The config we pass here is not arbitrary, but a list of
debug actions
(my name, vs**de calls themlaunch.json
s) that configure debug actions for a language.Since most JavaScript frameworks will share this configuration, it is easiest to just put them all into a table and declaring the config iteratively.
If you want to debug something other than JS, TS or Svelte, this is where you need to declare your intention to do so! Just extend the table with
"typescriptreact"
,"vue"
or whatever language matches your jam.Now is the part where things get interesting, as you can customize the
debug adapter config
s to fit your needs. I will go over the threedebug actions
I personally use in this article, but feel free to post your preferred configs, suggestions or fixes into the comments so other people can benefit from your knowledge!1. Launch current file in new node process
The first debug action (or
launch.json
) I’ll share here is for launching the current javascript file in node (and thus won’t work inside typescript, svelte or any other non-standard dialect).2. Attach to an inspectable node process
This debug action attaches to a node process that has been started with the
--inspect
or--inspect-brk
flag. For long running tasks (like a dev server) usually just using--inspect
will do, but for short lived tasks (like a one-off script)--inspect-brk
is better suited, as it will delay execution until the debugger has attached instead of racing through the program before the debugger could communicate any breakpoints.But let’s say you want to debug a long running npm script, to debug server side code in a SvelteKit app while running the dev server?
If you want to debug a npm script, you can use the
--node-options
flag to pass along flags to the node binary when starting the script:# start SvelteKit dev server with inspectable node process npm --node-options --inspect-brk run dev
the below debug action assumes some defaults to make debugging common web development projects more enjoyable, but you might need to play with it to get it working for the types of projects your working with (tested with SvelteKit, should work great with other vite based meta-frameworks)
3. Debug the web in google chrome
This launch action launches and immediately attaches to a debuggable chrome browser.
Similar to the previous one, this launch action also assumes some defaults to be tailored to a vite based dev experience.
For example, it assumes that your web app will run on port
5174
(default vite port) and skips a few vite specific files in the browser to not show you auto generated code while debugging. If you don’t use vite, you’ll be able to get it working with a bit of tweaking!Last but not least: an automagic GUI!
The Debug Adapter Protocol exposes a few events we can hook into to have
nvim-dap-ui
's GUI automagically open and close when we start or terminate our debug sessions. > I’ve foundnvim-dap-ui
's GUI a bit finicky - with the breaking when repeatedly opening it. Passingreset = true
when launching the GUI alleviates these problems a bit.While this works reliably in about ~85% of cases, you might still want to map
require("dapui").toggle()
to some key, as there are cases where the UI sticks around after an error and closing all it’s windows manually can be a bit of a pain.Putting it all together!
I know, I know. This article was way too long and by now you’re probably sick of my writing style, but trust me: the payoff is worth it!
First, let’s put all the pieces together for the final lazy spec you can copy and paste into your code:
Neovim Debugging in Action
Debugging plain old JavaScript
Now with everything setup, let’s see it in action.
Let’s do a test run with a plain JavaScript file first:
Open up a new file in neovim
nvim index.js
& write some random code:Now enter
gg
to jump back to line 1 and hit that keybind to add breakpoint to the current line (<leader>d
in the config above). If aB
shows up at the very left, everything is setup correctly.Now let’s start up the debugging session (
<leader>c
). You should be prompted with three options now:For now, let’s select option 3: Launch file in new node process. Just type
3
and hit enter.This will now open a few windows, which show you the current state of the runtime. Since the GUI is just comprised of windows, we can easily switch focus between them. For example, if we type
<c-w>
followed byh
, we end up in the left top window, titled “DAP Watches”. Here we can write javascript statements and expressions with the current debugger state. I’ve boundrequire 'dap'.step_over()
to<c-'>
, so I can use that keybinding to step through the lines of code, and use the GUI to see the current values I’m interested in.Debugging Client Side JavaScript
To debug Client Side Javascript, the concept is the same, but the steps are a bit different. Let’s go through it and try to debug the very SvelteKit site you’re reading this article on. Here are the necessary steps:
2: Launch Chrome to debug client
When the breakpoint is triggered on the client side, execution will stop and inspect the current state and step through the execution in chrome from the comfort of your own editor!
Debugging Server Side JavaScript
Basically almost the same thing again, but now we have to pass a flag when starting the dev server, so we can connect the debugger to it.
npm run dev
→npm --node-options --inspect run dev
1: Attach debugger to existing node --inspect process
As before, once your code reaches a breakpoint, execution will stop and you get to debug in style!
I hope this article helped you in setting up Neovim to your liking. Happy Debugging! 🤖
The text was updated successfully, but these errors were encountered: