Command of the debugging process is essential to any good developer's skillset. Any complex problem usually requires being able to spy on the most atomic elements of your application, and understanding how what they’re doing is perhaps changing over time; think race conditions, edge cases, faulty mutations and so forth. In this article, I want to share with you some of the essential methods in my debugging repertoire. This article will focus specifically on debugging a Vue.js application, but the principles can be applied to any javascript application with the proper source mapping.
Chrome comes with a built-in tool called “sources” available in the developer console, accessible by going to View > Developer > Developer Tools in the menu, or by pressing ⌥+⌘+i.
Quick disclaimer: I am actually using Brave, which is identical to Chrome except for additional privacy add-ons.
Already you probably see some folders and files available in the “Page” tab of the Sources tab. These in themselves are moderately useful — if for example you only have access to the live site and not the source code — and in some cases, without a source map you might be forced to use them. But the real power of this tool is realized through the use of source mapping.
Source mapping allows you to connect javascript code executed on the page to un-minified source files on your local hard drive. Typically the build process for Vue.js and other frameworks is to “compile” javascript into a combined minified file, which makes it hard to read and set breakpoints when all of the line breaks are removed and variable names possibly obfuscated. There are a lot of ways different frameworks provide source mapping, but since we are using Vue.js it should be enabled by default for development. See here for more information if you are having trouble enabling source maps.
The next step is to add your project source. Click the “Filesystem” tab next to the “Page” tab and then click the plus icon to add some local source code. Next add the folder with your javascript application, in my case that was resources/js. As you can see on the right of the image, the dev tools have notified me a source map was detected — we’re looking good so far!
Now that you’ve got your source code loaded into Chrome’s developer tools, let’s start doing some basic debugging!
Within the sources tab navigate to the file where you want to set a breakpoint, then in the left margin of the editor click and you should see a green arrow appear. This is the most basic breakpoint, it will be executed (it will freeze the state, more specifically) when your program executes that line.
From here you are able to inspect things by setting watchers or viewing variables that are within the scope of the line you set the breakpoint on. Personally I tend to roll with watchers because then I can specify only what I want to see and I can narrow down large objects to smaller atomic bits of data that I find more helpful. In the right panel of the developer tools, you should see eight accordion tabs, illustrated in the image below.
To set a watcher, click the plus icon in the “Watch” tab and type out the variable you want to view relative to the scope of your breakpoint; typically in a Vue.js app that will probably be this and some additional path information, but it could also be any local variables as well.
Setting breakpoints in this fashion is particularly powerful because you don’t have to go and throw console.log's everywhere and recompile/dirty up your code in order to see how the state is mutating. It’s particularly helpful when a simple behavior might generate 10’s or 100’s of lines of logged values — causing you to have to essentially sift through the state as a block of dead text. Using a breakpoint you can even manipulate values in your watch window in real-time, exploring how a behavior might change with different inputs before you even have to touch the code.
Next, my personal favorite: conditional breakpoints. Setting conditional breakpoints is extremely helpful (and satisfying) when working with a section of code that may get called hundreds or thousands of times (like a loop or function) and you don’t want to manually step through it until you hit the portion of data you want to observe being processed. With conditional breakpoints, you can say essentially “break when x condition becomes true” — such as when the key in loop is “user” or a value contains something you’re looking for. To add a conditional breakpoint, add a normal breakpoint first, then right-click on it and go to “edit breakpoint.”
In the image above I’ve specified that I want the breakpoint to become active only when x === 25. Let’s give it a shot and see if it works. Locally defined variables actually display their values inline as you can see, x is 25 at this iteration of the loop when the breakpoint kicked in.
Last but not least, the feature I want to specifically point out is the “Call Stack” tab in the right panel. This can be a particularly useful tool when you’re perhaps trying to trace the origin of a request, i.e. is this method being called from where I think it is? Maybe an event you think is being called from one place is being called from another place as well; rather than add a trail of console.log statements you can stick a breakpoint at the line in question and see if it’s being called multiple times and what’s calling it. The stack traces can be quite large, but they run in order of the last (most nested) call on top, and the origination point at the bottom of the stack. I tend to need what’s at the top or at the very bottom, and the in-between is typically filled with a lot of innocuous stuff that I don’t care about. Not always the case, but generally speaking it seems to be for me.
There is so much more to be said about this tool but I feel with these basics you’ll be able to delve deeper into the specific tools and how they best serve your development needs.
Thanks for reading, feel free to leave comments or questions!