How to add sourcemap in React Native for Production?
Asked Answered
F

4

45

I received error log like the following while the app crashed:

Fatal Exception: com.facebook.react.modules.core.JavascriptException: onSelect index.android.bundle:20:7148 onPress index.android.bundle:20:2435

But it's not really helpful for me to trouble shoot. How could I enable source map so that I could track down where the issue is ?

UPDATE 2018 https://docs.expo.io/versions/latest/guides/using-sentry.html Looks promising !

Funambulist answered 11/1, 2016 at 6:2 Comment(0)
S
62

For source mapping here is the way I go about it:

In my bundle command for my production build I tell it to generate a source map:

iOS:

react-native bundle --platform ios --entry-file index.ios.js --dev false --bundle-output ./ios/main.jsbundle --assets-dest ./ios --sourcemap-output ./sourcemap.js

Android - I had to actually modify the android/app/react.gradle file to get source maps generating on release compile. There might be an easier way but basically you find where it builds up the bundle command in the bundleReleaseJsAndAssets method and add the source map bit to it:

if (Os.isFamily(Os.FAMILY_WINDOWS)) {
    commandLine "cmd","/c", "react-native", "bundle", "--platform", "android", "--dev", "false", "--entry-file",
        entryFile, "--bundle-output", jsBundleFileRelease, "--assets-dest", resourcesDirRelease, "--sourcemap-output", file("$buildDir/../../../sourcemap.js")
} else {
    commandLine "react-native", "bundle", "--platform", "android", "--dev", "false", "--entry-file",
        entryFile, "--bundle-output", jsBundleFileRelease, "--assets-dest", resourcesDirRelease, "--sourcemap-output", file("$buildDir/../../../sourcemap.js")
}

The output path looks a bit odd but that puts it at your root level (same spot as iOS. I wanted it that way. You can obviously put it anywhere).

Then once you have an error with the line number that means nothing you run it through the "source-map" NPM package. You could probably get very elaborate with your approach but I simply went with:

var sourceMap = require('source-map');
var fs = require('fs');

fs.readFile('./sourcemap.js', 'utf8', function (err, data) {
    var smc = new sourceMap.SourceMapConsumer(data);

    console.log(smc.originalPositionFor({
        line: 16,
        column: 29356
    }));
});

Where line and column should be replaced withe line and column number from your example output above.

This obviously works best if you have the source maps stored somewhere as the line and column numbers change from build to build as your code changes. It should get pretty close though if you can use you source control setup of choice to go back to the commit that was used to build the app in question and re-generate the bundle with the additional bits to the command to generate the source map.

Stratocumulus answered 12/1, 2016 at 1:0 Comment(11)
I ended up got something similar to this and was looking for remote logging server that can do the source map automatically. docs.getsentry.com/hosted/clients/javascript/integrations/… looks interesting. but didnt manage to get it to work.Funambulist
Ah, that would be interesting. I am just using Crashlytics to log the errors then manually mapping it with the above method when investigating the error. It would be nice to have a service that did it automatically but I have yet to see one.Stratocumulus
I'd love to see this too, and for some reason I don't expect to see support for this added to Crashlytics anytime soon. Someone should build this!Piroshki
On v0.24 worked for me: commandLine "node", "node_modules/react-native/local-cli/cli.js", "bundle", "--platform", "android", "--dev", "${devEnabled}", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, "--sourcemap-output", "${bundleAssetName}.map" Also, You need to use local react.gradle. Issue: github.com/facebook/react-native/issues/7393Bramante
On v0.32 it's simpler to add the args to generate a sourcemap. Just add project.ext.react = [ extraPackagerArgs: [ "--sourcemap-output", file("$buildDir/../../sourcemaps/sourcemap.js")] ] to android/app/build.gradle just before the apply from: line.Dawdle
My outputted sourcemaps returns the wrong values. It misses with some 10-20 lines when I produce an error! Any experiece with this? @DawdleDenticulation
Here a slightly more convenient script. Cut&paste from the log file, and it should create the call stack: gist.github.com/shadowbrush/347e0a1fbda4fa38f15064d8602ffa80Circumscissile
Extending @Dawdle answer, you can keep the sourceMap you created for each version of the app by using this extraPackagerArgs: ["--sourcemap-output", file("$buildDir/../../sourcemaps/sourcemap-${android.defaultConfig.versionName}.js")] and moving the project.ext.react definition and the apply from: "../../node_modules/react-native/react.gradle" line after the android {...} definition.Consumedly
please anyone tell me where is write fs.readFile('./sourcemap.js'... codePiers
just in case it helps anyone github.com/bugsnag/bugsnag-sourcemapsGregoire
new sourceMap.SourceMapConsumer(data) returns a promise, can you update your answer to reflect that? Perhaps this is a new change I don't know. This snippet works well otherwiseErased
T
25

Android inspired by @chetstone's answer

Starting on v0.32 for android, you can modify your android/app/build.gradle to accomplish this. Look for the line

apply from: "../../node_modules/react-native/react.gradle"

Just above this, you will see something like:

project.ext.react = [
    entryFile: "index.js",
]

Modify it to match the following

project.ext.react = [
    entryFile: "index.js",
    extraPackagerArgs: ["--sourcemap-output", file("$buildDir/../../../sourcemap.android.js")]
]

On iOS

Go to your build phases in Xcode for the "Bundle React Native code and images" phase and add:

export EXTRA_PACKAGER_ARGS="--sourcemap-output sourcemap.ios.js"
Threadfin answered 26/3, 2018 at 8:38 Comment(3)
sourcemap file did not created form me. Can there be any reason for that.Unprovided
I get these errors on Android: FAILURE: Build failed with an exception. * What went wrong: Failed to capture fingerprint of input files for task ':cntral:bundleReleaseJsAndAssets' property '$1' during up-to-date check. > Failed to create MD5 hash for file '/Users/me/tmp/skylight-87084.sock' as it does not exist.Cockayne
I created an Issue on react-native for it since I can't seem to find anybody else with the issue: github.com/facebook/react-native/issues/24044Cockayne
B
4

As noted, there's no obvious way to generate the sourcemap file for React Native on iOS. The bundle command is called from react-native-xcode.sh, and there's no provision to add parameters to the bundle command line. But I found a clean way to do it.

react-native-xcode.sh uses the environment variable BUNDLE_CONFIG to specify a config file. If you create an empty config file it has no effect, and then you can add additional CLI parameters.

Create an empty config file.

touch null_config

Set BUNDLE_CONFIG with your config file, and piggyback the --sourcemap-output parameter.

export BUNDLE_CONFIG="null_config --sourcemap-output ./sourcemap.js.map"

When you build, the file sourcemap.js.map will be created.

Basilica answered 28/3, 2018 at 17:57 Comment(1)
can you provide more details on this? where do you place the null_config file? what are it's contents exactly? do you set the env vars in Xcode?Tracheitis
A
4

This is only for iOS.

step 1: Generate sourcemap.js file by using following command.

add this line in package.json file

 "bundle:ios": "mkdir -p ios/{Bundle,source-map}; react-native bundle --platform ios --entry-file index.js --dev false --bundle-output ios/Bundle/main.jsbundle --assets-dest ios/Bundle --sourcemap-output ios/source-map/sourcemap.js" 

Run this command, it will create sourcemap.js file under $PROJECT_DIR/ios/source-map/ folder

$ yarn bundle:ios

Step 2: Create a file sourcemap-decoder.js under $PROJECT_DIR/ios/source-map/

$ cd ios/source-map/

$ touch sourcemap-decoder.js

Content of sourcemap-decoder.js is

const sourceMap = require('source-map'); //(install- npm i source-map)
const fs = require('fs');
const path = require('path');

fs.readFile(path.resolve(__dirname, 'sourcemap.js'), 'utf8', async (err, data) => {
  if (err) {
    console.log('err', err,);
  }
  const consumer = await new sourceMap.SourceMapConsumer(JSON.parse(data));

  console.log(consumer.originalPositionFor({
    line: 1408,
    column: 7762
  }));
});

Step 3: execute the script for decoding

$ node ios/source-map/sourcemap-decoder.js
Arise answered 9/3, 2020 at 10:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.