Graduate Program KB

Step Into Debugging with VS Code and Chrome DevTools

  1. Why Go Beyond console.log()?
  2. Script Debugging in VS Code
  3. Browser Debugging with Chrome DevTools
    1. Debugging DOM Elements & Styles
    2. Console
    3. Extensions
      1. Debugging React Components
      2. Debugging Redux

Why Go Beyond console.log()?

  • By its nature, debugging with console.log requires adding statements that change your program's behaviour. You need to remember where each logging statement is and remove them before committing changes once you're finished debugging. This can be especially problematic if you change existing parts of your program in order to make the information you want to log available.
  • You may waste time writing out and formatting each individual log statement.
  • If the function you're debugging is called many times, your output may become cluttered with messages unrelated to the behaviour you're investigating.
  • Log statements only have access to the variables in their scope, so you may need to put many log statements in different files to gain a comprehensive understanding of the behaviour you're investigating.
  • Every time you want to change your log statements to provide more/different information you need to re-run your program.
  • Severely impractical for debugging aspects of webapps aside from scripting, such as styling, network requests, and the state of the DOM.

Script Debugging in VS Code

More info available in the VS Code Docs

To set up a debugger that fits your use case, create a launch configuration entry in your project's .vscode/launch.json. Intellisense can tell you about all the attributes you can set to configure the debugger's behaviour, but the following attributes are mandatory:

  • type - The type of debugger to use for the launch configuration (e.g. node, docker, chrome, etc)
  • request - The request type of this launch configuration. "launch" will launch an instance of the debugger type and "attach" will attach to a running debugger.
  • name - The reader-friendly name of your configuration.

If you're using TypeScript or a bundler in your project, ensure your build tool outputs source maps for your dev build and set sourceMap to true in launch.json. This enables the debugger to be aware of how the script used for execution relates to the source code you wrote.

To start a debugging session, open the "Run and Debug" side panel via the activity bar, open the drop-down menu at the top, select the name of your configuration and hit play (or press F5). The Debug toolbar will appear at the top of the editor. Clicking icons on this toolbar can trigger the following actions:

  • Continue/Pause execution [F5]
  • Step Over [F10] - Execute the next method as a single command without inspecting its component steps.
  • Step Into [F11] - Enter the next method to follow its execution line-by-line.
  • Step Out [Shift+F11] - When inside a method, return to the earlier execution context by completing the remaining lines of the current method.
  • Restart Execution [Ctrl+Shift+F5]
  • Stop Execution [Shift+F5]

To pause execution at a specific line, create a breakpoint by clicking on the space to the left of the line number in the editor. By right-clicking on a breakpoint, the breakpoint can be edited to:

  • Log a certain message when the breakpoint is reached (essentially a template string, you can use variables in your messages by wrapping them in {curly braces}).
  • Only pause execution when a specified expression is true.
  • Only pause execution when the breakpoint has been reached a specified number of times.
  • Only pause execution if another specified breakpoint has been reached already.

When the debugger pauses execution, the "Run and Debug" panel will update to show the following sections:

  • VARIABLES - Shows the name and value of every variable available in the current scope. Values can be mutated by double clicking on a variable.
  • WATCH - By right clicking on a variable in the above section, you can move them to your watchlist, enabling you to easily monitor how their values change as execution continues. Clicking on the + Button at the top of the section allows you to add expressions to your watchlist, which will be evaluated at every step.
  • CALL STACK - Shows every function call that led to execution reaching the breakpoint. Clicking on a call in the call stack will show you the relevant file (focused on that call's current point of execution) and update the VARIABLES box to show variables available in the context of that call.
  • LOADED SCRIPTS - A file explorer showing the scripts involved in your application.
  • BREAKPOINTS - A checklist of every breakpoint you've set, allowing you to remove or disable your breakpoints in one place.
  • EVENT LISTENER BREAKPOINTS - Present if using browser debugging. Shows a checklist of browser events that you can check to make the debugger pause execution.

The debug console will also become active, providing you with a JavaScript REPL where you can access or mutate variables available in the current scope. The scope available to the debug console depends on the selected call in the call stack, meaning you're not limited to just the scope where the breakpoint was set.

Browser Debugging with Chrome DevTools

While VS Code provides powerful tools for debugging scripts, integrated developer tools like those provided by Firefox, Chrome (and other chromium-based browsers) and Edge provide a vast array of options for profiling and debugging webapps. Chrome DevTools works by utilizing the Chrome DevTools Protocol, an API that allows tools to inspect and instrument basically everything about a Chrome browser instance. DevTools itself consists of a number of tools or "panels" that use the protocol in different ways to monitor and control different aspects of the webpage it was opened on at different levels of abstraction.

Given the sheer amount of tools provided by DevTools cannot cover everything. If you'd like to know more about what you can do with it, check out the documentation, the official tips playlist and this list of tools in the DevTools ecosystem.

Before we continue, some useful shortcuts:

  • There are several ways you can open Chrome DevTools:
    • Open the Elements panel using Ctrl+Shift+C or right-clicking on a page and selecting "Inspect Element".
    • Open the Console panel using Ctrl+Shift+J
    • Open whatever panel you used last using F12 or Ctrl+Shift+I
  • You can navigate between panels with Ctrl+[ and Ctrl+]
  • DevTools has a command palette similar to VS Code which you can bring up with Ctrl+Shift+P
    • You may want to use this to quickly dock DevTools to your preferred side of the screen, or undock it into its own window.
  • You can close and open the "drawer" panel with Esc. You can use this to view multiple panels at once.
  • While in DevTools, you can toggle emulation of different viewport sizes with Ctrl+Shift+M.

Debugging DOM Elements & Styles

The Elements panel is home to the DOM viewer. Note that this panel shows the DOM tree, not the page's HTML, so updates to the DOM will be reflected here.

  • You can manipulate attributes of an element by double-clicking on the attribute. You can cycle through attributes with Tab (forward) and Shift+Tab (backward).
  • If you right-click on an element and select "Break On..." you can create breakpoints that will trigger upon either subtree modification, attribute modification or node removal. Once one of these breakpoints is triggered, script execution will pause and you will be brought to the Sources tab to view the line of JavaScript responsible for manipulating that element.
  • You can also right-click an element to force it into a particular state (:active, :focus, :focus-within, :hover, etc) to test CSS rules with pseudo-classes.
  • Reorder elements by dragging them or selecting them and using Ctrl+↑ and Ctrl+↓.
  • If you select an element, the tabs to the side will update to reflect the state of that node. The Elements panel some tabs that are useful for debugging styles:
    • Styles - View all CSS rules applied to the element from all stylesheets in descending cascade order.
      • Unmatched selectors will be pale
      • Invalid values and invalid declarations will be crossed out with a ⚠️ warning icon beside them
      • Overridden properties will be crossed out with no warning icon
      • Inactive properties will be pale with an 🛈 information icon beside them
      • Non-inherited properties will be pale with no icon
      • If the element uses flexbox or grid, an icon with six rectangles will be next to the rule setting the display property. Click it to immediately change related properties (eg. flex-direction, flex-wrap, align-content etc. for flexbox).
    • Computed - List resolved properties applied to an element as they are rendered by Chrome. You can click the arrow next to a property to see values overridden by the cascade winner. Property values calculated at runtime will be pale.
    • Layout - Modify overlays for flexbox and grid elements.
  • The experimental CSS Overview panel generates a report about the CSS of the current page. These reports show all colours on the page (and reports any contrast issues), all fonts used and all media queries used. Each colour, font and media query in the report can be clicked to see a list of DOM elements that use it. Importantly for debugging purposes, the report also shows unused CSS declarations, grouped by reason.
  • The Animations property records any animations on the page and allows you to capture animations occurring on the page, replay them, scrub through them and modify their length and delay.

Console

Like VS Code's Debug Console, DevTool's Console panel allows you to evaluate JavaScript expressions in the current runtime context. However, DevTool's console provides some extra functionality over a normal REPL:

  • You can access the last 5 selected DOM elements using $0 to $4, where $0 is the most recently selected element
  • Use $_ to use the result of the last expression in a new expression.
  • You can select elements via queries, either using document.querySelector('<query>'[, nodeToSearch]) or the shorthand $('<query>'[, nodeToSearch]). If you want to select all elements that match the query, you can use the shorthand $$() instead.
  • queryObjects(<Constructor>) will show all JavaScript objects used on the page with that constructor
  • Use table(<Object>) to display key-value pairs for an object in an easy to view table.
  • Use keys(<Object>) to get an array of all keys used in an object, or use values(<Object>) to get all values
  • Use copy(<expression>) to copy the output of the command to your clipboard
  • Get a node's event listeners using getEventListeners(<element>)
  • Create an event listener in the console with monitorEvents(<target>, '<eventName>'). This will information about all matching events to the console until you use unmonitorEvents(<target>)
  • Create a function call listener using monitor(<functionName>), which will log information about each call until you use unmonitor(<functionName>)
  • Clicking the eye icon at the top of the panel allows you to add a watch expression that will update live if the expression result changes.

Extensions

You can add custom DevTools panels to your toolset as extensions in the Chrome Web Store.

Debugging React Components

React Developer Tools (available here) is an official React extension that adds two new panels: "⚛️ Components" and "⚛️ Profiler". ⚛️ Components is particularly useful for debugging as it allows you to:

  • View the entire React component tree live in the browser
  • View and edit props passed into the component
  • View hooks used by the component

Debugging Redux

Redux DevTools (available here) is another official extension that enables debugging of Redux. The use of Redux DevTools for Debugging is strongly recommended in the Redux Style Guide, so it's worth familiarising yourself with it. You can either use it within DevTools or right-click on a page to open it in its own window. Per the description in the Redux Documentation, Redux DevTools allows you to view:

  • The history log of dispatched actions
  • The contents of each action
  • The final state after an action was dispatched
  • The diff in the state after an action
  • The function stack trace showing the code where the action was dispatched

Redux DevTools also allows you to do "time-travel" debugging to see how each recorded action effected the state tree and the UI