Pull down to refresh

CHICIO CODING

Dirty clean code. Creative Stuff. Stuff.

How to: enable Hermes JavaScript engine in your React Native app

React native 0.60.4 has a new cool feature for Android: a new JavaScript engine called Hermes. Let’s see how you can turn it on in your React Native application to get all its benefits.


As you already may know, there has been some complains in the past related to the performance of React Native on the Android platform. One of the main reason was due to a big difference in the React Native architecture implementation between Android and iOS: the JavaScript engine used to execute your code. On iOS React Native uses the JavaScript Core engine exposed in the iOS SDK. On Android the SDK doesn’t offer the same feature, so the React Native Android implementation embeds a compiled version of the JavaScript Core engine. As a consequence of this fact the engine used on Android didn’t receive the regular updates that the iOS counterpart received on each system major update, and was also not optimized for React Native and generally speaking for executing JavaScript code for mobiles apps. This is the reason why the Facebook React Native team decided to create Hermes, an open source JavaScript engine optimized for mobile apps.
Which benefits does the new Engine bring to the table? As reported in the presentation blog post, there were a few key metrics kept in consideration by the Facebook React Native team:

For JavaScript-based mobile applications, user experience benefits from attention to a few primary metrics:

The time it takes for the app to become usable, called time to interact (TTI) The download size (on Android, APK size) Memory utilization

That seems really cool!! Hermes is available starting from React Native 0.60.4. Now the question is: how can you start to use it? Let’s see how we enabled this new cool new engine in the lm group mobile apps (did you remember how much we love React Native?) while we were doing the upgrade to the latest version of React Native in order to enable AndroidX in our apps.

Implementation

The first thing to do is to set the enableHermes option to true in the React Native project configuration. This is typically done in the build.gradle app file or, if you have one, in your react.gradle custom gradle file at app level.

project.ext.react = [
  /// ...other options...
  enableHermes: true
]

Then we need to tell to ProGuard (if you’re using it) to keep some Hermes classes.

-keep class com.facebook.hermes.unicode.** { *; }

In the official documentation these are all the step needed to activate Hermes. So we added these configurations to our apps and we launched our app, but we got the following error.

2020-01-17 22:04:30.194 5745-6293/it.app E/SoLoader: couldn't find DSO to load: libhermes.so
2020-01-17 22:04:30.646 5745-6293/it.app E/AndroidRuntime: FATAL EXCEPTION: create_react_context
    Process: it.app, PID: 5745
    java.lang.UnsatisfiedLinkError: couldn't find DSO to load: libhermes.so
        at com.facebook.soloader.SoLoader.doLoadLibraryBySoName(SoLoader.java:738)
        at com.facebook.soloader.SoLoader.loadLibraryBySoName(SoLoader.java:591)
        at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:529)
        at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:484)
        at com.facebook.hermes.reactexecutor.HermesExecutor.<clinit>(HermesExecutor.java:20)
        at com.facebook.hermes.reactexecutor.HermesExecutorFactory.create(HermesExecutorFactory.java:27)
        at com.facebook.react.ReactInstanceManager$5.run(ReactInstanceManager.java:952)
        at java.lang.Thread.run(Thread.java:761)

As the error says, the compilation is failing because gradle is not able to find one the shared libraries used by Hermes. If you think well we are also missing a part in our setup: we said that React Native contains a compiled version of Hermes, but we are not telling to gradle where it can pick the aar file that contains it. Let’s fix this problem with the help of the React Native upgrade tool.
First we need to add to the repository section in the main gradle file a new maven repository (that is contained in the node_modules of the app).

//....

allprojects {
    repositories {
      //....
      maven { url("$rootDir/../node_modules/jsc-android/dist") }
      //....
    }
}

//....

Then we need to declare the Hermes compiled version as dependencies in the build.gradle file.

//...

debugImplementation files("../../node_modules/hermes-engine/android/hermes-debug.aar")
qaReleaseImplementation files("../../node_modules/hermes-engine/android/hermes-release.aar")
releaseImplementation files("../../node_modules/hermes-engine/android/hermes-release.aar")

//...

As you can see from above we needed to link the aar version of Hermes specifically for each build variant we have. We also need to rename our qa flavor to qaRelease and link it to the hermes-release.aar file. Why? Because our QA build configuration inherits from the release one, and the react.gradle contained in the React Native itself (node_modules/react-native/react.gradle) does some checks based on the flavor name and, if it contains release, it performs some additional operations for apps with Hermes enabled related to the generation of the sourcemap and the removal of the debugger libraries (not needed for a release build). Below you can find the parts that do checks on the variant name.

//...

if (enableHermes) {
  doLast {
    def hermesFlags;
    def hbcTempFile = file("${jsBundleFile}.hbc")
    exec {
      if (targetName.toLowerCase().contains("release")) {
        // Can't use ?: since that will also substitute valid empty lists
        hermesFlags = config.hermesFlagsRelease
        if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
        } else {
          hermesFlags = config.hermesFlagsDebug
          if (hermesFlags == null) hermesFlags = []
        }

        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
          commandLine("cmd", "/c", getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
      } else {
        commandLine(getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
      }
    }

    //....
  }
}

//...
def isRelease = targetName.toLowerCase().contains("release")
def libDir = "$buildDir/intermediates/transforms/"
def vmSelectionAction = {
    fileTree(libDir).matching {
        if (enableHermes) {
            // For Hermes, delete all the libjsc* files
            include "**/libjsc*.so"

            if (isRelease) {
                // Reduce size by deleting the debugger/inspector
                include '**/libhermes-inspector.so'
                include '**/libhermes-executor-debug.so'
            } else {
                // Release libs take precedence and must be removed
                // to allow debugging
                include '**/libhermes-executor-release.so'
            }
        } else {
            // For JSC, delete all the libhermes* files
            include "**/libhermes*.so"
        }
    }.visit { details ->
        def targetVariant = ".*/transforms/[^/]*/${targetPath}/.*"
        def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char)
        if (path.matches(targetVariant) && details.file.isFile()) {
            details.file.delete()
        }
    }
}

Conclusion

Hermes is one of the cool new features contained in the new version of React Native :sunglasses:. Stay tuned for more updates and see how we are using all of them here at lm group.

Recent posts