Technical Guide, Part 1: Compiling Hermes for Apple Platforms

Months of our intense work with teams at Facebook and Microsoft resulted in bringing Hermes to iOS. We are happy to share the details of the process in a series of articles. This article is the third in the series and the first to focus on the technical journey:

You are going to find out how we brought Hermes to iOS and how you can implement it yourself. We provide a detailed guide to Hermes implementation based on the actual work done. So, if you want to learn more about how different core pieces play together, keep reading! 

Compiling Hermes for Apple platforms

Before we talk about bringing Hermes to React Native on iOS, we actually need to compile it for Apple platforms. Hermes is written in C++ and compiled with cmake to existing platforms, so at a glance, it sounds like fun!

Just to be on the safe side, let me explain that C++ is one of these cross-platform languages that can run literally everywhere. For example, you can write native modules in C++ for Android and on iOS (hey, the Objective-C is not just similar in its name). Thanks to that, seeing a task of compiling Hermes on Apple devices didn’t sound that scary when I first started playing around that topic.

Thankfully, I didn’t have to start from the middle of nowhere (but I have to admit that playing around with cmake in general was quite an experience!). Folks at Microsoft have been working on bringing Hermes to Mac for their React Native macOS project. The work was done primarily by Eloy Durán, who sent a PR to Hermes with the base for my work.

On a high level, this PR enables cmake to package Hermes in a dynamic library so that it can be used on a macOS platform. To make the integration with Apple ecosystem smoother, the PR adds a special Podspec so that you don’t have to manually import a framework file to your project. You can let CocoaPods do that magic for you instead.

At this point, I was amazed by the comprehensiveness of cmake and the number of out-of-the-box features it provides. If you look at the changes in the aforementioned PR, they’re all related to the build system. It’s mind blowing to see that such an advanced project like a JavaScript engine can be run on macOS by just flipping a few flags, i.e. without changing the business logic of the engine itself.

That’s good for me and all of you planning to work on C++ bits in the future! With that in mind, let’s move onto the iOS part.

On the way to iOS

Having Hermes running on macOS was a good indicator that it might work on iOS as well. In case you want a quick version - here’s my PR with all the changes. If you’re curious about all the steps and a bit of technical explanations, carry on. 

#1

First thing I had to do was to tell cmake that it is no longer building Hermes for macOS, but for iOS. This can be achieved by setting a special variable CMAKE_OSX_SYSROOT to configure the build pipeline to target specific SDK.  

set(CMAKE_OSX_SYSROOT ${HERMES_APPLE_TARGET_PLATFORM})

I ended up going straight with a variable. We will need to build Hermes for every platform and architecture separately, which means building it a couple of times. Having a variable definitely helps - we can change its value depending on what we are targeting.

The list of all platforms and architectures should be aligned with what React Native supports right now - otherwise, developers may run into issues on certain devices.

Here’s a breakdown of the platforms together with their architectures.

Graph showing platforms and their architectures

#2

Another important thing was to tell cmake where to actually output generated files for every platform. 

By default, the library would be placed under a Library/Frameworks/hermes.framework path within a build folder. Unfortunately, that would result in one build process overwriting the artifacts from the previous one.

Since I wanted to keep the artifacts for every platform, I ended up tweaking the location where the files are placed:

install(DIRECTORY ${DSYM_PATH} DESTINATION
Library/Frameworks/${HERMES_APPLE_TARGET_PLATFORM})

As a result, the files would be now placed under Library/Frameworks/iphonesimulator or Library/Frameworks/iphoneos, depending on whether we’re building for a device or a simulator.

#3

Now that the platform part was sorted, it was time to look at the architectures. The idea was to precompile Hermes in all possible configurations so that you don’t have to run it from source. That would be not only quite a time consuming process, but also prone to many errors, due to different configurations of our development machines. 

To do so, for each invocation of cmake, I ended up setting CMAKE_OSX_ARCHITECTURES with the right value for every platform. Looking at the table I have shared just a few paragraphs earlier, that would be "armv7;armv7s;arm64" for iPhone and "x86_64;i386" for iPhone Simulator.

Since that variable can be passed as a command line argument straight to cmake, there is no custom code that I had to do to make it work.

#4

The last thing to set was the deployment target - the version that we are targeting and is the minimum supported by Hermes. Again, that one is supported by cmake out of the box, so no changes here.

The value of CMAKE_OSX_DEPLOYMENT_TARGET was set equally to “10.0” for both simulator and the device.

build_apple_framework

After testing the combinations a few times, I packaged them in a helper Bash function, called build_apple_framework, that takes these settings and tells CMake what to do. 

build_apple_framework "iphoneos" "armv7;armv7s;arm64" "10.0"
build_apple_framework "iphonesimulator" "x86_64;i386" "10.0"

Thanks to that, it becomes trivial to control what platforms and architectures Hermes supports on iOS. Bonus points: it can be used to build macOS version too, so I went ahead and updated Eloy Durán part too:

build_apple_framework "macosx" "x86_64" "10.0"

hermes.framework files

After building Hermes with CMake for all the combinations, I ended up with two hermes.framework files: for iPhone supporting armv7, armv7s and arm64 as well as for iPhone Simulator supporting x86_64 and i386.

It would be a poor developer experience if you had to change a hermes.framework in your project depending on whether you run on a device or a simulator. It would definitely hinder your work if you had to manually replace the library in your project.

Thankfully, there are universal frameworks, in other words - frameworks that support more than a single platform. Simply put - it’s a way to combine two hermes.framework into a single one!

You can create one programmatically with a lipo - a tool to create multi-architectural files. To generate a universal framework file, the invocation would look as follows:

lipo -create -output

Library/Frameworks/iphoneos/hermes.framework/hermes
Library/Frameworks/iphoneos/hermes.framework/hermes
Library/Frameworks/iphonesimulator/hermes.framework/hermes

To speed things up, I decided to merge all additional architectures into the iPhone binary. The first argument to lipo is the destination, the following ones are input binaries that should be combined together. Just like before, I moved the logic into a Bash function, called create_universal_framework:

create_universal_framework "iphoneos" "iphonesimulator"

Again, such an approach allows us to easily control the contents of the final hermes.framework file.

Last but not least

The last piece was to update the Hermes.podspec created by Eloy Durán to add iOS support.

That required changing spec.vendored_frameworks to spec.osx.vendored_frameworks and spec.ios.vendored_frameworks to tell CocoaPods that this package contains frameworks for both macOS as well as iOS (note that macOS and iOS binaries can’t be merged into a single universal framework - they are separate).

In other words, replacing this:

spec.vendored_frameworks = "destroot/Library/Frameworks/hermes.framework"

with:

spec.ios.vendored_frameworks = "destroot/Library/Frameworks/iphoneos/hermes.framework"
spec.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework"

Try Hermes yourself

The process of doing CMake reverse engineering took me three weeks, but it was worth it. I have learned a lot about build tools and this knowledge will be very useful in the future. You should definitely clone Hermes and play around with it. Follow our Hermes implementation guide and test it yourself. It’s quite easy to get started and working on a JavaScript engine can get really rewarding! If you want to learn more about Hermes, check our podcast: React Native 0.64 with Hermes for iOS. My guests, Microsoft and Facebook engineers, discuss the engine in detail!

‍

‍

In the next part of this guide, “Integrating Hermes with React Native,” we will go through the steps that are needed to enable a custom engine to work with React Native, instead of the default JSC.

Insights

Learn more about Hermes

cover

Dec 18Article

Why You Don’t Have to Minify JavaScript Code in React Native Apps

React Native skips JavaScript minification in production because Hermes handles dead code elimination at the bytecode level. Here’s how and why it works.

cover

Dec 4Podcast

From Static Hermes to Hermes V1: The Road to Default

Nicola Corti (Meta) explains the evolution of Static Hermes into Hermes V1, the performance boosts it ships, and the (experimental) path to making it the new default engine.

cover

Nov 6Live Stream

Hermes V1: What It Is, What It Isn’t, and What’s Next

Łukasz Chludziński, Szymon Chmal, and Matt Hargett unpack Hermes V1, covering what’s new, what’s changing, and what’s ahead for React Native performance.

cover

Dec 4Article

How to Optimize Your Android App Startup Time With Hermes

Learn how to achieve better performance and optimize Android app startup time with Hermes.

cover

Jun 14Article

Technical Guide, Part 2: Integrating Hermes With React Native

Interested in the Hermes engine? Check the second part of our expert guide to find out how to integrate Hermes with React Native.

cover

May 11Podcast

React Native 0.64 With Hermes for iOS

Discover how Hermes boosts React Native performance on iOS and macOS, with insights into AOT, bytecode precompilation, and the future of Hermes support.

cover

Apr 21Article

Hermes Performance on iOS

Wondering how the Hermes engine performs on iOS? We tested it on a React Native mobile app. Check the results and see how it compares to JSC.

cover

Apr 14Article

Bringing Hermes to iOS in React Native 0.64

Hermes is now available for iOS as well. Find out more about this JS engine - check the first part of our series on bringing Hermes to iOS.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

Writing a Swift TurboModule in React Native

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.