When it comes to production debugging tools (Sentry in our case), these bundles are also not convenient for showing exact (source-code like) stack traces. So, if you want to have readable stack traces, your bundles have to be somehow readable to the debugging tool. Here comes source maps to the rescue. Source maps are files which map content from these transformed bundles to original sources. This way Sentry (and browser as well) can reconstruct the original source and present the stack trace in a readable format.
The most popular asset-bundlers for Rails are Sprockets and Webpack (with usage of Webpacker as a wrapper around). Here we deal with Webpack and Rails 6 app (react-rails) deployed on Heroku. So, when you configure Webpack to generate source maps and then browse your server, you’ll see files with the same name as the original bundles, but with .js.map extension. If you configured the Webpack to send the source maps to Sentry (during the build time), you’ll see the same files in Sentry dashboard (Release Details -> Source Maps -> Artifacts). So far so good… But there is one issue.
Having the source maps generated this way also makes them available for browser. This means browser will be able to reconstruct your source files and anyone would see your source code in browser’s Sources tab, webpack folder. Ok, no problem you might think, let’s just configure Webpack not to generate the source maps in production. This will work in terms of hiding the source code from browser, but at the same time it will also hide it from Sentry. So, the main question here is: how to, at the same time, make the source maps visible to Sentry but not to browser?
It seems "hidden-source-map" option in Webpack config can help us, but unfortunately, this won't work with Sentry since this option doesn't create source maps references in bundle(s) and Sentry needs these references to match bundles with appropriate source maps. So, what we need to do is: generate source maps normally, send them to Sentry, and then, somehow, delete them from the server. Sure, let’s just connect to server after deployment (when source maps are already sent to Sentry), and delete them. Fail again, this won’t work since Heroku’s file system is ephemeral, any change to the file system is discarded when dyno restarts. Hmm, can we maybe use Heroku’s “postbuild” or “cleanup” hooks to interfere the build process and delete the source maps there, before slug is created? Unfortunately no, because source maps are created after these hooks get executed.
What we ended up doing was letting the source maps generation finish normally, and then deleting them in Rails initializer file (what needs to be done as well is deleting the references from bundle; otherwise browsers might return 404 error). So, whenever dyno is started on Heroku, source maps will be deleted from its instance of the app. This works pretty well, although it’s definitely not the most elegant solution.
Some (better) alternatives might be:
- create source maps locally and push them to Sentry (via sentry-cli)
- create a custom buildpack in Heroku (with delete command in there) and run it after default buildpacks are run
- if you use e.g. GitHub CI/CD, set the delete command in the workflow file (yaml) just after source maps are sent to Sentry