Skip to content

Instantly share code, notes, and snippets.

@dchowitz
Last active August 30, 2023 06:23
Show Gist options
  • Star 67 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save dchowitz/83bdd807b5fa016775f98065b381ca4e to your computer and use it in GitHub Desktop.
Save dchowitz/83bdd807b5fa016775f98065b381ca4e to your computer and use it in GitHub Desktop.
Debugging ES6 in VS Code

Debugging ES6 in VS Code

My current editor of choice for all things related to Javascript and Node is VS Code, which I highly recommend. The other day I needed to hunt down a bug in one of my tests written in ES6, which at time of writing is not fully supported in Node. Shortly after, I found myself down the rabbit hole of debugging in VS Code and realized this isn't as straightforward as I thought initially. This short post summarizes the steps I took to make debugging ES6 in VS Code frictionless.

What doesn't work

My first approach was a launch configuration in launch.json mimicking tape -r babel-register ./path/to/testfile.js with babel configured to create inline sourcemaps in my package.json. The debugging started but breakpoints and stepping through the code in VS Code were a complete mess. Apparently, ad-hoc transpilation via babel-require-hook and inline sourcemaps do not work in VS Code. The same result for attaching (instead of launch) to babel-node --debug-brk ./path/to/testfile.js with same babel sourcemap configuration as before.

What works

Stepping back from my naive approach, I figured out that transpiling the code beforehand with sourcemaps (set to inline and both both work in package.json, true doesn't), executing the transpiled entry point and then attaching the VS Code debugger worked:

node_modules/.bin/babel src --out-dir .compiled
node --debug-brk .compiled/path/to/testfile.js

The VS Code attach configuration in launch.json:

    {
      "name": "Attach",
      "type": "node",
      "request": "attach",
      "port": 5858,
      "address": "localhost",
      "restart": false,
      "sourceMaps": true,
      "outDir": "${workspaceRoot}/.compiled",
      "localRoot": "${workspaceRoot}",
      "remoteRoot": null
    }

While this worked, I wasn't satisfied since switching back and forth between VS Code and the command line didn't feel right.

Can we do better and automate the transpilation step?

Yes, we can. For that we have to compile the sources immediately before the debug session starts. The launch configuration provides preLaunchTask as pre-debug-hook for this purpose. First, I've added a script in package.json to compile the sources:

{
    ...
    "scripts": {
        ...
        "compile": "rm -rf .compiled && babel src --out-dir .compiled/src"
    }
    ...
}

Unfortunately, we cannot call this script directly in our launch.json since preLaunchTask references tasks configured in tasks.json under .vscode. So let's create a tasks.json file containing our compile task:

{
  "version": "0.1.0",
  "command": "npm",
  "isShellCommand": true,
  "args": ["run"],
  "showOutput": "silent",
  "tasks": [
    {
      "taskName": "compile",
      "isBuildCommand": false,
      "isTestCommand": false,
      "showOutput": "silent",
      "args": []
    }
  ]
}

This just maps our npm script to a task. We could add other npm scripts here as well. Assumed we have a npm script which runs our test suite, we could configure a corresponding task and run our tests by pressing cmd+shift+T, if we set isTestCommand to true.

Finally, we have to configure the launch section in launch.json accordingly:

    {
      "name": "debug",
      "type": "node",
      "request": "launch",
      "program": "${workspaceRoot}/.compiled/path/to/file.js",
      "stopOnEntry": false,
      "args": [],
      "cwd": "${workspaceRoot}",
      "preLaunchTask": "compile",
      "runtimeExecutable": null,
      "runtimeArgs": [
        "--nolazy"
      ],
      "env": {
        "NODE_ENV": "development"
      },
      "externalConsole": false,
      "sourceMaps": true,
      "outDir": "${workspaceRoot}/.compiled"
    }

Now we can press F5 in VS Code and the file configured under program will be debugged.

Can we do even better?

It still feels cumbersome to edit launch.json each time we want to debug a different file. What about debugging the current opened file in VS Code?

To achieve this we somehow have to get the active file into the program property in launch.json. Since VS Code supports variable substitution in tasks.config and launch.config this is not too hard. So is ${file} replaced with the current opened file -- exactly what we need here. The only problem is, that we have to execute the opened file's compiled counterpart.

One solution to overcome this obstacle is to introduce a wrapper script which gets executed for the debug session instead of the file to debug. This wrapper takes the current opened file as single argument, derives its compiled counterpart location and finally just requires the compiled file. The corresponding launch.json section is shown here:

    {
      "name": "debug current file",
      "type": "node",
      "request": "launch",
      "program": "${workspaceRoot}/runcompiled.js",
      "stopOnEntry": false,
      "args": ["${file}"],
      "cwd": "${workspaceRoot}",
      "preLaunchTask": "compile",
      "runtimeExecutable": null,
      "runtimeArgs": [
        "--nolazy"
      ],
      "env": {
        "NODE_ENV": "development",
        "NODE_PATH": "${workspaceRoot}/.compiled/src"
      }

Note the changed properties program and args. The remaining task is to implement runcompiled.js. A simple solution taking advantage of the fact that our src folder is completely mirrored in .compiled is shown below:

// runcompiled.js
// Takes an uncompiled .js file as argument and executes its pendant in .compiled folder.
// Assumes that source files in cwd are mirrored in folder .compiled.

var path = require('path');
var uncompiledFile = process.argv[2];
var compiledDir = path.resolve(process.cwd(), '.compiled');

if (!uncompiledFile) {
  process.stderr.write('filename missing');
  process.exit(1);
}

uncompiledFile = path.resolve(uncompiledFile);

if (uncompiledFile.indexOf(compiledDir) === 0) {
  process.stderr.write(`file in ${compiledDir} not allowed`);
  process.exit(1);
}

var relativePath = path.relative(process.cwd(), uncompiledFile);
var compiledFile = path.join(compiledDir, relativePath);

require(compiledFile);

With those ingredients in place debugging the current opened ES6 file in VS Code is only an F5 away. Enjoy!

Conclusion

I have shown some approaches to debugging ES6 in VS Code from attaching to a node process running the transpiled sources, over automatically transpilation in a pre-debug-hook, and, finally, improving this further to be able to debug the current opened ES6 file with just a keystroke.

Anyway, I still feel that I may have missed something. If that's the case I'm eager hearing about simpler solutions!

@tiendq
Copy link

tiendq commented Apr 1, 2019

It's much easier now, here my launch.json and task.json files, build:server is a npm script to run babel-node.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Start Debug",
      "program": "${workspaceFolder}/dist/index.js",
      "preLaunchTask": "build",
      "stopOnEntry": true
    }
  ]
}
{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "npm",
      "label": "build",
      "script": "build:server",
      "problemMatcher": []
    }
  ]
}

@gavinmcfarland
Copy link

gavinmcfarland commented Aug 29, 2019

If I'm just using babel to compile my code into a dist folder how can I setup VScode to debug it? I'm just using

babel src -d dist --copy-files && NODE_ENV=test node dist/index.js

to compile the files to a dist folder and then using node to run the application

My .babelrc file has the following in it

{
"sourceMaps": true
}

My launch.json file looks like:

{
	"version": "0.2.0",
	"configurations": [{
		"type": "node",
		"request": "launch",
		"name": "Launch Program",
		"program": "${workspaceFolder}/dist/index.js",
		"sourceMaps": true,
	}]
}

Should the program be pointing at the compiled version or the source code?

@kimfucious
Copy link

I'm glad I came across this gist 😃

The following allows me to launch the debugger from vscode with nodemon and babel-node.

I hope this helps someone.

.babelrc

{
  "presets": ["@babel/preset-env"],
  "env": {
    "debug": {
      "sourceMaps": "inline",
      "retainLines": true
    }
  }
}

launch.json

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "babel-nodemon",
      "program": "${file}",
      "restart": true, // <= important!
      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/nodemon", // <= local path if nodemon not installed globally
      "args": ["--exec", "${workspaceRoot}/node_modules/.bin/babel-node" ],
      "sourceMaps": true,
      "env": {
        "BABEL_ENV": "debug"
      }
    }
  ]
}

@specimen151
Copy link

@kimfucious's solution works for me. First it did not seem to work, but then I saw that you need to have the file open that you want to debug (but this could probably just be changed in the "program" parameter). Thanks.

@alexander9306
Copy link

I just set the runtimeExecutable to babel-node in my default Launch configuration and it seemed to work fine with break points in the editor, like so:

{
  "name": "Launch",
  "type": "node2",
  "request": "launch",
  "program": "${workspaceRoot}/src/index.js",
  "stopOnEntry": false,
  "args": [],
  "cwd": "${workspaceRoot}",
  "preLaunchTask": null,
  "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/babel-node",
  "runtimeArgs": [
    "--nolazy"
  ],
  "env": {
    "NODE_ENV": "development"
  },
  "console": "internalConsole",
  "sourceMaps": false,
  "outFiles": []
}

Thanks to this answer I was able to make work easily, thank you so much

@Doogiemuc
Copy link

Thank you so much for this gist. Lead me in the right direction to Debug Mocha Tests in VS Code with Babel

@micah-akpan
Copy link

Thank you @dchowitz. If you want to debug your ESModules without first transpiling with babel (that is, using on-the-fly), you can use @babelregister: node --inspect-brk --require @babel/register {server_entry_point}

@Osuriel
Copy link

Osuriel commented May 10, 2021

thanks @markacola idk why this works but it does! i wish i understood why but oh well. at least i can finally debug this crap...

@anabellchan
Copy link

anabellchan commented May 27, 2021

The key to debugging in ES6 is to add sourcemaps files separately from the transpiled code. So after setting up babel on your app, make sure to add sourcemaps that Node can use to debug your code.

Mine works with the following:

npm install

  • @babel/cli
  • @babel/preset-env.

babel.config.js

module.exports = {
  presets: [
    ["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }]],
};

package.json

  "scripts": {
    "build": "babel src --out-dir dist --source-maps",
   }

launch.json

   {
      "type": "node",
      "request": "launch",
      "name": "Launch Handler",
      "program": "${workspaceFolder}/dist/handler.js",
      "sourceMaps": true,
    }

Now set a breakpoint on the code then run your debugger for "Launch Handler". The breakpoint will be hit.

@dchowitz
Copy link
Author

Thanks, @anabellchan! Amazing to see that after 5 years, this topic is still a thing 😀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment