Skip to content

Mercateo/how-to-debug-javascript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

11 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

How to debug JavaScript?

If you're happy using console clap your hands! πŸ‘

I know there are many tutorials about this topic on the internet. However I still get asked this question quite a lot from non JavaScript developers whenever they need to write some JavaScript code: "How do I actually debug my JavaScript code?"

I guess they ask, because they found old resources about this topic or they found resources which use a different tooling or they found a tutorial for debugging JavaScript inside the browser, but not Node or... There are probably many reasons. So here is my attempt to answer their question.

In this article you'll get

  • a short introduction to debugging JavaScript
  • references to more in-depth explanations
  • examples for debugging JavaScript in the browser and in Node
  • examples with and without tooling1

1 Tooling refers to our setup here at Mercateo. We use our ws tool (which uses TypeScript, Babel, Webpack and more) for building our projects and many of our developers use VS Code as their IDE.

You'll not get

  • a basic introduction to JavaScript itself
  • explanations about memory and performance profiling
  • explanations about debugging non JavaScript stuff in your application (CSS, HTML, network...)
  • explanations about configuring tools (e.g. for source map support)

Table of contents

  1. Don't fear the console
    1. A simple web project
    2. A more complex web project
    3. Web projects with a build setup
    4. Logging in a Node project
  2. Leverage breakpoints
    1. Breakpoints in a web project
    2. Breakpoints in a Node project or projects with a build step
  3. Debugging in VC Code
  4. Advanced topics

Don't fear the console

A simple web project

Hands down. I use the console for 90% of my debugging needs. It just fits my habbits well and most of the time I immediatelly see what's the problem. I don't know why many people feel bad about using the console for debugging purposes. Probably because in other programming languages printing to the console lacks a lot of features. Thankfully the console is really powerful in JavaScript - espacially for browser environments.

Let's get started with a very simple example.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Debugging JavaScript</title>
</head>
<body>
  <h1>Debugging JavaScript</h1>
  <p>Open you browser dev tools to see the console output.</p>
  <script src="./index.js"></script>
</body>
</html>
var someObject = {
  foo: {
    bar: {
      baz: 'hello'
    }
  }
};

console.log(someObject);

The same example can be found inside ./browser-console. Just open in the HTML with a browser of your choice and open the browser console.

πŸ’‘ All mordern browsers have usually everything you need for basic debugging purposes. But this was not allways the case and some special developer tools are still better in browser X than in browser Y. Historically most developers I know use Google Chrome for debugging their applications. But I'd like to point out Firefox which has awesome developer tools as well. Espacially the debugger called debugger.html itself is nice, because it is a standalone web application written in React.

This is how the browser with the open console looks like in Chrome:

chrome console

And this is Firefox:

firefox console

As you can see you get the basic object printed to the console (1) and you can see the origin of the output (2) as script-name:row(:column). Note that 1 and 2 are interactive elements. (Also note that I'll use screens from Chrome from now on - except when I'd like to point out something special in the Forefox debugger.)

If you click on 1 you can navigate into the object and you can also see properties which we didn't added, because they are on our objects prototype (in this case just __proto__).

chrome console

If you click on 2 you'll jump to the "sources panel" in Chrome or "debugger panel" in Firefox where you can inspect the source code and create breakpoints.

chrome sources panel

We'll spend more time here in the next chapter. For now let's stick with the console.

A more complex web project

Let us swith to a more complex project now which you can find in ./browser-console-complex or by copying the following code:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Debugging JavaScript</title>
</head>
<body>
  <h1>Debugging JavaScript</h1>
  <p>Open you browser dev tools to see the console output.</p>

  <button id="log-element">Log element</button>
  <button id="log-time">Log time</button>

  <script src="./index.js"></script>
</body>
</html>
var logElementButton = document.getElementById('log-element');
logElementButton.addEventListener('click', function onClick(event) {
  console.log(event, logElementButton);
  console.dir(logElementButton);
});

var logTimeButton = document.getElementById('log-time');
logTimeButton.addEventListener('click', function onClick(event) {
  var msg = 'getBoundingClientRect().height';
  console.time(msg);
  logTimeButton.getBoundingClientRect().height;
  console.timeEnd(msg)
});

Open the index.html again and click on the "Log element" button. It should look like this:

chrome log element

Here we can notice a couple of things.

  1. console.log can log as much params as you want (here: event and logElementButton).
  2. The logged event is labeled with a descriptive MouseEvent, which is the name of its constructor.
  3. If you console.log a DOM element it is logged as "HTML markup".
  4. There are multiple ways to log a variable. console.dir is another one which logs the DOM element as an interactive list of its properties.

That's all we can see from the screenshot alone, but there is more under the hood.

If you hover over the console.loged DOM element the element will actually be highlighted in the screen, but not for console.dir. You can also right click on the logged DOM element (via console.log or console.dir) and click on "Reveal in Elements panel" to jump to exactly this element in the DOM view.

chrome highlighted element

If you right click on the console.dir'ed element you can also see the option to "Store as global variable". (This should work in nearly all cases, not just for logged DOM elements. Not sure why this isn't an option in the console.log'ed DOM element.)

chrome global var

Now you can access this global var (in my case its called temp1) as often as you want in your console. For example we could just add another event listener to it and use a new console method called count while doing so:

temp1.addEventListener('mouseover', function onHover() { console.count('hovered'); })

chrome event listener

If you hover over our "Log element" button you should see the new output. count just counts how often the line 'hovered' was logged.

chrome count

If you console gets cluttered by too much output just click the stop icon in the left top corner to clear you current console (or call console.clear()).

chrome clear console

Now press our "Log time" button a couple of times. You should see an unput similar to this:

chrome perf

As you can see we made some basic performance testing with console.time and console.endTime. While this is definitely not for complex scenarios you can already gather useful information from these methods. (E.g. we can see that getBoundingClientRect().height gets faster over time - probably because the data gets cached and optimized when we run this function multiple times without altering our layout.)

πŸ’‘ The difference we saw in console.log and console.dir can be seen in a lot of places - not just DOM elements. In general the log represantation is a simpler view ("stringified") while the dir representation is more useful, if you wan't to do more with the variable besides reading.

chrome dir vs log

πŸ’‘ In many small cases Firefox behaves differently than Chrome here and other browsers will have their own distinct behaviour, too. Try out different tools and see what fits you. In Firefox the difference between console.log and console.dir is way less explicit. It just alter the initial "look" of the logged variable, but we can swith between the log und dir represantation on the fly. It is also possible here to store the console.log'ed DOM element as a globar variable and the DOM element is highlighted when we hover of the console.dir'ed DOM element as well. IMHO Firefox behaviour is more usefull in these cases.

πŸ’‘ Chrome dev tools offer some special functions. For example you can use monitor(someFunction) and every time someFunction is called, this will be logged to your console including all params. Or you can use queryObjects(constructor) to get all instances of that constructor. E.g. when you call queryObjects(Array) you'll get all arrays which are currently used in your application. Read this article to get more information about how to use the console.

There is much more to discover about the console alone. Checkout the official documentation about the console from Chrome and Firefox.

Web projects with a build setup

Many JavaScript projects use a build step. You use Webpack, TypeScript, Babel or any other compiler/transpiler/preprocessor. The point is: the code you wrote is not necessarly the code that runs inside the browser or Node. Therefor the lines you see in your logs and stack traces don't match the places where they appear in your source code. Thankfully there is a technology to solve that: Source Maps. Source Maps an enable debugging tools to match the compiled code back to your original source code. This feature should be enabled in your tooling setup.

Just a quick example which you can find inside ./browser-console-build. This is the old example just "converted" to TypeScript and build with our ws tool. Install dependencies with $ yarn and run it with $ yarn start.

Now you should see your .ts file - not the .js file - in the console output.

chrome browser console build

What the ws tool does is basically configure TypeScript, Babel, Webpack and co. correctly to include Source Maps in your compiled code.

Logging in a Node project

Logging a variable inside a Node project is by default less useful than inside a browser, because the terminal just lacks the interactive features of browser developer tools. You can see the limited output if you run the following example (also available inside ./node-console):

var someObject = {
  foo: {
    bar: {
      baz: {
        hello: 'foo'
      }
    }
  }
};

console.log(someObject);
$ node index.js
{ foo: { bar: { baz: [Object] } } }

You can hack around this by using a "pretty printer" which formats your output (occasionally JSON.stringify(someObject, null, 2) or require('util').inspect(someObject, { depth: 100 }) can help here to avoid installing additional dependencies), but don't worry! We can actually use the browser developer tools we just introduced to debug Node applications as well 😍

To do that we just need to pass the --inspect flag to Node like this: $ node --inspect index.js. Now we are able to open our Chrome developer tools so they can connect to the running Node program. But because our Node script immediately exits after console.log, we don't have enough time to do so. That's why we also need to pass the --debug-brk flag which sets a breakpoint to our script befor it runs so we have enough time to open and configure our browser dev tools. (πŸ’‘ Since Node v7 you can also use --inspect-brk instead of --inspect --debug-brk.)

Now you should see the following output:

$ node --inspect --debug-brk index.js
Debugger listening on port 9229.
Warning: This is an experimental feature and could change at any time.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/remote/serve_file/@60cd6e859b9f557d2312f5bf532f6aec5f284980/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229/9557375c-e101-4100-ac8c-8860d8a9dca5

You can actually ignore the URL in the output and just open chrome://inspect in you Chrome browser. You should see all available "debugging targets" - but there probably is just one target for the script you just started. Now click on "inspect".

chrome inspect

You should now be in the "sources" panel. Click on the blue arrow to run the program.

chrome inspect run

Now you can switch to the "console" and you should be able to see the output from your Node program.

chrome inspect output

πŸ’‘ Source Maps work for Node projects as well.

With --debug-brk we also saw the first usage of a breakpoint so let's move on to learn more about them!

Leverage breakpoints

Breakpoints in a web project

Now I'll introduce you to several ways how you can set breakpoints in you application. We need another example for that which you can find inside ./browser-breakpoints or by copying the following code:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Debugging JavaScript</title>
</head>
<body>
  <h1>Debugging JavaScript</h1>
  <p>Open you browser dev tools to test the breakpoints.</p>

  <button id="break-debugger">Break with `debugger`</button>
  <button id="break-manually">Break manually</button>
  <button id="break-on-error">Throw error</button>

  <script src="./index.js"></script>
</body>
</html>
var breakDebuggerButton = document.getElementById('break-debugger');
breakDebuggerButton.addEventListener('click', function onClick(event) {
  debugger;
});

var breakManuallyButton = document.getElementById('break-manually');
breakManuallyButton.addEventListener('click', function onClick(event) {
  console.log('Place a breakpoint here.');
});

var breakOnErrorButton = document.getElementById('break-on-error');
breakOnErrorButton.addEventListener('click', function onClick(event) {
  throw 'You are not allowed to click this.';
});

Probably the easiest way to add a breakpoint is by using debugger;. Just write it down wherever you want to investigate something. Very much like you used console.log(). If you open your dev tools and click on the "Break with debugger" button you should see something like this:

chrome breakpoint

Your website grayed out and overlay appeared (1). When you click on the blue arrow your application continues as usual. If you click on the black arrow with the dot underneath it you'll skip the next function call (not really meaningful in this example).

You'll also see that your dev tools switched to the "Sources" panel and you can see your source code with the values of some of your variables inlined (2, here: event = MouseEvent). This is even more powerful, if you hover over a variable. It will show you a nice navigatable view for your variable - very much like if you would have used console.log.

chrome breakpoint hover

But one of the most powerful tools can be found on the right side (3). Here you can investigate the call stack or things like your current scopes. Very powerful. You should play around a little bit with this view to get the hang of everything.

Writing debugger; somewhere in your application isn't the only way to set a breakpoint. An alternative where you don't need to actually change the source code - and therefor can be used on just any page you browse - is by clicking on a specific line in the column left to your source code.

chrome breakpoint hover

You can see that this line now contains a breakpoint by looking at the blue arrow. If you click on "Break manually" now you'll see that your application is paused again.

There are sooooo many ways to set a breakpoint. We'll have a look on two more ways, before we switch to next chapter.

Let us switch to the "Elements" panel. We can create a breakpoint even here. Try to find the "Break manually" button in the elements tree view. Right click on the element and go to "Break on... > attribute modifications":

chrome breakpoint elements

You'll see that a small blue dot appeared next to your element:

chrome breakpoint elements on

If you write something like document.getElementById('break-manually').setAttribute('data-foo', 'bar') in your console now, you'll see that your application will run into your configured breakpoint, because we modified the attributes of our element.

Now to the last example which I personally use a lot: "Pause on exceptions". Try to click on the "Throw error" button. You should see the following error in your console:

chrome throw error

Who hasn't seen an application which suddenly throws an error like this? 😏 Imagine this error happens deep in your application or somewhere in a framework you use. How to get to this place where the error was thrown? I'm glad you asked! Just click on the "pause" symbol in the upper right of your "Sources" panel. (Note the checkbox below - we can even pause on caught exceptions, if we want to.)

chrome breakpoint exception

If you click on the "Throw error" button again you'll see our familiar paused application state. πŸŽ‰

chrome breakpoint exception pause

Breakpoints in a Node project or projects with a build step

TODO

Debugging in VS Code

TODO

Advanced topics

TODO


Thank you for reading so far β™₯ I hope you learned something new on the way.

If you found any errors or typos I'd be happy to get a PR.

About

To console.log or to not console.log?

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published