Building Sentry: RAM Bundles
Welcome to our series of blog posts about all the nitty-gritty details that go into building a great debug experience at scale. This time, we’re going to cover RAM bundles, which Sentry recently started to support to improve the debugging experience of React Native projects.
RAM bundle is a new format of React Native application packaging that helps to optimize load times of your app. Starting from sentry-cli 1.43.0
and react-native-sentry 0.43.1
, RAM bundle packages can now be parsed and uploaded to Sentry along with their source maps so that good-looking symbolicated stack traces can be later displayed for all application exceptions your app sends to Sentry.
To understand why additional work was needed to support this new format and how we implemented it, read on.
Bundling and React Native
So you have a JavaScript app, and it’s time for it to enter the real world. In the browser JavaScript realm, this usually means that all your scripts and modules have to be bundled, i.e., combined and minified (e.g., via Webpack) before deploying. As a result, you drastically reduce the number of files (usually down to one) along with the total size of the code you’re serving by removing all information not necessary for production, i.e., mercilessly rewriting function and variables names, deleting code comments, and so on. Bundlers also can produce additional debug information called “source maps” that help map positions in the minified code to original positions in the unminified sources. This is extremely useful when we talk about error tracking and good debugging experience (and this is exactly what we’re talking about here!), and of course, Sentry has source map support.
The whole procedure stays pretty much the same when you’re developing a React Native app. It’s still (React-flavored) JavaScript you write there, right?
But here’s a problem: when your app grows, so does the bundle size, and let’s just say that React Native apps are usually more than a few kilobytes in size. The heavier your app bundle is, the more time it needs to be fully loaded into memory, parsed, and executed, before even showing you a splash screen!
However, the difference is that a React Native app is executed in a different environment — on your mobile device running Android or iOS (coming soon on Windows devices) rather than in your browser. This allows React Native to be smarter about what it loads to memory and when. And this is when RAM bundles come into play: they define a new bundling format that enables on-demand loading of individual app modules, resulting in a smoother user experience.
Let’s take a look at how RAM bundles are structured.
What’s a RAM bundle?
To sum up, a RAM (Random Access Memory) bundle is a special format (or more precisely, a family of formats) that assembles multiple individual JavaScript modules into a single package, which is then used by React-Native to dynamically load more modules on demand.
The official way to bundle your React Native apps at the moment is using Metro Bundler, which currently supports the following bundling formats:
-
Plain: Good old, pure JavaScript bundle. This is the default type that doesn’t offer any optimizations.
-
Indexed RAM Bundle: This format defines a binary file with a header that can be used for quickly finding a specific module. For example, the header can tell us that module number 23 (all JavaScript modules in React Native apps are given numerical identifiers) can be found at offset 320 in the RAM bundle file, and has a length of 430 bytes. How does this help us? React Native runtime now doesn’t need to read the entire bundle file into memory, and can load specific modules only when they are needed (e.g., via inline requires).
-
File RAM Bundle: With this approach, every module is stored in a separate file with the name
js-modules/${id}.js
, where${id}
is the module’s ID. This format is used by default on Android devices but has the same purpose: to have fast access to individual modules in your bundle.
Parsing RAM bundles
The reason Indexed and File RAM Bundles needed additional tooling support in sentry-cli
is because Facebook (the main developer of React Native and Metro) introduced custom extensions to the original index source map format in order to produce proper stack traces for RAM bundles. Without them, a stack trace line from a RAM-bundled app (such as onPress@365.js:1:1157
, or, if put nicely, app:///365.js in onPress at line 1:1157
as in the example below) cannot be properly mapped to the original source location, even with a valid source map.
For example, this is how an unsymbolicated stack trace from a React Native app looks like in Sentry:
One of the extensions, introduced by Facebook, is called x_facebook_offsets
, which is a list that contains line offsets for every module in your app. To continue our example, for module 365.js
, it may contain offset 400
, which would mean that 400
should be first added to the line number in the stack trace (1
) before applying the standard symbolication procedure. After this transformation is applied (it is now performed automatically in sentry-cli
) and all the module files are uploaded to Sentry along with the source map, we get the desired symbolicated stack trace:
Internally, sentry-cli
extracts individual modules and their source maps from the RAM bundle and its “original” indexed source map, then uploads all of these assets to Sentry the server.
Enabling RAM Bundles for your code
To enable RAM bundling for your React Native application, please consult with the official React Native documentation.
Good news for Sentry users: starting from sentry-cli
version 1.43.0
, upload-sourcemaps
command now can detect and automatically parse Indexed RAM bundles and their source maps.
The command also has two additional parameters --bundle
and --bundle-sourcemap
that let users upload all the existing bundle types (plain, indexed RAM bundles, and file RAM bundles) in a more explicit manner. For example, the command for uploading a bundle looks like this:
sentry-cli releases files RELEASE_ID upload-sourcemaps --bundle ios.bundle --bundle-sourcemap ios.bundle.map
Additionally, if you use our react-native-sentry
SDK, RAM bundles parsing and uploading is now automatically supported there starting from version 0.43.1
for both iOS and Android.
The RAM bundle format offers a way to optimize load performance of your React Native app but needs additional support from the bundling and symbolication toolchains. Now that RAM bundles can now be handled correctly by sentry-cli
, we hope that your React Native debugging experience will be taken to another level. Jump to the Sentry documentation to get started!