React Native Architecture Overview

 


React Native Architecture Overview 

 

Welcome to the React Native Architecture Overview!  


Architecture Overview is intended to share conceptual overview of how React Native's internals work.  You can still benefit from the overview as it will give you insights into how React Native works under the hood. 


Table of Contents  

  • Rendering 

  • Build Tools 

 

  • Rendering 


Fabric 

 

Fabric is React Native's new rendering system, a conceptual evolution of the legacy render system. The core principles are to unify more render logic in C++, improve interoperability with host platforms, and to unlock new capabilities for React Native. Development began in 2018 and in 2021, React Native in the Facebook app is backed by the new renderer. 


This documentation provides an overview of the new renderer and its concepts. It avoids platform specifics and doesn’t contain any code snippets or pointers. This documentation covers key concepts, motivation, benefits, and an overview of the render pipeline in different scenarios. 


Motivations and Benefits of the new renderer  

 

The render architecture was created to unlock better user experiences that weren’t possible with the legacy architecture. Some examples include: 


  • With improved interoperability between host views and React views, the renderer is able to measure and render React surfaces synchronously. In the legacy architecture, React Native layout was asynchronous which led to a layout “jump” issue when embedding a React Native rendered view in a host view. 


  • With support of multi-priority and synchronous events, the renderer can prioritize certain user interactions to ensure they are handled in a timely manner. 


  • Integration with React Suspense which allows for more intuitive design of data fetching in React apps. 


  • Enable React Concurrent Features on React Native. 


  • Easier to implement server side rendering for React Native. 


The new architecture also provides benefits in code quality, performance, and extensibility: 


  • Type safety: code generation to ensure type safety across the JS and host platforms. The code generation uses JavaScript component declarations as source of truth to generate C++ structs to hold the props. Mismatch between JavaScript and host component props triggers a build error. 


  • Shared C++ core: the renderer is implemented in C++ and the core is shared among platforms. This increases consistency and makes it easier to adopt React Native on new platforms. 


  • Better Host Platform Interoperability: Synchronous and thread-safe layout calculation improves user experiences when embedding host components into React Native, which means easier integration with host platform frameworks that require synchronous APIs. 


  • Improved Performance: With the new cross-platform implementation of the renderer system, every platform benefits from performance improvements that may have been motivated by limitations of one platform. For example, view flattening was originally a performance solution for Android and is now provided by default on both Android and iOS. 


  • Consistency: The new render system is cross-platform, it is easier to keep consistency among different platforms. 


  • Faster Startup: Host components are lazily initialized by default. 


  • Less serialization of data between JS and host platform: React used to transfer data between JavaScript and host platform as serialized JSON. The new renderer improves the transfer of data by accessing JavaScript values directly using JavaScript Interfaces (JSI). 

 

Render, Commit, and Mount 

 

The React Native renderer goes through a sequence of work to render React logic to a host platform. This sequence of work is called the render pipeline and occurs for initial renders and updates to the UI state. This document goes over the render pipeline and how it differs in those scenarios. 


The render pipeline can be broken into three general phases: 


  1. Render: React executes product logic which creates a React Element Trees in JavaScript. From this tree, the renderer creates a React Shadow Tree in C++. 


  1. Commit: After a React Shadow Tree is fully created, the renderer triggers a commit. This promotes both the React Element Tree and the newly created React Shadow Tree as the “next tree” to be mounted. This also schedules calculation of its layout information. 


  1. Mount: The React Shadow Tree, now with the results of layout calculation, is transformed into a Host View Tree. 


 


Initial Render  

 

Imagine you want to render the following: 


function MyComponent() { 
return ( 
<View> 
<Text>Hello, World</Text> 
</View> 
); 
} 
 
// <MyComponent /> 
 

In the example above, <MyComponent /> is a React Element. React recursively reduces this React Element to a terminal React Host Component by invoking it (or its render method if implemented with a JavaScript class) until every React Element cannot be reduced any further. Now you have a React Element Tree of React Host Components. 


Phase 1. Render  

 

Phase one: render 


During this process of element reduction, as each React Element is invoked, the renderer also synchronously creates a React Shadow Node. This happens only for React Host Components, not for React Composite Components. In the example above, the <View> leads to the creation of a ViewShadowNode object, and the <Text> leads to the creation of a TextShadowNode object. Notably, there is never a React Shadow Node that directly represents <MyComponent>. 


Whenever React creates a parent-child relationship between two React Element Nodes, the renderer creates the same relationship between the corresponding React Shadow Nodes. This is how the React Shadow Tree is assembled. 


Additional Details 


  • The operations (creation of React Shadow Node, creation of parent-child relationship between two React Shadow Nodes) are synchronous and thread-safe operations that are executed from React (JavaScript) into the renderer (C++), usually on the JavaScript thread. 


  • The React Element Tree (and its constituent React Element Nodes) do not exist indefinitely. It is a temporal representation materialized by “fibers” in React. Each “fiber” that represents a host component stores a C++ pointer to the React Shadow Node, made possible by JSI. Learn more about “fibers” in this document. 


  • The React Shadow Tree is immutable. In order to update any React Shadow Node, the renderer creates a new React Shadow Tree. However, the renderer provides cloning operations to make state updates more performant (see React State Updates for more details). 


In the example above, the result of the render phase looks like this: 


Step one 

After the React Shadow Tree is complete, the renderer triggers a commit of the React Element Tree. 


Phase 2. Commit  

 

Phase two: commit 


The commit phase consists of two operations: Layout Calculation and Tree Promotion. 


  • Layout Calculation: This operation calculates the position and size of each React Shadow Node. In React Native, this involves invoking Yoga to calculate the layout of each React Shadow Node. The actual calculation requires each React Shadow Node’s styles which originate from a React Element in JavaScript. It also requires the layout constraints of the root of the React Shadow Tree, which determines the amount of available space that the resulting nodes can occupy. 


Step two 


  • Tree Promotion (New Tree → Next Tree): This operation promotes the new React Shadow Tree as the “next tree” to be mounted. This promotion indicates that the new React Shadow Tree has all the information to be mounted and represents the latest state of the React Element Tree. The “next tree” mounts on the next “tick” of the UI Thread. 


Additional Details 


  • These operations are asynchronously executed on a background thread. 


  • Majority of layout calculation executes entirely within C++. However, the layout calculation of some components depend on the host platform (e.g. TextTextInput, etc.). Size and position of text is specific to each host platform and needs to be calculated on the host platform layer. For this purpose, Yoga invokes a function defined in the host platform to calculate the component’s layout. 


Phase 3. Mount  

 

Phase three: mount 


The mount phase transforms the React Shadow Tree (which now contains data from layout calculation) into a Host View Tree with rendered pixels on the screen. As a reminder, the React Element Tree looks like this: 


<View> 
<Text>Hello, World</Text> 
</View> 
 

At a high level, React Native renderer creates a corresponding Host View for each React Shadow Node and mounts it on screen. In the example above, the renderer creates an instance of android.view.ViewGroup for the <View> and android.widget.TextView for <Text> and populates it with “Hello World”. Similarly for iOS a UIView is created with and text is populated with a call to NSLayoutManager. Each host view is then configured to use props from its React Shadow Node, and its size and position is configured using the calculated layout information. 


Step two 


In more detail, the mounting phase consists of these three steps: 


  • Tree Diffing: This step computes the diff between the “previously rendered tree” and the “next tree” entirely in C++. The result is a list of atomic mutation operations to be performed on host views (e.g. createViewupdateViewremoveViewdeleteView, etc). This step is also where the React Shadow Tree is flattened to avoid creating unnecessary host views. See View Flattening for details about this algorithm. 


  • Tree Promotion (Next Tree → Rendered Tree): This step atomically promotes the “next tree” to “previously rendered tree” so that the next mount phase computes a diff against the proper tree. 


  • View Mounting: This step applies the atomic mutation operations onto corresponding host views. This step executes in the host platform on UI thread. 


Additional Details 


  • The operations are synchronously executed on UI thread. If the commit phase executes on background thread, the mounting phase is scheduled for the next “tick” of UI thread. On the other hand, if the commit phase executes on UI thread, mounting phase executes synchronously on the same thread. 


  • Scheduling, implementation, and execution of the mounting phase heavily depends on the host platform. For example, the renderer architecture of the mounting layer currently differs between Android and iOS. 


  • During the initial render, the “previously rendered tree” is empty. As such, the tree diffing step will result in a list of mutation operations that consists only of creating views, setting props, and adding views to each other. Tree diffing becomes more important for performance when processing React State Updates. 


  • In current production tests, a React Shadow Tree typically consists of about 600-1000 React Shadow Nodes (before view flattening), the trees get reduced to ~200 nodes after view flattening. On iPad or desktop apps, this quantity may increase 10-fold. 

 
 

React State Updates  

 

Let’s explore each phase of the render pipeline when the state of a React Element Tree is updated. Let’s say, you’ve rendered the following component in an initial render: 


function MyComponent() { 
return ( 
<View> 
<View 
style={{ backgroundColor: 'red', height: 20, width: 20 }} 
/> 
<View 
style={{ backgroundColor: 'blue', height: 20, width: 20 }} 
/> 
</View> 
); 
} 
 

Applying what was described in the Initial Render section, you would expect the following trees to be created: 


Render pipeline 4 

Notice that Node 3 maps to a host view with a red background, and Node 4 maps to a host view with a blue background. Assume that as the result of a state update in JavaScript product logic, the background of the first nested <View> changes from 'red' to 'yellow'. This is what the new React Element Tree might look: 


<View> 
<View 
style={{ backgroundColor: 'yellow', height: 20, width: 20 }} 
/> 
<View 
style={{ backgroundColor: 'blue', height: 20, width: 20 }} 
/> 
</View> 
 

How is this update processed by React Native? 


When a state update occurs, the renderer needs to conceptually update the React Element Tree in order to update the host views that are already mounted. But in order to preserve thread safety, both the React Element Tree as well as the React Shadow Tree must be immutable. This means that instead of mutating the current React Element Tree and React Shadow Tree, React must create a new copy of each tree which incorporates the new props, styles, and children. 


Let’s explore each phase of the render pipeline during a state update. 


Phase 1. Render  

 

Phase one: render


 

When React creates a new React Element Tree that incorporates the new state, it must clone every React Element and React Shadow Node that is impacted by the change. After cloning, the new React Shadow Tree is committed. 


React Native renderer leverages structural sharing to minimize the overhead of immutability. When a React Element is cloned to include the new state, every React Element that is on the path up to the root is cloned. React will only clone a React Element if it requires an update to its props, style, or children. Any React Elements that are unchanged by the state update are shared by the old and new trees. 


In the above example, React creates the new tree using these operations: 


  1. CloneNode(Node 3, {backgroundColor: 'yellow'}) → Node 3' 

  1. CloneNode(Node 2) → Node 2' 

  1. AppendChild(Node 2'Node 3') 

  1. AppendChild(Node 2'Node 4) 

  1. CloneNode(Node 1) → Node 1' 

  1. AppendChild(Node 1'Node 2') 


After these operations, Node 1' represents the root of the new React Element Tree. Let's assign T to the “previously rendered tree” and T' to the “new tree”: 


Render pipeline 5 

Notice how T and T' both share Node 4. Structural sharing improves performance and reduces memory usage. 


Phase 2. Commit 

 

Phase two: commit


 

After React creates the new React Element Tree and React Shadow Tree, it must commit them. 


  • Layout Calculation: Similar to Layout Calculation during Initial Render. One important difference is that layout calculation may cause shared React Shadow Nodes to be cloned. This can happen because if the parent of a shared React Shadow Node incurs a layout change, the layout of the shared React Shadow Node may also change. 


  • Tree Promotion (New Tree → Next Tree): Similar to Tree Promotion during Initial Render. 


  • Tree Diffing: This step computes the diff between the “previously rendered tree” (T) and the “next tree” (T'). The result is a list of atomic mutation operations to be performed on host views. 


  • In the above example, the operations consist of: UpdateView(**Node 3'**, {backgroundColor: '“yellow“}) 


Phase 3. Mount 

 

Phase three: mount 


  • Tree Promotion (Next Tree → Rendered Tree): This step atomically promotes the “next tree” to “previously rendered tree” so that the next mount phase computes a diff against the proper tree. Diff can be calculated for any currently mounted tree with any new tree. The renderer can skip some intermediate versions of the tree. 


  • View Mounting: This step applies the atomic mutation operations onto corresponding host views. In the above example, only the backgroundColor of View 3 will be updated (to yellow). 


Render pipeline 6 

 
 

React Native Renderer State Updates 

 

For most information in the Shadow Tree, React is the single owner and single source of truth. All data originates from React and there is a single-direction flow of data. 


However, there is one exception and important mechanism: components in C++ can contain state that is not directly exposed to JavaScript, and JavaScript is not the source of truth. C++ and Host Platform control this C++ State. Generally, this is only relevant if you are developing a complicated Host Component that needs C++ State. The vast majority of Host Components do not need this functionality. 


For example, ScrollView uses this mechanism to let the renderer know what’s the current offset. The update is triggered from the host platform, specifically from the host view that represents the ScrollView component. The information about offset is used in an API like measure. Since this update stems from the host platform, and does not affect the React Element Tree, this state data is held by C++ State. 


Conceptually, C++ State updates are similar to the React State Updates described above. With two important differences: 


  1. They skip the “render phase” since React is not involved. 


  1. The updates can originate and happen on any thread, including the main thread. 


Phase 2. Commit 

 

Phase two: commit


 

When performing a C++ State update, a block of code requests an update of a ShadowNode (N) to set C++ State to value S. React Native renderer will repeatedly attempt to get the latest committed version of N, clone it with a new state S, and commit N’ to the tree. If React, or another C++ State update, has performed another commit during this time, the C++ State commit will fail and the renderer will retry the C++ State update many times until a commit succeeds. This prevents source-of-truth collisions and races. 

 

Phase 3. Mount 

 

Phase three: mount 


The Mount Phase is practically identical to the Mount Phase of React State Updates. The renderer still needs to recompute layout perform a tree diff, etc. 

 

Cross Platform Implementation 

 

The React Native renderer utilizes a core render implementation to be shared across platforms. 

 

In the previous render system of React Native, the React Shadow Tree, layout logic, and View Flattening algorithm were implemented once for each platform. The current renderer was designed to be a cross-platform solution by sharing a core C++ implementation. 


The React Native team intends to incorporate an animation system into the render system and also extend the React Native render system to new platforms such as Windows, and operating systems in game consoles, televisions, and more. 


Leveraging C++ for the core render system introduces several advantages. A single implementation reduces the cost of development and maintenance. It improves the performance of creating React Shadow Trees and layout calculation because the overhead of integrating Yoga with the renderer is minimized on Android (i.e. no more JNI for Yoga). Finally, the memory footprint of each React Shadow Node is smaller in C++ than it would be if allocated from Kotlin or Swift. 


The team is also leveraging C++ features that enforce immutability to ensure there are no issues related to concurrent access to shared but not protected resources. 


It is important to recognize that the renderer use case for Android still incurs the cost of JNI for two primary use cases: 


  • Layout calculation of complex views (e.g. TextTextInput, etc.) requires sending props over JNI. 


  • The mount phase requires sending mutation operations over JNI. 


The team is exploring replacing ReadableMap with a new mechanism to serialize data using ByteBuffer to reduce overhead of JNI. Our goal is to reduce overhead of JNI by 35–50%. 


The renderer provides two sides of its C++ APIs: 

  • (i) to communicate with React 

  • (ii) to communicate with the host platform 


For (i), React communicates with the renderer to render a React Tree and to “listen” for events (e.g. onLayoutonKeyPress, touch, etc). 


For (ii), the React Native renderer communicates with the host platform to mount host views on the screen (create, insert, update or delete of host views) and it listens for events that are generated by the user on the host platform. 

Cross-platform implementation diagram 

View Flattening 

 

View Flattening is an optimization by the React Native renderer to avoid deep layout trees. 

 

The React API is designed to be declarative and reusable through composition. This provides a great model for intuitive development. However, in implementation, these qualities of the API lead to the creation of deep React Element Trees, where a large majority of React Element Nodes only affect the layout of a View and don’t render anything on the screen. We call these types of nodes “Layout-Only” Nodes. 


Conceptually, each of the Nodes of the React Element Tree have a 1:1 relationship with a view on the screen, therefore rendering a deep React Element Tree that is composed by a large amount of “Layout-Only” Node leads to poor performance during rendering. 


Here is a common use case that is affected by the cost of "Layout Only" views. 


Imagine you want to render an image and a title that is handled by the TitleComponent, and you include this component as a child of the ContainerComponent that has some margin styles. After decomposing the components, the React code would look like this: 


function MyComponent() { 
return ( 
<View> // ReactAppComponent 
<View style={{margin: 10}} /> // ContainerComponent 
<View style={{margin: 10}}> // TitleComponent 
<Image {...} /> 
<Text {...}>This is a title</Text> 
</View> 
</View> 
</View> 
); 
} 
 

As part of the render process, React Native will produce the following trees: 


Diagram one 

Note that the Views (2) and (3) are “Layout Only” views, because they are rendered on the screen but they only render a margin of 10 px on top of their children. 


To improve the performance of these types of React Element Trees, the renderer implements a View Flattening mechanism that merges or flattens these types of Nodes, reducing the depth of the host view hierarchy that is rendered on the screen. This algorithm takes into consideration props like: marginpaddingbackgroundColoropacity, etc. 


The View Flattening algorithm is integrated by design as part of the diffing stage of the renderer, which means that we don’t use extra CPU cycles to optimize the React Element Tree flattening these types of views. As the rest of the core, the View flattening algorithm is implemented in C++ and its benefits are shared by default on all supported platforms. 


In the case of the previous example, the Views (2) and (3) would be flattened as part of the “diffing algorithm” and as a result their styles will be merged into the View (1): 


Diagram two 

It is important to note that this optimization allows the renderer to avoid the creation and render of two host views. From the user’s perspective there are no visible changes on the screen. 

 

Threading Model 

 

The React Native renderer distributes the work of the render pipeline across multiple threads. 

 

Here we define the threading model and provide some examples to illustrate thread usage of the render pipeline. 


React Native renderer is designed to be thread safe. At a high level thread safety is guaranteed by using immutable data structures in the internals of the framework (enforced by C++ “const correctness” feature). This means that every update in React creates or clones new objects in the renderer instead of updating data structures. This allows the framework to expose thread safe and synchronous APIs to React. 


The renderer uses three different threads: 

  • UI thread (often called main): The only thread that can manipulate host views. 

  • JavaScript thread: This is where React’s render phase is executed. 

  • Background thread: Thread dedicated to layout. 


Let’s review the supported scenarios of execution for each phase: 


Threading model symbols 

Render Scenarios 

 

Render in a Background Thread 

 

This is the most common scenario where most of the render pipeline happens on JavaScript and background thread. 


Threading model use case one 
 

Render in the UI Thread 

 

When there is a high priority event on the UI Thread, the renderer is able to execute all the render pipeline synchronously on the UI thread. 


Threading model use case two 
 

Default or continuous event interruption 

 

This scenario shows the interruption of the render phase by a low priority event in the UI thread. React and the React Native renderer are able to interrupt the render phase and merge its state with a low priority event that is executed on the UI thread. In this case the render process continues executing in the background thread. 


Threading model use case three 
 

Discrete event interruption 

 

The render phase is interruptible. This scenario shows the interruption of the render phase by a high priority event in the UI thread. React and the renderer are able to interrupt the render phase and merge its state with a high priority event that was executed on the UI thread. The render phase executes synchronously on the UI thread. 


Threading model use case four 
 

Background thread batches updates from JavaScript 

 

Before background thread dispatches update to UI thread, it checks if a newer update hasn’t come in from JavaScript. This way, the renderer doesn’t render stale state when it knows a newer state is coming in. 


Threading model use case five 
 

C++ State update 

 

Update originating on UI thread and skips rendering phase. See React Native Renderer State Updates for more details. 


Threading model use case six 

 

  • Build Tools 


Bundled Hermes 

 

This page gives an overview of how Hermes and React Native are built. 


If you're looking into instructions on how to use Hermes in your app, you can find instructions on this other page: using Hermes.

 

What is 'Bundled Hermes' 

 

Starting with React Native 0.69.0, every version of React Native will be built alongside to a Hermes version. We call this distribution model Bundled Hermes. 

From 0.69 on, you will always have a JS engine that has been built and tested alongside each React Native version that you can use. 


Why we moved to 'Bundled Hermes' 

 

Historically, React Native and Hermes followed two distinct release processes with distinct versioning. Having distinct releases with distinct numbers created confusion in the OSS ecosystem, where it was not clear if a specific version of Hermes was compatible with a specific version of React Native (i.e. you needed to know that Hermes 0.11.0 was compatible only with React Native 0.68.0, etc.) 


Both Hermes and React Native, share the JSI code (Hermes here and React Native here). If the two JSI copies of JSI get out of sync, a build of Hermes won't be compatible with a build of React Native. You can read more about this ABI incompatibility problem here. 


To overcome this problem, we've extended the React Native release process to download and build Hermes and made sure only one copy of JSI is used when building Hermes. 


we can release a version of Hermes whenever we release a version of React Native, and be sure that the Hermes engine we built is fully compatible with the React Native version we're releasing. We're shipping this version of Hermes alongside the React Native version we're doing, hence the name Bundled Hermes. 


How this will impact app developers 

 

As mentioned in the introduction, if you're an app developer, this change should not affect you directly. 


The following paragraphs describe which changes we did under the hood and explains some of the rationales, for the sake of transparency. 


iOS Users 

 

On iOS, we've moved the hermes-engine you're using. 


Prior to React Native 0.69, users would download a pod (here you can find the podspec). 


On React Native 0.69, users would instead use a podspec that is defined inside the sdks/hermes-engine/hermes-engine.podspec file in the react-native NPM package.


That podspec relies on a pre-built tarball of Hermes that we upload on GitHub Releases, as part of the React Native release process (i.e. see the assets of this release). 


Android Users 

 

On Android, we're going to update the android/app/build.gradle file in the default template the following way: 


dependencies { 
// ... 
 
if (enableHermes) { 
+ implementation("com.facebook.react:hermes-engine:+") { 
+ exclude group:'com.facebook.fbjni' 
+ } 
- def hermesPath = "../../node_modules/hermes-engine/android/"; 
- debugImplementation files(hermesPath + "hermes-debug.aar") 
- releaseImplementation files(hermesPath + "hermes-release.aar") 
} else { 
implementation jscFlavor 
} 
} 
 

Prior to React Native 0.69, users will be consuming hermes-debug.aar and hermes-release.aar from the hermes-engine NPM package. 


On React Native 0.69, users will be consuming the Android multi-variant artifacts available inside the android/com/facebook/react/hermes-engine/ folder in the react-native NPM package. Please also note that we're going to remove the dependency on hermes-engine entirely in one of the future version of React Native. 


Android Users on New Architecture 

 

Due to the nature of our native code build setup (i.e. how we use the NDK), users on the New Architecture will be building Hermes from source. 


This aligns the build mechanism of React Native and Hermes for users on the New Architecture (they will build both framework from source). This means that such Android users might experience a performance hit at build time on their first build. 


You can find instructions to optimize your build time and reduce the impact on your build on this page: Speeding up your Build phase. 


Android Users on New Architecture building on Windows 

 

Users building React Native App, with the New Architecture, on Windows machines need to follow those extra steps to let the build work correctly: 


  • Make sure the Visual Studio Command Prompt is configured correctly. This is required as the proper C++ compiler environment variable is configured in those command prompt. 


  • Run the app with npx react-native run-android inside a Visual Studio Command Prompt. 


Can users still use another engine? 

 

Yes, users are free to enable/disable Hermes (with the enableHermes variable on Android, hermes_enabled on iOS). The 'Bundled Hermes' change will impact only how Hermes is built and bundled for you. 


Please note that, at the time of writing, the default for enableHermes/hermes_enabled is false. We're looking into updating the template to have it default to true in the near future. 


How this will impact contributor and extension developers 

 

If you're a React Native contributor or you're building an extension on top of React Native or Hermes, please read further as we explain how Bundled Hermes works. 


How is Bundled Hermes working under the hood? 

 

This mechanism relies on downloading a tarball with the Hermes source code from the facebook/hermes repository inside the facebook/react-native repository. We have a similar mechanism in place for other native dependencies (Folly, Glog, etc.) and we aligned Hermes to follow the same setup. 


When building React Native from main, we will be fetching a tarball of main of facebook/hermes and building it as part of the build process of React Native. 


When building React Native from a release branch (say 0.69-stable), we will instead use a tag on the Hermes repo to syncronize the code between the two repos. The specific tag name used will then be stored inside the sdks/.hermesversion file inside React Native in the release branch (e.g. this is the file on the 0.69 release branch). 


In a sense, you can think of this approach similarly to a git submodule. 


If you're building on top of Hermes, you can rely on those tags to understand which version of Hermes was used when building React Native, as the version of React Native is specified in the tag name (e.g. hermes-2022-05-20-RNv0.69.0-ee8941b8874132b8f83e4486b63ed5c19fc3f111). 


Android implementation details 

 

To implement this on Android, we've added a new build inside the /ReactAndroid/hermes-engine of React Native that will take care of building Hermes and packaging for consumption ((See here for more context)[https://github.com/facebook/react-native/pull/33396]). 


You can now trigger a build of Hermes engine by invoking: 

// Build a debug version of Hermes 
./gradlew :ReactAndroid:hermes-engine:assembleDebug 
// Build a release version of Hermes 
./gradlew :ReactAndroid:hermes-engine:assembleRelease 
 

from the React Native main branch. 


You won't need to install extra tools (such as cmakeninja or python3) in your machine as we configured the build to use the NDK versions of those tools. 


On the Gradle consumer side, we also shipped a small improvement on the consumer side: we moved from releaseImplementation & debugImplementation to implementation. This is possible because the newer hermes-engine Android artifact is variant aware and will properly match a debug build of the engine with a debug build of your app. You don't need any custom configuration here (even if you use staging or other build types/flavors). 


However, this made this line necessary in the template: 


exclude group:'com.facebook.fbjni' 
 

This is needed as React Native is consuming fbjni using the non-prefab approach (i.e. unzipping the .aar and extracting .so files). Hermes-engine, and other libraries, are using prefab instead to consume fbjni. We're looking into addressing this issue in the future so the Hermes import will be a oneliner. 


iOS implementation details 

 

The iOS implementation relies on a series of scripts that lives in the following locations: 

  • /scripts/hermes. Those scripts contain logic to download the hermes tarball, unzip it, and configure the iOS build. They're invoked at pod install time if you have the hermes_enabled field set to true. 


  • /sdks/hermes-engine. Those scripts contain the build logic that is effectively building Hermes. They were copied and adapted from the facebook/hermes repo to properly work within React Native. Specifically, the scripts inside the utils folder are responsible of building Hermes for all the Mac platforms. 


To distribute a prebuilt for iOS, we rely on the build_hermes_macos Job on CircleCI. The job will produce as artifact a tarball which will be downloaded by the hermes-engine podspec (i.e. those are the artifacts for an execution for React Native 0.69). 


I'm afraid this change is impacting me 

 

We'd like to stress that this is essentially an organizational change on where Hermes is built and how the code is syncronized between the two repositories. The change should be fully transparent to our users. 


Historically, we used to cut a release of Hermes for a specific version of React Native (e.g. v0.11.0 for RN0.68.x). 


With 'Bundled Hermes', you can instead rely on a tag that will represent the version used when a specific version of React Native was cut. 


  • Glossary 

 

Fabric Renderer 

 

React Native executes the same React framework code as React for the web. However, React Native renders to general platform views (host views) instead of DOM nodes (which can be considered web’s host views). Rendering to host views is made possible by the Fabric Renderer. Fabric lets React talk to each platform and manage its host view instances. The Fabric Renderer exists in JavaScript and targets interfaces made available by C++ code. Read more about React renderers in this blog post. 


Host platform 

 

The platform embedding React Native (e.g., Android, iOS, macOS, Windows). 


Host View Tree (and Host View) 

 

Tree representation of views in the host platform (e.g. Android, iOS). On Android, the host views are instance of android. View. ViewGroup,  android. Widget. TextView, etc.  

which are the building blocks of the host view tree. The size and location of each host view are based on LayoutMetrics calculated with Yoga, and the style and content of each host view are based on information from the React Shadow Tree. 


JavaScript Interfaces (JSI) 

 

A lightweight API to embed a JavaScript engine in a C++ application. Fabric uses it to communicate between Fabric’s C++ core and React. 


Java Native Interface (JNI) 

 

An API for to write Java native methods used to communicate between Fabric’s C++ core and Android, written in Java. 


React Component 

 

A JavaScript function or class that instructs how to create a React Element. Read more about React components, elements in this blog post. 


React Composite Components 

 

React Components with render implementations that reduce to other React Composite Components or React Host Components. 


React Host Components or Host Components 

 

React Components whose view implementation is provided by a host view (e.g., <View>, <Text> ). On the Web, ReactDOM's Host components would be components like <p> and <div>. 


React Element Tree (and React Element) 

 

React Element Tree is created by React in JavaScript and consists of React Elements. A React Element is a plain JavaScript object that describes what should appear on the screen. It includes props, styles, and children. React Elements only exist in JavaScript and can represent instantiations of either React Composite Components or React Host Components. Read more about React components and elements in this blog post.

 

React Shadow Tree (and React Shadow Node) 

 

React Shadow Tree is created by the Fabric Renderer and consists of React Shadow Nodes. A React Shadow Node is an object that represents a React Host Component to be mounted, and contains props that originate from JavaScript. They also contain layout information (x, y, width, height). In Fabric, React Shadow Node objects exist in C++. Before Fabric, these existed in the mobile runtime heap (e.g. Android JVM). 


Yoga Tree (and Yoga Node) 

 

The Yoga Tree is used by Yoga to calculate layout information for a React Shadow Tree. Each React Shadow Node typically creates a Yoga Node because React Native employs Yoga to calculate layout. However, this is not a hard requirement. Fabric can also create React Shadow Nodes that do not use Yoga; the implementation of each React Shadow Node determines how to calculate layout. 

Comments

Popular posts from this blog

Oracle Database Server Architecture: Overview

Oracle E-Business Suite (EBS) - Introduction

Advantages and Disadvantages of React Native