Source maps for Sentry: setting the UTF-8 encoding.

We use Sentry at TransparentClassroom to monitor errors in our front-end application. It's an excellent tool and has proven very useful, but for the longest time we could not get source maps to work correctly. As a consequence, we'd get woefully useless errors like these:

No source map on Sentry

This article quickly covers how we set up source maps using Webpack and got them working correctly on Sentry, using some fixes that were not obvious and not covered by the Sentry documentation. It took me the better part of a day running into dead ends and chasing my tail before it was working end to end; hopefully this post saves you similar pain.

Setting up source maps using Webpack.

Webpack is an incredible tool, and a pain to understand. Fortunately, getting source maps to work on Webpack "should" only require one line to magically work:

config.devtool = /* your source map compilation setting goes here */  

I followed an excellent post by SurviveJS to get my source maps working. It turns out source maps can be generated through many different means, which vary in speed and quality. The post covers the different kinds here. For our setup, we do the following:

var production = process.env.NODE_ENV === 'production';

var config = {  
  ...
}

if (production) {  
  config.devtool = 'source-map';
} else {
  config.devtool = 'eval-source-map';
}

Checking that it works.

If you're like us and use eval-source-map for development, you will not be outputting an actual .map file. See the (edited) output of our webpack:

Version: webpack 1.14.0  
                                            Asset       Size  Chunks             Chunk Names
                                application.en.js    5.18 MB       0  [emitted]  application.en
                                    manifest.json    3.71 kB          [emitted]

But, happily, it still works magically in the browser:

Development source maps

Notice the correct stack trace when you click on the error, and how it points to the correct file and line number in boom.js.

In production, the output should look something like this:

Version: webpack 1.14.0  
                                                Asset       Size  Chunks             Chunk Names
               application.en-c763e9250b27a905d9e7.js     761 kB       2  [emitted]  application.en
           application.en-c763e9250b27a905d9e7.js.map    4.44 MB       2  [emitted]  application.en
                                        manifest.json    5.04 kB          [emitted]

Gotchas.

Remember to restart your Webpack.

Not doing what you expected? Make sure you restart webpack, before digging in further.

Don't listen to Sentry's setup for source maps.

Some tutorials for setting up source maps, such as this one from Sentry, tell you to manually configure the source map output, like so:

module.exports = {  
    // ... other config above ...
    output: {
      path: path.join(__dirname, 'dist'),
      filename: "[name].js",
      source mapFilename: "[name].js.map",
    }
};

My suggestion: do not do this, it will only end in tears.

Watch out for UglifyJS optimization.

You may be optimizing your UglifyJS plugin for production, in which case you'll need to specify that you still want source maps. We do it at the end in a specific segment of code that adds configuration specific to production, but you can place it wherever you actually set up the plugin:

if (production) {  
  config.plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compressor: { warnings: false },
      source map: true // <-- this is what you need
    }),
  );
}

Setting up Sentry to use your source maps.

TransparentClassroom is served through a Rails server running on a Heroku deployment. Based on the source map setup documentation for Sentry, our source maps should've started working magically, but sadly they weren't. Sentry would report a cryptic error when we looked at the details on a user-generated error (note we use a Cloudfront distribution, but hitting www.transparentclassroom.com had the same effect):

Source file was not 'utf8' encoding: https://d2ps218qravhxv.cloudfront.net/webpack/application.en-c763e9250b27a905d9e7.js.map  

It turns out that Sentry does not automatically assume source maps are UTF-8 encoded (even though Webpack does this for us). This is because, at least with our Rails server, the .map files had a Content-Type that did not specify UTF-8 encoding:

$ curl -I https://d2ps218qravhxv.cloudfront.net/webpack/application.en-c5ebd83387db86032a23.js.map
HTTP/1.1 200 OK  
...snip...
Content-Type: text/plain  # <-- :-(  

While some setups have artifacts served from S3 via Cloudfront, we choose to have Cloudfront capture artifacts on a cache miss; so the first time it will hit our server, after which point it will be cached in CF. This meant we needed to change the Content-Type for our .map files, which turned out to be relatively easy with Rails:

# config/initializers/mime_types.rb

Rack::Mime::MIME_TYPES[".map"] = "text/plain; charset=utf-8"  

Now when we hit the server, we get the correct Content-Type:

$ curl -I https://d2ps218qravhxv.cloudfront.net/webpack/application.en-c5ebd83387db86032a23.js.map
HTTP/1.1 200 OK  
Content-Type: text/plain; charset=utf-8  # :-)  

Making this change FINALLY got souremaps working for us in Sentry:

Working source maps in Sentry

Wrapping up.

Configuring source maps to work correctly in Sentry has been so valuable to us in our debugging process, as it's decidedly unpleasant to dig into minified, webpacked code and attempt to figure out it's corresponding location in your codebase. Hopefully this article has served you well! Let me know your thoughts or if you have any other insights to share!