Frontend developers live in the future. Complex single-page web applications are rarely written in raw JavaScript and CSS anymore, but that’s still what browsers run. So if you’re using React JSX, TypeScript, or Sass, then your build tool is transpiling and minifying that code into standard JS and CSS that can run in your browser.
The problem is that when an error occurs, it’s in the transpiled/minified version of the file—on a completely different line number from the error in the original file. This has the potential to make debugging a nightmare. Fortunately, when you’re using your local developer toolkit and your browser console, you never have to worry about this. Errors magically get mapped over and point to the right place in your original file.
How does this work? We’ll explain—and show you how we found a library that you can use to perform the error mapping yourself, since that’s exactly what we needed to do here at CodeSignal.
Background
First, a bit of context. CodeSignal is a cloud platform that candidates and interviewers use to conduct live technical interviews and take-home assessments. Our goal is to build the most realistic remote interview experience out there. When candidates use our IDE, we want them to feel like they’re developing on their own machines so they’re comfortable and perform at their best.
For programmers, the process of debugging when something goes wrong is just as important as writing the code—because something will always go wrong. When a program throws an error, being able to see the line number it maps to in the source code is the first step to figuring out what happened. For our general-purpose programming languages, we often don’t have to do anything special to make this work in CodeSignal’s IDE. If a candidate wants to run a Java program, we compile and run it in a virtual environment using a microservice that we call a coderunner. If the program throws an error, the coderunner gives us the line number, and we simply show that directly to the developer.
But with frontend code, the flow looks a bit different. We built a live preview so candidates and interviewers can interact with the UI and see instant updates as they make changes. So rather than executing the code on a coderunner, we’re running it in the browser.
After writing some code to handle errors thrown by the browser and show them in our console, we immediately ran into an issue with line numbers. Our logs might say an error occurred on line 35—when the candidate’s code was only 20 lines long. This is because of the transpilation that’s happening in the browser to turn modern JS and CSS into the raw versions. Here’s an example of how different the generated file can look from the source:
How browsers solve this problem
Browsers are running raw JS and CSS, but when you’re debugging in the browser console, you magically see the right line numbers for errors in your Typescript or Less files. How does your browser manage this? By using a source map, which is sort of like a translation manual between one file and another.
A source map is a standard that can be used with any language. Here’s a simple example:
{
"version" : 3,
"file": "out.js",
"sourceRoot": "",
"sources": ["foo.js", "bar.js"],
"sourcesContent": [null, null],
"names": ["src", "maps", "are", "fun"],
"mappings": "A,AAAB;;ABCDE;"
}
This source map is made up of several parts:
- file specifies the file name of the generated code (e.g. the transpiled JS code)
- sourceRoot, sources, and sourcesContent are all ways to access the source file(s) (e.g. the TypeScript)
- names are function/symbol names used by mappings
- mappings is where the magic happens. This is a VLQ Base64 encoded string that, when decoded, will show you exactly how and where the sources map into the generated file. In other words, this will show you which lines in the original files point to which lines in the generated file.
Where do source maps come from? Your build tool generates them when it’s doing the trasnpilation, and tells the browser where to find them with a comment at the bottom of the file that looks like:
//# sourceMappingURL=<url>
Making this work for CodeSignal
For developers solving frontend tasks on our platform, our platform is essentially the build tool instead of a tool like Webpack. So, we had to start by generating source maps for all the frontend languages that we transpile. Including those source maps as part of the preview made it possible for the browser to automatically output the right error numbers in its console. But this was just the first step—it didn’t fix our main issue. We still had the wrong line numbers in our IDE console, where we catch and display errors thrown by our developers’ frontend applications.
It would be great if we could just grab the source-mapped errors from the browser, but unfortunately that’s not possible. So in addition to doing a build tool’s work, we had to explore how to do the browser’s work, too.
Luckily, we found a great library that could do exactly what we needed: consume a source map and decode the mappings to understand how the line numbers match up. The library we use is source-map by Mozilla. It’s always nice when you can utilize something open source rather than having to build an intricate custom solution, and even better when a library for replicating browser functionality is maintained by the team that built Firefox. Using this library, we were able to convert the transpiled line numbers in errors that we catch and display them to the user with a more helpful stack trace.
In summary, we had to implement some logic to a few places in order to get our error line numbers matching up in the console for frontend applications.
- When the candidate runs something like TypeScript, JSX, or Less in CodeSignal’s live preview in the browser, we’re building a transpiled version of the code that’s just standard JS/CSS.
- When we transpile the source code, we generate a source map that explains how to translate back to the original files, and we tell the browser where to find it.
- When we catch an error in the user’s code, we use the source maps ourselves to map the line numbers back to the right place in the original file.
Source maps are pretty darn useful! If you’re ever trying to debug a cryptic stack trace deep in some Node server logs, and you’re able to emit source maps for your applications, consider giving the source-map library a shot.
Conclusion
We hope you’ve found this article interesting and maybe learned something new. If you like digging into the inner workings of IDEs and build tools, CodeSignal could be a great place for you. We’re building the #1 interview tool for technical recruiting and we’re looking for passionate folks to join—check out our jobs page.
Michael leads the product engineering team at CodeSignal.