Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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!

@markacola

This comment has been minimized.

Copy link

@markacola markacola commented Dec 21, 2016

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": []
}
@cellvia

This comment has been minimized.

Copy link

@cellvia cellvia commented Jan 28, 2017

amazing work!!! what a PITA this has been for me. while extremely impressed, I'm going back to sublime until this inordinate number of steps to debug ES6 is ironed out :-/ i have some very simple npm scripts that launch my transpiled code in chrome devtools so i'll just use that. the above feels very fragile, as clever and impressive as it is.

@DavidBabel

This comment has been minimized.

Copy link

@DavidBabel DavidBabel commented Feb 25, 2017

@markacola i don't think the map is respected this way. You have to enable "sourceMaps": true, if you want your debugger act with corrects lines. If you are lucky it can work, but if you have babel things on top of your compiled file, it will not work as expected.

@noam-honig

This comment has been minimized.

Copy link

@noam-honig noam-honig commented Apr 4, 2017

The answer by @markacola works great for me.

@jmjpro

This comment has been minimized.

Copy link

@jmjpro jmjpro commented Aug 31, 2017

@markacola it works for me with node 8.4.0 and the latest version of babel-node. thank you.

@Nirelko

This comment has been minimized.

Copy link

@Nirelko Nirelko commented Sep 9, 2017

I think i found a solution that babel register still can work with, my debug configuration is:

      {
        "name": "Launch Electron",
        "type": "node",
        "request": "launch",
        "stopOnEntry": false,
        "runtimeExecutable": "electron",
        "runtimeArgs": [
            "-r",
            "babel-register",
            "./app"
        ]
      }

the problem was that when you gave the runtime arg "-r babel-register" visual studio ran it as string, '-r babel-register', but when i split the commands in order it not to add the redundant apostrophes it worked.

@es-repo

This comment has been minimized.

Copy link

@es-repo es-repo commented Sep 15, 2017

Works for me:

{

  "type": "node",
  "request": "launch",
  "name": "Debug current test file",
  "program": "${workspaceRoot}/node_modules/tape/bin/tape",
  "args": [
    "-r", "babel-register", "${file}"
  ],
  "env": {
    "NODE_ENV": "test"
  }
}
@mrchief

This comment has been minimized.

Copy link

@mrchief mrchief commented Oct 24, 2017

This works for me:

nodemon ./index.js --inspect --exec babel-node
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node2",
      "request": "attach",
      "name": "Node.js 6+",
      "address": "localhost",
      "port": 9229,
      "stopOnEntry": false
    }
  ]
}

I do have to start the server separately (maybe there is a way to do it via launch config) but that works out OK most of the time since I don't need to attach the debugger all the time. This way I just start the server and Attach the debugger only needed (and it attaches to the running process).

I'm using Node 8.4.0 and babel config is contained within my webpack config. The debugger stops at ES6 code at the right places. No precompile is needed.

@jkettmann

This comment has been minimized.

Copy link

@jkettmann jkettmann commented Oct 27, 2017

@mrchief Thanks a lot! This is working great!

@vanhumbeecka

This comment has been minimized.

Copy link

@vanhumbeecka vanhumbeecka commented Nov 20, 2017

My solution is a combination of the proposed solutions in this thread.
I was able to combine nodemon and babel-node with this configuration:

{
            "type": "node",
            "request": "launch",
            "name": "nodemon",
            "runtimeExecutable": "nodemon",
            "args": [
                "--exec", "${workspaceRoot}/node_modules/.bin/babel-node"
            ],
            "program": "${workspaceFolder}/src/services/mappingService.js",
            "restart": true,
            "console": "integratedTerminal",
            "internalConsoleOptions": "neverOpen"
        },
@zpeterg

This comment has been minimized.

Copy link

@zpeterg zpeterg commented Jan 1, 2018

@markacola
Your solution seems to work for me, except changing "node2" to "node" and turning on sourcemaps.

@Norfeldt

This comment has been minimized.

Copy link

@Norfeldt Norfeldt commented Feb 1, 2018

I'm gonna throw mine into the pot as well

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug",
      "program": "${workspaceFolder}/<path to file>",
      "runtimeArgs": ["--es_staging"]
    }
  ]
}
@MadCow234

This comment has been minimized.

Copy link

@MadCow234 MadCow234 commented May 3, 2018

@markacola
Thank you for this!
If it helps anyone, I made the following changes:

  1. "node2" is no longer supported, this must be changed to "node". VSCode then suggests to add a "protocol" argument as "inspector", which I actually set to "auto".
  2. Initially, I set a breakpoint on a variable and hit F5, but VSCode changed the breakpoint and all my variables were undefined in the debugger. After setting "sourcemaps" to true, all breakpoints are respected and variables are defined. Thank you to @DavidBabel and @zpeterg.
@Izhaki

This comment has been minimized.

Copy link

@Izhaki Izhaki commented May 31, 2018

Note that when using babel when debugging, it is important to enable the sourcemaps and retainLines options.

Here it is as part of .babelrc:

{
  "presets": ["env"],
  "plugins": ["babel-plugin-transform-object-rest-spread"],
  "retainLines": true,
  "sourceMaps": true
}
@AkashBabu

This comment has been minimized.

Copy link

@AkashBabu AkashBabu commented Jul 15, 2018

@markacola Million Thanks 👍
Only Change is that, you must use "sourceMaps": true
Thanks to @Izhaki as well, now VSCode respects the debugging lines.

Suggestion for the people trying to add extra lines only for debugging. Please add the below code in your file to be debugged and add your custom statements inside it. This piece of code will be run only on running the file directly and will not be run if imported.

if (require.main === module) {
      // your custom statements for debugging
}
@Bala-raj

This comment has been minimized.

Copy link

@Bala-raj Bala-raj commented Sep 19, 2018

This works for me:

nodemon ./index.js --inspect --exec babel-node
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node2",
      "request": "attach",
      "name": "Node.js 6+",
      "address": "localhost",
      "port": 9229,
      "stopOnEntry": false
    }
  ]
}

I do have to start the server separately (maybe there is a way to do it via launch config) but that works out OK most of the time since I don't need to attach the debugger all the time. This way I just start the server and Attach the debugger only needed (and it attaches to the running process).

I'm using Node 8.4.0 and babel config is contained within my webpack config. The debugger stops at ES6 code at the right places. No precompile is needed.

Thank you very much for sharing this. We just need to add Lunch via NPM which will make your job easy.

package.json

 "scripts": {
    "start": "node ./.build/index.js",        
    "build": "babel application -d .build",
    "dev": "nodemon ./application/index.js --inspect --exec babel-node"
  },`

launch.json

       {
            "type": "node",
            "request": "launch",
            "name": "Start Dev",
            "runtimeExecutable": "npm",
            "runtimeArgs": [
                "run-script",
                "dev"
            ],
            "port": 9229
        } 
@IanSavchenko

This comment has been minimized.

Copy link

@IanSavchenko IanSavchenko commented Oct 5, 2018

Thank you, @Izhaki! Your tip regarding "retainLines": true in .babelrc made my day!

@dhirajsharma072

This comment has been minimized.

Copy link

@dhirajsharma072 dhirajsharma072 commented Dec 13, 2018

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": []
}

👍

@tiendq

This comment has been minimized.

Copy link

@tiendq 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": []
    }
  ]
}
@limitlessloop

This comment has been minimized.

Copy link

@limitlessloop limitlessloop 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

This comment has been minimized.

Copy link

@kimfucious kimfucious commented Dec 5, 2019

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

This comment has been minimized.

Copy link

@specimen151 specimen151 commented Feb 25, 2020

@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

This comment has been minimized.

Copy link

@alexander9306 alexander9306 commented May 28, 2020

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

This comment has been minimized.

Copy link

@Doogiemuc Doogiemuc commented Dec 2, 2020

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

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