Skip to content

Commit 38cd915

Browse files
author
Konrad Dysput
committed
react-native: Simplify react-native source map injection flow
1 parent f25156f commit 38cd915

File tree

10 files changed

+225
-70
lines changed

10 files changed

+225
-70
lines changed

examples/sdk/reactNative/android/app/build.gradle

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
apply plugin: "com.android.application"
22
apply plugin: "com.facebook.react"
3+
apply from: "$rootDir/../node_modules/@backtrace/react-native/android/upload-sourcemaps.gradle"
4+
35

46
/**
57
* This is the configuration block to customize your React Native Android app.
@@ -47,7 +49,7 @@ react {
4749
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
4850
//
4951
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
50-
// hermesFlags = ["-O", "-output-source-map"]
52+
hermesFlags = ["-O", "-output-source-map"]
5153

5254
/* Autolinking */
5355
autolinkLibrariesWithApp()
@@ -116,3 +118,9 @@ dependencies {
116118
implementation jscFlavor
117119
}
118120
}
121+
122+
tasks.matching {
123+
it.name.startsWith("assemble") || it.name.startsWith("build")
124+
}.configureEach { task ->
125+
task.finalizedBy("uploadSourceMapsToBacktrace")
126+
}

examples/sdk/reactNative/ios-hermesc.sh

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/bin/bash
2+
3+
# Script responsible for preprocessing source maps with debugid and uploading it to Backtrace via backtrace-js.
4+
# Usage: ./ios-sourcemap-upload.sh <source_map_file_path> <debug_id_file_path> <backtrace_configuration_path
5+
# Parameters:
6+
# <source_map_file_path> (Required) Path to the source map file.
7+
# <debug_id_file_path> (Required) path to generated backtrace debug id.
8+
# <backtrace_configuration_path> (Required) Path to the .backtracejsrc configuration file.
9+
#
10+
# Adjusting metro configuration is required in order to correctly use debug_id available in the debug_id_file_path.
11+
12+
13+
set -e
14+
set -x
15+
16+
if [ -z "$1" ]; then
17+
echo "Error: Missing path to the source map file."
18+
exit 1
19+
fi
20+
21+
source_map_file_path="$1"
22+
23+
# Check if the file exists
24+
if [ ! -f "$source_map_file_path" ]; then
25+
echo "Error: File '$source_map_file_path' does not exist."
26+
exit 1
27+
fi
28+
29+
DEBUG_ID_PATH_ENV="DEBUG_ID_PATH"
30+
31+
debug_id_file_path=${!DEBUG_ID_PATH_ENV:-${2:-}}
32+
33+
if [ ! -f "$debug_id_file_path" ]; then
34+
echo "Error: File '$debug_id_file_path' does not exist."
35+
exit 1
36+
fi
37+
38+
if [ -z "$3" ]; then
39+
echo "Error: Missing path to the .backtracejsrc file."
40+
exit 1
41+
fi
42+
43+
backtrace_configuration_path="$3"
44+
45+
debug_id=$(<"$debug_id_file_path")
46+
47+
jq ". += {\"debugId\": \"$debug_id\"}" "$source_map_file_path" > tmp.map && mv tmp.map $source_map_file_path
48+
49+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
50+
51+
# path to react-native module dir relative from this script
52+
backtrace_js_path="${script_dir}/node_modules/.bin/backtrace-js"
53+
54+
echo "Backtrace JS PATH $backtrace_js_path"
55+
56+
# check and assign NODE_BINARY env
57+
source "$REACT_NATIVE_PATH/scripts/node-binary.sh"
58+
59+
# run backtrace-js on bundle
60+
"$NODE_BINARY" "$backtrace_js_path" upload \
61+
-p "$source_map_file_path" \
62+
--config "$backtrace_configuration_path"

examples/sdk/reactNative/ios-sourcemaps.sh

Lines changed: 0 additions & 32 deletions
This file was deleted.

examples/sdk/reactNative/ios/reactNative.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@
269269
);
270270
runOnlyForDeploymentPostprocessing = 0;
271271
shellPath = /bin/sh;
272-
shellScript = "set -e\nset -x\n# destination source map directory\nSOURCE_MAP_DIR=\"$(pwd)/../build\"\nmkdir -p $SOURCE_MAP_DIR\n\nexport SOURCEMAP_FILE=\"$SOURCE_MAP_DIR/main.js.map\";\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n# use hermesc script provided by Backtrace to populate source maps\n# if you dont use hermes support, please skip this step.\nexport HERMES_CLI_PATH=\"$(pwd)/../ios-hermesc.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n\n# copy javascript build output to the build directory\ncp \"$CONFIGURATION_BUILD_DIR/main.jsbundle\" $SOURCE_MAP_DIR \n\nPROCESS_SOURCEMAPS_SCRIPT=\"$(pwd)/../ios-sourcemaps.sh\"\nexport BACKTRACE_JS_CONFIG=\"$(pwd)/../.backtracejsrc\"\nexport BACKTRACE_JS_BUNDLE_PATH=\"$SOURCE_MAP_DIR/main.jsbundle\"\n \n# process source map with javascript code\n/bin/sh -c \"$WITH_ENVIRONMENT $PROCESS_SOURCEMAPS_SCRIPT\"\n";
272+
shellScript = "set -e\nexport SOURCEMAP_FILE=\"$(pwd)/../main.jsbundle.map\"\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n\nSOURCE_MAP_UPLOAD=\"$(pwd)/../ios-sourcemap-upload.sh\"\nBACKTRACE_JS_CONFIG=\"$(pwd)/../.backtracejsrc\"\n/bin/sh -c \"$SOURCE_MAP_UPLOAD $SOURCEMAP_FILE $TARGET_BUILD_DIR/.backtrace-sourcemap-id $BACKTRACE_JS_CONFIG\" \n\n";
273273
};
274274
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = {
275275
isa = PBXShellScriptBuildPhase;
Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
1+
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
22
const path = require('path');
3+
const backtraceSourceMapProcessor = require('@backtrace/react-native/processSourceMap');
34

45
/**
56
* Metro configuration
@@ -8,12 +9,16 @@ const path = require('path');
89
* @type {import('metro-config').MetroConfig}
910
*/
1011
const config = {
11-
watchFolders: [
12-
path.resolve('../../../packages/react-native'),
13-
path.resolve('../../../packages/react-native/node_modules'),
14-
path.resolve('../../../node_modules'),
15-
path.resolve('../../../packages/sdk-core'),
16-
],
12+
watchFolders: [
13+
path.resolve('../../../packages/react-native'),
14+
path.resolve('../../../packages/react-native/node_modules'),
15+
path.resolve('../../../node_modules'),
16+
path.resolve('../../../packages/sdk-core'),
17+
],
18+
serializer: {
19+
async customSerializer(entryPoint, preModules, graph, options) {
20+
return backtraceSourceMapProcessor.processSourceMap(entryPoint, preModules, graph, options);
21+
},
22+
},
1723
};
18-
1924
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

examples/sdk/reactNative/package-lock.json

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/sdk/reactNative/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@babel/preset-env": "^7.20.0",
1919
"@babel/runtime": "^7.20.0",
2020
"@backtrace/javascript-cli": "file:../../../tools/cli",
21+
"@backtrace/sourcemap-tools": "file:../../../tools/sourcemap-tools",
2122
"@react-native/babel-preset": "0.75.3",
2223
"@react-native/eslint-config": "0.75.3",
2324
"@react-native/metro-config": "0.75.3",
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import groovy.json.JsonBuilder
2+
import groovy.json.JsonSlurper
3+
4+
5+
tasks.register("uploadSourceMapsToBacktrace") {
6+
group = "backtrace"
7+
description = "This task uploads source maps generated by the react-native builder to Backtrace based on the .backtracejsrc file."
8+
9+
doLast {
10+
// Fetch the build variant (debug or release)
11+
// Default to 'release' if not set
12+
def buildVariant = project.hasProperty("buildVariant") ? project.buildVariant.toLowerCase() : "release"
13+
14+
def debugIdPathEnv = System.getenv("DEBUG_ID_PATH")
15+
16+
def debugIdFile = debugIdPathEnv ? file(debugIdPathEnv) : layout.buildDirectory
17+
.dir("intermediates/sourcemaps/react/${buildVariant}/.backtrace-sourcemap-id")
18+
.get()
19+
.asFile
20+
21+
if (!debugIdFile.exists()) {
22+
println("Backtrace: Cannot find .backtrace-sourcemap-id debug info.")
23+
return
24+
}
25+
26+
def sourcemapDestinationDirectory = layout.buildDirectory.dir("generated/sourcemaps/react").get().asFile
27+
def mapFiles = fileTree(dir: sourcemapDestinationDirectory, include: '**/*.map').files
28+
def mapFile = mapFiles ? mapFiles.iterator().next() : null
29+
30+
if (!mapFile) {
31+
println("Backtrace: Cannot find final source map file.")
32+
return
33+
}
34+
35+
def jsonSlurper = new JsonSlurper()
36+
def mapData = jsonSlurper.parse(mapFile)
37+
mapData.debugId = debugIdFile.text
38+
def jsonBuilder = new JsonBuilder(mapData)
39+
mapFile.text = jsonBuilder.toPrettyString()
40+
41+
// Execute Backtrace JS CLI to process upload processed source maps
42+
def command = ["npx", "--yes", "@backtrace/javascript-cli", "upload", "-p", mapFile.absolutePath]
43+
exec {
44+
workingDir file("$rootProject.projectDir/..")
45+
commandLine command
46+
}
47+
}
48+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const bundleToString = require('metro/src/lib/bundleToString');
2+
const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle');
3+
const CountingSet = require('metro/src/lib/CountingSet').default;
4+
const fs = require('fs');
5+
const path = require('path');
6+
const { DebugIdGenerator, SourceProcessor } = require('@backtrace/sourcemap-tools');
7+
8+
const DEBUG_ID_PATH = process.env.DEBUG_ID_PATH;
9+
10+
/**
11+
* Process metro build with source map support powered by Backtrace.
12+
*/
13+
function processSourceMap(entryPoint, preModules, graph, options) {
14+
const bundle = bundleToString(baseJSBundle(entryPoint, preModules, graph, options));
15+
16+
// development build - skip source map upload
17+
if (graph.transformOptions.hot || graph.transformOptions.dev) {
18+
return bundle;
19+
}
20+
21+
// no source map option set
22+
const sourceMapOutputPathParameter = process.argv.indexOf('--sourcemap-output');
23+
if (sourceMapOutputPathParameter === -1) {
24+
return bundle;
25+
}
26+
27+
const sourceMapOutputPath = process.argv[sourceMapOutputPathParameter + 1];
28+
const backtraceSourceMapId =
29+
DEBUG_ID_PATH ?? path.join(path.dirname(sourceMapOutputPath), '.backtrace-sourcemap-id');
30+
31+
const debugIdGenerator = new DebugIdGenerator();
32+
const sourceProcessor = new SourceProcessor(debugIdGenerator);
33+
const { source, debugId } = sourceProcessor.processSource(bundle.code);
34+
const snippet = debugIdGenerator.generateSourceSnippet(debugId);
35+
36+
console.debug(`Backtrace: saving debugId ${debugId} to ${backtraceSourceMapId}.`);
37+
fs.writeFileSync(backtraceSourceMapId, debugId);
38+
39+
const debugIdPrepend = {
40+
dependencies: new Map(),
41+
getSource: () => Buffer.from(snippet),
42+
inverseDependencies: new CountingSet(),
43+
path: '__prelude__',
44+
output: [
45+
{
46+
type: 'js/script/virtual',
47+
data: {
48+
code: snippet,
49+
lineCount: 1,
50+
map: [],
51+
},
52+
},
53+
],
54+
};
55+
preModules.unshift(debugIdPrepend);
56+
return {
57+
...bundle,
58+
code: source,
59+
};
60+
}
61+
62+
module.exports = { processSourceMap };

0 commit comments

Comments
 (0)