The latest React Native 0.64 release adds support for the Hermes engine on iOS, and this is a direct result of our intensive work with teams at Facebook and Microsoft. We share the journey to Hermes on iOS in a series of four pieces:
- Bringing Hermes to iOS in React Native 0.64
- Hermes Performance on iOS: How it Compares with JSC
- Technical Guide, Part 1: Compiling Hermes Engine for Apple Platforms
- Technical Guide, Part 2: Integrating Hermes with React Native (you are here)
While the first two introduce Hermes and give details about running Hermes in production, the last two offer solid technical insights based on the implementation done. They’re actually one two-part guide.
If you prefer to dig the code yourself, here’s my PR to React Native. Otherwise, fasten your seatbelts and you’re in for another technical ride!
Integrating Hermes with React Native
After Hermes was compiled for both iOS devices and simulators, and packaged into a Pod (a CocoaPods name for a dependency), it was time to integrate it to React Native. Thankfully, Eloy Durán and Microsoft team were here before as well! Their work on enabling Hermes in their macOS fork not only made it easier to compile Hermes for iOS, as discussed in the first part of the guide, but also made it easier to integrate Hermes with React Native.
By upstreaming some of their work from their fork, they paved the way for the integration and definitely made my life much easier!
For more context, you may want to check this PR to React Native macOS that captures the entire work that Eloy Durán did to bring Hermes to Apple computers.
Where to start the integration process
In order to execute the JavaScript code inside your application, React Native needs an instance of a JSExecutor. As the name suggests, it is responsible for executing the JavaScript code. On top of that, it provides additional API methods to allow interaction with native modules and synchronizing asynchronous actions, just to name a few.
As of today, there are two JSExecutors available inside React Native. The first one, which you may be most familiar with, is a JSCExecutor that executes your JavaScript code with the JavaScript Core engine. The other one uses Hermes and is called HermesExecutor. React Native creates an instance of an executor via an ExecutorFactory.
You’re probably wondering why we have two executors available within React Native if, at least at the time of writing this step-by-step technical guide, there was only JavaScript Core available as the engine on iOS.
The answer is simple - the majority of the core React Native code is written in C++. That makes it easier to share core between Android and iOS, when possible. To answer the question from the previous paragraph, JSCExecutorFactory is a factory written in C++ that is shared between iOS and Android. HermesExecutorFactory is a factory written in C++ that is exclusive to Android, and located inside the ReactAndroid folder as a result.
When to use Hermes over JSC
As you can see, at the time of starting Hermes integration for iOS, a lot of important core pieces were already in place. Thanks to that cross-platform approach of React Native at its lowest core level, adding support to Hermes on iOS was faster and smoother due to pre-existing code that was already running on Android. (There were a few additional places with Hermes and I will highlight them later in this article.)
In other words, moving HermesExecutor outside of ReactAndroid folder to a shared folder and including it in both iOS and Android builds was already part of the success. This is exactly what happened in this PR.
Once this was done, the next step was to activate that mechanism - teach React Native when to use Hermes over JSC. Our approach was to make it as smooth as possible by reducing the amount of steps to be done by the developer to a bare minimum. I am proud to say we have managed to reduce it to exactly one step. And that’s what I have described in the next step.
How to activate the engine
React Native on iOS uses CocoaPods quite extensively to automate the process of installing native dependencies as well as configure some of the project settings. This is done via a special function called use_react_native which is embedded in your Podfile.
In order to turn on Hermes on iOS, all you have to do is just one step - set hermes_enabled to true where you have your use_react_native function:
Then run the <rte-code>pod install<rte-code> command once again. This will install any new native dependencies you have added, and reconfigure the project with the latest settings in mind. In this particular example, it will add the Hermes framework to your project.
Note: If you’re interested in how Hermes framework is generated and what lies inside, please check the previous part of this guide, where we go through the same process of generating Hermes framework step by step.
Unlike any other native code you may have worked with, there is no npm install or yarn install to do. This is because Hermes is already included with every React Native release and there is no need to install any additional packages to set things up.
Since the presence of Hermes binaries dictates whether it should or should not be used (based on your preferred setting in a Podfile), we can rely on this in all other places where we need to choose the right engine:
This macro will set a project-wide variable RCT_USE_HERMES if, and only if, Hermes headers are included in the project. As we know from the previous paragraph, such scenario will be true in cases when users opted-in to use Hermes.
Based on this behavior, we can now choose either Hermes or JSC executor (as mentioned at the beginning of this guide), depending on the presence of RCT_USE_HERMES.
First of all, we can choose which file to import in our file:
and then initialize the proper executor factory:
Thanks to that mechanism, RCTCxxBridge can choose the right executor factory, depending on whether Hermes is available or not. Thanks to the pre-existing abstraction within React Native that separates the engine from the rest of the architecture, no further changes were required in order to actually switch the engine itself.
How it works in practice
As soon as you turn on the demo application, you will see Hermes engine information at the top of your welcome screen. Even though we didn’t do any changes around React Native components, they are already able to detect that we’re running Hermes engine.
This is because the <NewAppScreen> is a cross-platform component and is actually running on Android too; Hermes has been out there in production for quite some time. That’s just another benefit of a cross-platform architecture.
The same thing applies to other pieces such as Flipper. I could go into details on how it works and what are its greatest benefits, but I guess there has to be something I leave for you to explore further!
Try Hermes yourself
You can start building mobile applications with Hermes on both platforms as of today! The results of our work are available as a part of recently published React Native 0.64. You can learn more about it and how to turn it on in my post on the official React Native blog!
Can’t wait to see what you build with Hermes and how it turns out for you in production! While we continue our benchmarking, please share your findings and feelings on Twitter - make sure to tag me so that we can celebrate together!
Find out more
As part of The React Native Show, we released a podcast episode fully dedicated to Hermes: React Native 0.64 with Hermes for iOS. Check it out to learn more about Hermes on iOS and MacOS from Facebook and Microsoft engineers.