Tracking Asynchronous Events in React: How We Built Track-React, a DevTools Extension and Vite Plugin

Debugging asynchronous behavior in modern React applications, especially when it spans across fetches, effects, and timers, can quickly become overwhelming. Errors are often vague and buried deep in chains of callbacks, promises, and side effects. Our team set out to build Track-React, an open-source developer tool and Chrome DevTools extension that helps developers visualize, debug, and better understand async flows in their React apps. Building a tool that integrates seamlessly into user applications without degrading performance or affecting functionality was a major challenge.

In this post, we’ll break down the technical journey behind Track-React: how we inject async tracking logic into React apps without needing a wrapper component, how we traverse ASTs using Babel, how we built a custom Vite plugin and Chrome DevTools panel, and how we solved the many challenges that came with wiring it all together.

The Problem: Invisible Async Chains

Modern frontend development is riddled with asynchronous behavior — fetch, setTimeout, useEffect, chained promises, and more. But JavaScript offers no built-in way to view or reason about async flows over time. Track-React aims to solve this problem by:

  • Tracking async events (like fetch and await) and React hooks in real-time
  • Logging metadata (e.g. timing, status, payload)
  • Visualizing these events in a dedicated Chrome DevTools panel
  • Surfacing async event chains and causality for easier debugging

Our MVP focused on tracking frontend fetch() calls—but quickly evolved to support a broader range of async events and backend calls as well.

Monkey Patching: A Necessary First Step (But Not a Good Final One)

We started by monkey patching global async methods like fetch to inject our logging logic. This got us up and running quickly, but it introduced serious concerns:

  • Monkey patching mutates native APIs at runtime, which could break user applications unexpectedly
  • It doesn’t scale well across multiple async types
  • It’s considered an anti-pattern in production-grade tooling

This approach helped us prove our concept, but we quickly decided we needed something more robust and safer.

From Monkey Patching to Babel + Vite: A Safer, Compile-Time Alternative

Instead of mutating global functions at runtime, we decided to use Babel to inject tracking logic at compile time. This gave us complete control without altering runtime behavior.

The challenge? We had to learn how Babel works under the hood:

  • Babel transforms JavaScript into an Abstract Syntax Tree (AST)
  • We wrote a custom Babel plugin that traverses the AST to identify fetch() calls (and eventually then, useEffect, etc.)
  • We replaced those calls with our custom tracking wrapper retrieveFetchData() to log metadata and send events to our DevTools panel

This approach gave us reliability, safety, and extensibility. It also meant our tool could be development-only — no tracking code makes it into the production build.

Wrapping It in a Vite Plugin

To inject our Babel transformation into the user’s build process, we wrote a Vite plugin that applies only in development mode. But this brought up another issue: how do we distribute our plugin so other users can easily npm install it?

The solution: we moved all plugin-related files into a self-contained directory with its own package.json. This allowed us to build and publish it independently, while maintaining separation from the rest of the Track-React codebase.

Building the Messaging Pipeline

Once async events were being logged in the browser, we needed a way to get them into our DevTools panel. That meant building a messaging pipeline with two layers:

  1. window.postMessage(): Used to send messages from the user application (where the events happen) to the background script
  2. chrome.runtime.sendMessage(): Used to pass events from the background script into the DevTools panel

The challenge here was maintaining a consistent message format across both pipelines, handling async message bursts, and ensuring performance wasn’t affected.

We also had to be cautious of CSP (Content Security Policy) issues and context isolation in Chrome extensions, which often block communication between injected scripts and the extension. Solving this required carefully managing which scripts run where and using message relays.

Creating a Chrome DevTools Panel

Building a DevTools panel that displays async events in real time was a whole project in itself. Chrome DevTools extensions require a specific folder structure, a manifest.json, and a custom HTML + JavaScript bundle to render your panel.

Here’s what we learned:

  • chrome.devtools.panels.create() is how the panel UI is defined
  • We built the panel using React and rendered it via an index.html entry point
  • We had to ensure our panel connected to the messaging pipeline on load and was reactive to incoming async event data

Other Technical Challenges We Solved

In addition to the big-ticket items, here are some smaller but meaningful problems we overcame:

  • AST traversal: Understanding how to traverse and modify nested call expressions in Babel (e.g. nested .then() or async IIFEs) took trial and error.
  • Environment handling: Ensuring that our plugin only runs in development and doesn’t inject tracking code into production builds. We used apply: 'serve' and additional environment-based logic in our Vite plugin files to enforce this.
  • Error-resilient tracking: When cloning and reading a fetch response, errors can occur if the body has already been consumed or if the response isn’t valid JSON. We wrapped our json() parsing in try/catch to prevent these crashes from stopping the tool.
  • Custom import aliases: To ensure users could call import { retrieveFetchData } from 'retrieveFetchData', we created a custom alias inside our Vite plugin using resolveId().

What’s Next for Track-React

Track-React’s MVP already supports full-stack async tracking, including:

  • Frontend fetches
  • Backend route handlers
  • Event metadata (start time, duration, URL, response status, payload)

But we’re just getting started. Upcoming features include:

  • Tracking setTimeout, .then and useEffect chains
  • Displaying async event causality (i.e. which event triggered which)
  • Fetch request calls from the DevTools panel to the OpenAI API, letting users click an “Ask AI” button to summarize or debug async errors in real-time
  • Richer DevTools UI with filtering, sorting, and expanded views for each event

Final Thoughts

Building a developer tool like Track-React has been an incredible learning experience. We moved from runtime hacks to compile-time AST transformations, built a full messaging pipeline, and created a functional Chrome DevTools panel from scratch. Each piece pushed us to understand JavaScript, React, build tools, and the browser environment at a deeper level.

If you’re interested in contributing or learning more, Track-React is open source and actively maintained. We’d love your feedback, contributions, or ideas for new async events to track.

Follow Along

GitHub repo: https://github.com/6tring/track-react
npm Package: https://www.npmjs.com/package/track-react

Leave a Reply

Your email address will not be published. Required fields are marked *

© 2024 Peter Ciluzzi