Dev.to · 5 min read

Debugging JS/TS from Neovim: JS/TS, the NestJS special & the React Native drama queen

Debugging JS/TS from Neovim: JS/TS, the NestJS special & the React Native drama queen

So today let's talk about getting real breakpoints in Neovim. Not console.log, actual pause-here-inspect-the-variables debugging. The good news is: for plain Node/TS it's almost boringly simple. Then NestJS throws a small curveball. Aaaaand [...] - hum - react native decides it's the main character (that sad since I started that to actually debug in react native). Step 1: install js-debug We first need to install the adapter, this is a server we run with node. You can download the tar.gz build in github and unpack it in a choosen folder (I choose to create a .dap folder into my home): https://github.com/microsoft/vscode-js-debug/releases mkdir ~/.dap tar -xvzf js-debug-dap-v1.117.0.tar.gz -C ~/.dap Were going to point nvim's adapter at that dapDebugServer.js just after now :) Step 2: the actual nvim config Just a side note, for React Native we need to add the nvim-dap-react-native plugin, it builds a "direct to Hermes" adapter on top of our pwa-node adapter. Here's the whole config: { "AkisArou/nvim-dap-react-native", build = "npm ci", }, -- check https://codeberg.org/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#vscode-js-debug { "mfussenegger/nvim-dap", lazy = false, config = function() local dap = require("dap") dap.adapters["pwa-node"] = { type = "server", host = "localhost", port = "${port}", executable = { command = "node", args = { "~/.dap/js-debug/src/dapDebugServer.js", "${port}" }, }, } dap.adapters["reactnativedirect"] = require("dap-react-native").create_adapter(dap.adapters["pwa-node"]) for _, ft in ipairs({ "javascript", "typescript", "javascriptreact", "typescriptreact" }) do dap.configurations[ft] = dap.configurations[ft] or {} table.insert(dap.configurations[ft], { type = "pwa-node", request = "launch", name = "Launch file", program = "${file}", cwd = "${workspaceFolder}", }) table.insert(dap.configurations[ft], { type = "pwa-node", request = "launch", name = "Launch file (tsx)", runtimeExecutable = "tsx", program = "${file}", cwd = "${workspaceFolder}", }) table.insert(dap.configurations[ft], { type = "pwa-node", request = "attach", name = "Attach NestJS (9229)", cwd = "${workspaceFolder}", port = 9229, }) table.insert(dap.configurations[ft], { type = "reactnativedirect", request = "attach", name = "RN: Attach Hermes", cwd = "${workspaceFolder}", }) end local keymap = vim.keymap.set keymap("n", "db", dap.toggle_breakpoint, { desc = "Toggle breakpoint" }) keymap("n", "dc", dap.continue, { desc = "Continue / launch" }) keymap("n", "do", dap.step_over, { desc = "Step over" }) keymap("n", "di", dap.step_into, { desc = "Step into" }) keymap("n", "du", dap.step_out, { desc = "Step out" }) keymap("n", "dt", dap.terminate, { desc = "Terminate session" }) keymap("n", "dr", function() dap.repl.toggle() end, { desc = "DAP repl" }) end, }, What's happening: one pwa-node adapter for everything JS, and a react native direct adapter created from it for Hermes. Four configurations: launch plain, launch with tsx, attach Nest, attach RN. Step 3: Node and TypeScript Open a .ts file, hit db to toggle a breakpoint, dc to launch. If you're running a TS file directly, use "Launch file (tsx)", it runs node tsx under the hood and needs no manual build step. The one trick: debugging a compiled process only shows you JS unless you tell js-debug about sourcemaps. That's why the launch configs work with .ts files directly. Js-debug resolves them via sourcemaps. Step 4: NestJS, the special part NestJS is still Node, so the same pwa-node stack works. But there are three gotchas: You debug a compiled app. Nest builds to dist/ and runs that. Attach like a plain script and you see compiled JS, not your decorators DI bootstrap happens fast. Attach too late and you'll never break in AppModule (for example) because it's already initialized So you attach before it boots Fortunately Nest CLI has a debug flag for that, that is already there in the npm run scripts: "start:debug": "nest start --debug --watch" Start Nest with the previous command, then dc and pick "Attach NestJS (9229)". The flow is something like that: nvim - DAP ▶ js-debug ─ CDP ▶ nest (runs dist/, sourcemaps map back to .ts) Step 5: Ok now the diva: React Native and why it doesn't work (yet) Same config as Node, just with the RN adapter added before: { "AkisArou/nvim-dap-react-native", build = "npm ci", }, It builds a reactnativedirect adapter on top of our pwa-node one. So: start the app, db to add a breakpoint, dc and pick "RN: Attach Hermes". And then... "Debugger and device timed out". Ha (: Since React Native 0.76, the debugger is React Native DevTools, a Chromium window. And that window is the CDP client, not a proxy. Hermes only opens its debug socket for that window, and the window consumes it. So our adapter shows up, finds no target and gives up: nvim ─ DAP ▶ reactnativedirect ─ CDP ▶ Hermes ▲ Hermes only talks to the DevTools window ─┘ our adapter: "Debugger and device timed out" Before 0.76 there was a seam (Metro's debugger proxy) external adapters could ride on. The 0.76 rewrite removed it. So: it's not nvim's fault, nor js-debug's fault ; the last hop of the chain only speaks to one client, and that client is a window. The fix is re-exposing a stable CDP endpoint for external debuggers and that actually sitting in an open PRs on microsoft/vscode-react-native repo (#2781/#2782). Until it lands, RN: Attach Hermes is a coin flip, even more so on RN 0.85 (Expo SDK 56). But, hey, at least your config is already setup! So I let you there, have fun!

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Programming & Dev News