Thursday, November 21

Debug JavaScript in Production with Source Maps

 
These days, the code you use to write your application isn’t usually the same code that’s deployed in production and interpreted by browsers. Perhaps you’re writing your source code in a language that “compiles” to JavaScript, like CoffeeScript, TypeScript, or the latest standards-body approved version of JavaScript, ECMAScript 2015 (ES6). Or, even more likely, you’re minifying your source code in order to reduce the file size of your deployed scripts. You’re probably using a tool like UglifyJS or Google Closure Compiler.

Such transformation tools are often referred to as transpilers — tools that transform source code from one language into either the same language or another similar high-level language. Their output is transpiled code that, while functional in the target environment (e.g., browser-compatible JavaScript), typically bears little resemblance to the code from which it was generated.

This presents a problem: when debugging code in the browser or inspecting stack traces generated from errors in your application, you are looking at transpiled and (typically) hard-to-read JavaScript, not the original source code you used to write your application. This can make JavaScript error tracking hard.

The solution to this problem is a nifty browser feature called source maps. Let’s learn more.

Source Maps

Source maps are JSON files that contain information on how to map your transpiled source code back to its original source. If you’ve ever done programming in a compiled language like Objective-C, you can think of source maps as JavaScript’s version of debug symbols.

Here’s an example source map:

{
    version : 3,
    file: "app.min.js",
    sourceRoot : "",
    sources: ["foo.js", "bar.js"],
    names: ["src", "maps", "are", "fun"],
    mappings: "AAgBC,SAAQ,CAAEA"
}

You’ll probably never have to create these files yourself, but it can’t hurt to understand what’s inside:

  • version: The version of the source map spec this file represents (should be “3”)
  • file: The generated filename with which this source map is associated
  • sourceRoot: The URL root from which all sources are relative (optional)
  • sources: An array of URLs to the original source files
  • names: An array of variable/method names found in your code
  • mappings: The actual source code mappings, represented as base64-encoded VLQ values

If this seems like a lot to remember, don’t worry. We’ll explain how to use tools to generate these files for you.

sourceMappingURL

To indicate to browsers that a source map is available for a transpiled file, the sourceMappingURL directive needs to be added to the end of that file:

// app.min.js
$(function () {
    // your application ...
});
//# sourceMappingURL=/path/to/app.min.js.map

When modern browsers see the sourceMappingURL directive, they download the source map from the provided location and use the mapping information inside to corroborate the running client-code with the original source code. Here’s what it looks like when we step through Sentry’s original ES6 + JSX code in Firefox using source maps (note: browsers only download and apply source maps when developer tools are open. There is no performance impact for regular users):

Generating the Source Map

Okay, we now roughly know how source maps work and how to get the browser to download and use them. But how do we go about actually generating them and referencing them from our transpiled files?

Good news! Basically every modern JavaScript transpiler has a command-line option for generating an associated source map. Let’s take a look at a few common options.

UglifyJS

UglifyJS is a popular tool for minifying your source code for production. It can dramatically reduce the size of your files by eliminating whitespace, rewriting variable names, removing dead code branches, and more.

If you are using UglifyJS to minify your source code, the following command from UglifyJS 3.3.x will additionally generate a source map mapping the minified code back to the original source:

$ uglifyjs app.js -o app.min.js --source-map

If you take a look at the generated output file, app.min.js, you’ll notice that the final line includes the sourceMappingURL directive pointing to our newly generated source map.

//# sourceMappingURL=app.min.js.map

Note that this is a relative URL. In order for the browser to download the associated source map, it must be uploaded to and served from the same destination directory as the Uglified file, app.min.js. That means if app.min.js is served from http://example.org/static/app.min.js, so too must your source map be served from http://example.org/static/app.min.js.map.

Relative URLs aren’t the only way of specifying sourceMappingURL. You can give Uglify an absolute URL via the --source-map-url <url> option. Or you can even include the entire source map inline, although that is not recommended. Take a look at Uglify’s command-line options for more information.

Webpack

Webpack is a powerful build tool that resolves and bundles your JavaScript modules into files fit for running in the browser. The Sentry project itself uses Webpack (along with Babel) to assemble and transpile its ES6 + JSX codebase into browser-compatible JavaScript.

Generating source maps with Webpack is super simple. In Webpack 4, specify the devtool property in your config:

// webpack.config.js
module.exports = {
    // ...
    entry: {
      "app": "src/app.js"
    },
    output: {
      path: path.join(__dirname, 'dist'),
      filename: "[name].js",
      sourceMapFilename: "[name].js.map"
    },
    devtool: "source-map"
    // ...
};

Now when you run the webpack command-line program, Webpack will assemble your files, generate a source map, and reference that source map in the built JavaScript file via the sourceMappingUrl directive.

Private Source Maps

Until this point, all of our examples have assumed that your source maps are publicly available and served from the same server as your executing JavaScript code. In that case, any developer could use your source maps to obtain your original source code.

To prevent this, instead of providing a publicly-accessible sourceMappingURL, you can serve your source maps from a server that is only accessible to your development team. An example would be a server that is only reachable from your company’s VPN.

//# sourceMappingURL: http://company.intranet/app/static/app.min.js.map

When a non-team member visits your application with developer tools open, they will attempt to download this source map but get a 404 (or 403) HTTP error, and the source map will not be applied.

Source Maps and Sentry

If you use Sentry to track exceptions in your client-side JavaScript applications, we have good news! Sentry automatically fetches and applies source maps to stack traces generated by errors. This means that you’ll see your original source code and not minified and/or transpiled code. Here’s how the stack trace of your unminified code looks in Sentry:

Basically, if you’ve followed the steps in this little guide and your deploy now has generated, uploaded source maps and transpiled files with sourceMappingURL directives pointing to those source maps, there’s nothing more to do. Sentry will do the rest.

Sentry and Offline Source Maps

Alternatively, instead of hosting source maps yourself, you can upload them directly to Sentry.

Why would you want to do that? A few reasons:

  • In case Sentry has difficulty reaching your servers (e.g., source maps are hosted on VPN)
  • Overcoming latency — source maps would be inside Sentry before an exception is thrown
  • You’re developing an application that runs natively on a device (e.g., using React Native or PhoneGap, whose source code/maps cannot be reached over the internet)
  • Avoid version mismatches where a fetched source map doesn’t match the code where the error was thrown

Wrapping Up

You just learned how source maps can save your skin by making your transpiled code easier to debug in production. Since your build tools likely already support source map generation, it won’t take very long to configure, and the results are very much worth it.

That’s it! Happy error monitoring.

Source: Scotch.io

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x