Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bundling React Native with Hermes manually #34043

Closed
marandaneto opened this issue Jun 22, 2022 · 24 comments
Closed

Bundling React Native with Hermes manually #34043

marandaneto opened this issue Jun 22, 2022 · 24 comments
Labels
p: Sentry Partner: Sentry Partner Stale There has been a lack of activity on this issue and it may be closed soon. Tech: Bundler 📦 This issue is related to the bundler (Metro, Haul, etc) used. Tech: Hermes Hermes Engine: https://hermesengine.dev/

Comments

@marandaneto
Copy link

marandaneto commented Jun 22, 2022

Description

I'd like to generate the bundles of an RN App with Hermes enabled manually, so I could upload them manually to 3rd tools such as Sentry.io in order to symbolicate stack traces.

I've not found official documentation about that so I've tried to do reverse engineering myself.

Linked issue getsentry/sentry-react-native#2244
Sentry's docs: https://docs.sentry.io/platforms/react-native/manual-setup/hermes/

Version

0.67.4

Output of npx react-native info

System:
OS: macOS 12.4
CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Memory: 132.30 MB / 32.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 18.3.0 - /usr/local/bin/node
Yarn: 1.22.19 - ~/.yarn/bin/yarn
npm: 8.11.0 - /usr/local/bin/npm
Watchman: 2022.06.13.00 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.11.3 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 21.4, iOS 15.5, macOS 12.3, tvOS 15.4, watchOS 8.5
Android SDK: Not Found
IDEs:
Android Studio: Chipmunk 2021.2.1 Patch 1 Chipmunk 2021.2.1 Patch 1
Xcode: 13.4.1/13F100 - /usr/bin/xcodebuild
Languages:
Java: 11.0.10 - /Users/user/.sdkman/candidates/java/current/bin/javac
npmPackages:
@react-native-community/cli: Not Found
react: 17.0.2 => 17.0.2
react-native: 0.67.4 => 0.67.4
react-native-macos: Not Found
npmGlobalPackages:
react-native: Not Found

Steps to reproduce

If I do it on Android:

cd android

./gradlew AssembleRelease
cd..
node_modules/@sentry/cli/bin/sentry-cli releases \
    files <release> \
    upload-sourcemaps \
    --dist <dist> \
    --strip-prefix $projectRoot/fullFolder \
    --bundle index.android.bundle \
    --bundle-sourcemap index.android.bundle.map

Bundle: android/app/build/generated/assets/react/release/index.android.bundle
Bundle.map: android/app/build/generated/sourcemaps/react/release/index.android.bundle.map

Everything works as expected, in this case, I'm using the generated bundles by the RN tooling.

If I do it on iOS:

npx react-native bundle --platform ios --dev false --entry-file index.js --reset-cache --bundle-output main.jsbundle --sourcemap-output main.jsbundle.packager.map --minify false
node_modules/hermes-engine/osx-bin/hermesc -O -emit-binary -output-source-map -out=main.jsbundle.hbc main.jsbundle
node node_modules/react-native/scripts/compose-source-maps.js main.jsbundle.packager.map main.jsbundle.hbc.map -o main.jsbundle.map

In this case, I've generated the bundles myself, now I have to upload it:

sentry-cli releases files $release upload-sourcemaps --dist $dist --strip-prefix $fullRootFolder main.jsbundle main.jsbundle.map

See screenshots in the Snack, code example, screenshot, or link to a repository section.

So my question is:

Am I using the right commands with the right parameters? npx react-native bundle, hermesc and compose-source-maps.js?
Am I uploading the right files?
Did this behavior change across RN versions? such as 0.63.x - 0.68.x?
Is there any official documentation around generating bundles manually with Hermes?

Thanks a bunch.

Snack, code example, screenshot, or link to a repository

How it is:

173807162-9810fda2-8fd5-4a66-b183-95139185bb7e

How it should be:
Screenshot 2022-06-22 at 13 12 23

@cortinico
Copy link
Contributor

Hey hey @marandaneto 👋

Am I using the right commands with the right parameters? npx react-native bundle, hermesc and compose-source-maps.js?

Yes those commands are correct (are the same we're generating during an app build).

Am I uploading the right files?

Yes you are.

Did this behavior change across RN versions? such as 0.63.x - 0.68.x?

Not that I'm aware. It's going to change in 0.69.x though. The significant change is that the path of hermesc will change:

// Before
node_modules/hermes-engine/osx-bin/hermesc
// After
node_modules/react-native/sdks/hermesc/osx-bin/hermesc

Is there any official documentation around generating bundles manually with Hermes?

Sadly the docs on this are scarce as we don't expect users to manually generate sourcemaps.
This is what we have on symbolicating JS stacktraces: https://reactnative.dev/docs/symbolication

@marandaneto
Copy link
Author

@cortinico Hey hey :)

Awesome, thank you for letting me know the hermes path change, I've added a note on our docs.

So the question is: if we're using the right commands and uploading the files correctly.
Why does it work if I upload the generated bundles by the tooling, but it does not if I bundle myself?
I can reproduce this issue with a simple template using npx react-native init, Adding Sentry, generating the Bundles, and uploading them to Sentry, so it's not a bug on our end as it works OOTB if I upload the automatically-generated files.
Can I provide you with more info to find a solution for this?

Thanks a lot for keeping improving React Native :)

@cortinico
Copy link
Contributor

Can I provide you with more info to find a solution for this?

I would suggest to try to verify that the source map is valid first.

  1. Generate the source map with the commands you're testing
  2. Try to create a crash in your app
  3. Use the metro-symbolicate package to verify that you're able to see the symbolicated stacktrace.

Also is this issue happening only for Hermes?

@marandaneto
Copy link
Author

marandaneto commented Jun 25, 2022

On Android, I've noticed that you have to upload the output (index.android.bundle) of the command 2 instead of command 1, so the .hbc file and then it works.

On iOS, it does not work at all, stack trace and line numbers still don't match.

I've noticed that the output of the command 1 (main.jsbundle) for iOS does not match the auto-generated bundle under /Users/$user/Library/Developer/Xcode/DerivedData/$projectName-$projectId/Build/Products/Release-iphonesimulator/main.jsbundle
E.g. using the command 2359677 bytes
The auto-generated file 2359629 bytes
So either the build isn't deterministic or the commands need to be adjusted for iOS.

Again, if you don't bundle yourself, everything works as expected.

@kidroca
Copy link
Contributor

kidroca commented Jun 28, 2022

@marandaneto
What seems to be working for me for ios is to skip the hermesc and the compose-source-maps.js steps

So just generate source-maps using

npx react-native bundle --platform ios --dev false --entry-file index.js --reset-cache --bundle-output main.jsbundle --sourcemap-output main.jsbundle.packager.map --minify false

Then you can check some stack traces locally to verify they are getting decoded

npx metro-symbolicate main.jsbundle.packager.map < ios.stacktrace.txt

Hermes for iOS is enabled (react-native 0.66.4)

I don't know why but combining the packager and the hbc source maps results it metro-symbolicate replacing everything with null

E.g.

npx metro-symbolicate main.jsbundle.map < ios.stacktrace.txt

0  ???                            0x0 shouldComponentUpdate + 199284 (null:null:null)
1  ???                            0x0 checkShouldComponentUpdate + 5581 (null:null:null)
2  ???                            0x0 updateClassComponent + 6842 (null:null:null)

While using just the .package.map file I get

npx metro-symbolicate main.jsbundle.packager.map < ios.stacktrace.txt

0  ???                            0x0 shouldComponentUpdate + 199284 ([redacted]\src\pages\home\report\ReportActionsView.js:114:constructor)
1  ???                            0x0 checkShouldComponentUpdate + 5581 ([redacted]\node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-prod.js:2527:checkShouldComponentUpdate)
2  ???                            0x0 updateClassComponent + 6842 ([redacted]\node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-prod.js:4461:updateClassComponent)
3

@marandaneto
Copy link
Author

@kidroca awesome, let me try this workaround, thanks.

@marandaneto
Copy link
Author

It didn't work for me on iOS, RN 0.67.4, even if uploading the main.jsbundle.packager.map.

@kidroca
Copy link
Contributor

kidroca commented Jun 29, 2022

It didn't work for me on iOS, RN 0.67.4, even if uploading the main.jsbundle.packager.map.

From the looks of it the manual setup that uses react-native's own react-native-xcode.sh script uses only the packager map:
See this here: https://docs.sentry.io/platforms/react-native/manual-setup/manual-setup/#bundle-react-native-code-and-images

export NODE_BINARY=node
export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map"
export SENTRY_PROPERTIES=../sentry.properties

../node_modules/@sentry/cli/bin/sentry-cli react-native xcode \
  ../node_modules/react-native/scripts/react-native-xcode.sh

EXTRA_PACKAGER_ARGS are only for the packager so the output map is only from the packager and not the combined map from the hermes compiler and the packager


To make a combined map automatically through the build phase we need to export SOURCEMAP_FILE

EMIT_SOURCEMAP=
if [[ ! -z "$SOURCEMAP_FILE" ]]; then
EMIT_SOURCEMAP=true
fi

Which then instructs the script to make 2 maps (if using Hermes) and combine them, but for some reason the combined map doesn't work to decode stack traces, only the packager map works.
I guess that's why by default ios build does not generate a source map

Perhaps something goes wrong while the maps are merged because the combined map size is smaller than any of the two maps, I'd expect it'll be as big as the two maps combined

@marandaneto
Copy link
Author

If I export SOURCEMAP_FILE=main.jsbundle.map, the map file is generated automatically in the temporary folder, also the bundle.
If I upload both files, symbolication still does not work, the same result as the screenshot in the Snack, code example, screenshot, or link to a repository section.

@marandaneto
Copy link
Author

marandaneto commented Jun 29, 2022

So on iOS, we're using the $DERIVED_FILE_DIR env. var to output the main.jsbundle.map file and that goes to /Users/$user/Library/Developer/Xcode/DerivedData/$projectName-$projectId/Build/Intermediates.noindex/$projectName.build/Release-iphonesimulator/$projectName.build/DerivedSources/main.jsbundle.map

If I upload the bundle and this main.jsbundle.map file, it works on iOS, but again, it depends on the automatically generated files and not the manually bundled.

@kidroca
Copy link
Contributor

kidroca commented Jun 29, 2022

If I upload the bundle and this main.jsbundle.map file, it works on iOS, but again, it depends on the automatically generated files and not the manually bundled.

What I'm trying to say is because of these packager args, the generated main.jsbundle.map is not the combined map but the packager's map saved as main.jsbundle.map

export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map"

If that works for you, you can generate the same manually
Run only the first step here: https://docs.sentry.io/platforms/react-native/manual-setup/hermes/#compile-sourcemaps and ignore the rest

npx react-native bundle --platform ios --dev false --entry-file index.js --reset-cache --bundle-output main.jsbundle --sourcemap-output main.jsbundle.packager.map --minify false

I'm working on decoding js stacktraces from Firebase Crashlytics and tried this in both existing and a new RN project
The js tacktraces I get from Crashlytics can only be decoded by the packager map and not the hbc or the combined map

@marandaneto
Copy link
Author

@kidroca Yep, I understood what you meant, still, output is the same, line numbers are all wrong.
So just calling npx react-native bundle --platform ios --dev false --entry-file index.js --reset-cache --bundle-output main.jsbundle --sourcemap-output main.jsbundle.packager.map --minify false and uploading both files to Sentry won't help, the problem still lies.
If I upload the auto generated files (using EXTRA_PACKAGER_ARGS), it works, so I believe the tooling still does something more than just this command and I have no idea what.

@marandaneto
Copy link
Author

Btw what you said, the file main.jsbundle.packager.map matches in size with the one generated by using the EXTRA_PACKAGER_ARGS.
The problem is the bundle itself, main.jsbundle differs in size, maybe that's where the problem lies.

@kidroca
Copy link
Contributor

kidroca commented Jun 30, 2022

The problem is the bundle itself, main.jsbundle differs in size, maybe that's where the problem lies.

I've noticed that the output of the command 1 (main.jsbundle) for iOS does not match the auto-generated bundle under /Users/$user/Library/Developer/Xcode/DerivedData/$projectName-$projectId/Build/Products/Release-iphonesimulator/main.jsbundle
E.g. using the command 2359677 bytes
The auto-generated file 2359629 bytes

Can you diff the 2 outputs?
For me the difference is just due to the source map comment appended at the end

react-native bundle vs xcode build phase

image

Depending on whether you've touched the build phase there might be no source map comment at all

@marandaneto
Copy link
Author

marandaneto commented Jul 1, 2022

@kidroca that was the trick, this comment is likely used within the symbolication process or within the file itself.

63301c63301
< //# sourceMappingURL=main.jsbundle.packager.map
\ No newline at end of file
---
> //# sourceMappingURL=main.jsbundle.map
\ No newline at end of file

Setting the sourceMappingURL to main.jsbundle.map worked out.

Or just set the file name correctly in command 1 for iOS:

npx react-native bundle --platform ios --dev false --entry-file index.js --reset-cache --bundle-output main.jsbundle --sourcemap-output main.jsbundle.map --minify false

Thanks @kidroca
So apparently there's a way out of generating the bundles manually, strange thing is that iOS skips some steps.

@kidroca
Copy link
Contributor

kidroca commented Jul 1, 2022

So for you - you had to have both files named main.jsbundle and main.jsbundle.map I guess because you upload both

For me, though, it seems I can name the sourcemap output to anything, this just works:

npx metro-symbolicate my-ios-map-file.map < ios.cralyitics.stacktrace.txt

@matt-dalton
Copy link

matt-dalton commented Aug 19, 2022

I'm having an issue even getting the bundle to run on Android. I've created a script to handle this:

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
    OS_BIN="linux64-bin" # osx-bin, win64-bin, or linux64-bin
elif [[ "$OSTYPE" == "darwin"* ]]; then
    OS_BIN="osx-bin" # osx-bin, win64-bin, or linux64-bin
fi

ARTIFACT_FILEPATH="android/app/src/main/assets/"

npx react-native bundle --platform android --dev false --entry-file index.js --reset-cache --bundle-output "$ARTIFACT_FILEPATH"index.android.bundle --sourcemap-output "$ARTIFACT_FILEPATH"index.android.bundle.packager.map --minify false
node_modules/hermes-engine/"$OS_BIN"/hermesc -O -emit-binary -output-source-map -out="$ARTIFACT_FILEPATH"index.android.bundle.hbc "$ARTIFACT_FILEPATH"index.android.bundle
rm -f "$ARTIFACT_FILEPATH"index.android.bundle
mv "$ARTIFACT_FILEPATH"index.android.bundle.hbc "$ARTIFACT_FILEPATH"index.android.bundle
node node_modules/react-native/scripts/compose-source-maps.js "$ARTIFACT_FILEPATH"index.android.bundle.packager.map "$ARTIFACT_FILEPATH"index.android.bundle.hbc.map -o "$ARTIFACT_FILEPATH"index.android.bundle.map

This looks the same to me as what's recommended here (albeit with a different directory). When I try and open up the resulting .apk, I get AndroidRuntime: com.facebook.jni.CppException: Could not get BatchedBridge, make sure your bundle is packaged correctly, so the bundle clearly isn't right.

Has anyone else seen this?

EDIT: I think perhaps I misunderstood here. I think the outputted bytecode bundle is just to be uploaded to sentry, not to actually be used in the .apk. So I need to output this somewhere else.

@mym0404
Copy link
Contributor

mym0404 commented Feb 10, 2023

I didn't investigate deeply, but now, Android and iOS with hermes work well without any hermesc or compose source map.

RN: 0.71.2

@krystofwoldrich
Copy link
Contributor

krystofwoldrich commented Feb 10, 2023

Is it possible that the source maps work without hermesc and compose source map because the React Native tooling doesn'texecute the hermesc and compose source map?

I've created a new app using npx react-native init ... --version 0.70.6 and hermes is enabled by default. The app template shows the "Engine: Hermes" label because global.HermesInternal exists. But the react-native-xcode.sh did not use hermesc and compose source map as the env USE_HERMES is empty.

This was changed in 0.71.2 and by default hermesc is executed. I'm surprised that the source maps work without it.

@mym0404
Copy link
Contributor

mym0404 commented Feb 12, 2023

Yes, also, it works well not only in the binary release but also code push release (Android, iOS both and both are using Hermes).

  1. Release code push release with appcenter cli without any config
  2. Bundle manually (Yes it is not ideal because bundling would be built twice).

I bundled assets manually(2) because, appcenter cli is using hermesc, compose source map internally, and it causes a failure with sourcemap flag.

I can't sure why this works, but it shows the correct stack trace in Sentry with code push release.

run: npm install -g appcenter-cli

// 1
appcenter codepush release-react -a {{Your Project}} -d {{Deployment}} -t {{Target Binary Version}} --token {{ Your TOKEN }}

// 2
node node_modules/.bin/react-native bundle --platform ios --dev false --entry-file index.js --bundle-output main.jsbundle --sourcemap-output main.jsbundle.map

node_modules/@sentry/cli/bin/sentry-cli releases files {{ Your Release }} upload-sourcemaps --dist {{ Your dist }} main.jsbundle main.jsbundle.map

image

@cortinico cortinico added Partner p: Sentry Partner: Sentry labels Feb 15, 2023
@krystofwoldrich
Copy link
Contributor

I did some more testing and this is what I've found.

Hermesc -output-source-map has a side effect on the bundle.

If hermes byte code bundle is generated without a source map the packager source map will work.

If hermes byte code bundle is generated with the source map the combined source map will work.

❯ hermesc -O -emit-binary -output-source-map -out=main.jsbundle.hbc.with.source.maps main.jsbundle

❯ ls -lh main.jsbundle.hbc.with.source.maps
-rw-r--r-- 1 krystofwoldrich staff 1.6M Mar 29 10:36 main.jsbundle.hbc.with.source.maps

❯ hermesc -O -emit-binary -out=main.jsbundle.hbc.without.source.maps main.jsbundle

❯ ls -lh main.jsbundle.hbc.without.source.maps
-rw-r--r-- 1 krystofwoldrich staff 2.1M Mar 29 10:36 main.jsbundle.hbc.without.source.maps

@mym0404
Copy link
Contributor

mym0404 commented Mar 29, 2023

@krystofwoldrich This seems to be true.

Recently, I bundled manually with sourcrmap and it didn't cause any error with combine sourcemap script(Android). And it works in sentry stacktrace.

In past, I didn't generate sourcemap in bundling process.

@kelset kelset added Tech: Bundler 📦 This issue is related to the bundler (Metro, Haul, etc) used. and removed 📦Bundler labels Apr 6, 2023
Copy link

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@github-actions github-actions bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Feb 16, 2024
@marandaneto marandaneto closed this as not planned Won't fix, can't repro, duplicate, stale Feb 16, 2024
@Mikchail
Copy link

Mikchail commented May 28, 2024

Hello! I'm having same issue with upload manually. Any update?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p: Sentry Partner: Sentry Partner Stale There has been a lack of activity on this issue and it may be closed soon. Tech: Bundler 📦 This issue is related to the bundler (Metro, Haul, etc) used. Tech: Hermes Hermes Engine: https://hermesengine.dev/
Projects
None yet
Development

No branches or pull requests

9 participants