Skip to content

Commit

Permalink
feat: Add page on how to use the CodeGen
Browse files Browse the repository at this point in the history
  • Loading branch information
Riccardo Cipolleschi committed Jun 13, 2022
1 parent 4a6a317 commit 613ac7b
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 2 deletions.
224 changes: 222 additions & 2 deletions docs/the-new-architecture/pillars-codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,225 @@ id: pillars-codegen
title: CodeGen
---

This section contains a refactoring of the [Appendix](../new-architecture-appendix) focused on the codegen details.
Further sections that needs to refer to the codegen should refer to this page, instead.
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import constants from '@site/core/TabsConstants';

The **CodeGen** is not a proper pillar, but it is a tool we developed to avoid writing of a lot of boilerplate code. Using the **CodeGen** is not mandatory: all the code that is generated by it can also be written manually. However, it generates boilerplate code that could save you a lot of time.

The **CodeGen** is invoked automatically by React Native every time we build an iOS or an Android app. There are cases when we would like to generate the code manually to know a type or of a file name that we need: this is a common scenario when developing **TurboModules** and **Fabric Components**, for example.

This guide teaches how to configure the **CodeGen**, how to invoke it manually for each platform, and it describes the generated code.

# Prerequisites

We always need a React Native app to generate the code properly, even if we are invoking the **CodeGen** manually.

The **CodeGen** process is tightly coupled with the build of the app and we are going to use some scripts that are located in the `react-native` NPM package.

For the sake of this guide, we're going to assume you created a project using the React Native CLI as follows:

```sh
npx react-native init SampleApp --version 0.70.0
```

:::note
This guide assumes that we are using a version of React Native that is 0.70.0 or greater.
Previous versions of React Native uses a **CodeGen** that requires a slightly different setup.
:::

Then, we need to add the module that requires the **CodeGen** as an NPM dependency of the app. This can be done with the command:

```sh
yarn add <path/to/your/TurboModule_or_FabricComponent>
```

See how to create a [TurboModule](./pillars-turbomodule) or a [Fabric Component](./pillar-fabric-components) to get more information on how to configure them.

The rest of this guide assumes that you have a `TurboModule` and/or a `Fabric Component` properly set up.

# iOS

## Running the CodeGen

The **CodeGen** for iOS relies on some Node scripts that are invoked during the build process. The scripts are located in the `MyApp/node_modules/react_native/scripts/` folder.

The script to run is the `generate-artifacts.js` script. This searches among all the dependencies of the app, looking for JS files which respects some specific conventions (look at [TurboModules](./pillars-turbomodule) and [Fabric Components](./pillar-fabric-components) sections for details) and generates the required code.

To invoke the script we can run this command from the root folder of our app:

```sh
node node_modules/react_native/scripts/generate-artifacts.js \
--path MyApp/ \
--outputPath <an/output/path> \
```

Given that the app has `TurboModules` and/or `Fabric Components` configured as a dependency, the **CodeGen** will look for all of them and it will generate the code in the path you provided.

## The Generated Code

If we run the **CodeGen** in our app with an output path of `codegen`, we obtain the following structure:

```title="iOS CodeGen output"
codegen
└── build
└── generated
└── ios
├── MyTurboModuleSpecs
│ ├── MyTurboModuleSpecs-generated.mm
│ └── MyTurboModuleSpecs.h
├── FBReactNativeSpec
│ ├── FBReactNativeSpec-generated.mm
│ └── FBReactNativeSpec.h
├── RCTThirdPartyFabricComponentsProvider.h
├── RCTThirdPartyFabricComponentsProvider.mm
└── react
└── renderer
└── components
├── MyFabricComponent
│ ├── ComponentDescriptors.h
│ ├── EventEmitters.cpp
│ ├── EventEmitters.h
│ ├── Props.cpp
│ ├── Props.h
│ ├── RCTComponentViewHelpers.h
│ ├── ShadowNodes.cpp
│ └── ShadowNodes.h
└── rncore
├── ComponentDescriptors.h
├── EventEmitters.cpp
├── EventEmitters.h
├── Props.cpp
├── Props.h
├── RCTComponentViewHelpers.h
├── ShadowNodes.cpp
└── ShadowNodes.h
```

At the root of the hierarchy, we have the `codegen` folder, as expected. Nested into it we have a couple more folders: `build/generated`.

Then, we have an `ios` folder which contains:

- A custom folder for each TurboModule.
- The header (`.h`) and implementation (`.mm`) files for the `RCTThirdPartyFabricComponentsProvider`.
- A base `react/renderer/components` folder which contains a custom folder for each `Fabric Component`.

In this hierarchy, there are a TurboModule and a set of Fabric Components that are generated by React Native itself: `FBReactNativeSpec` and `rncore`. These will appear even if we don't have any extra TurboModule or Fabric Component: React Native requires them in order to work properly.

### TurboModules

The various TurboModules folders contain two files for each TurboModule: an interface file and an implementation file.

The interface files have the same name of the TurboModule and they contain methods to initialize their the JSI interface.

The implementation files, instead, have the `-generated` suffix and they contains the logic to invoke the native methods from JS and viceversa.

### Fabric Components

The content of each Fabric Component folder contains several files. The basic element for a Fabric Componenent is the `ShadowNode`: it represents a node in the React absract tree. The `ShadowNode` represents a React entity, therefore it could need some props, which are defined in the `Props` files and, sometimes, an `EventEmitter`, defined in the corresponding file.

Additionally, the **CodeGen** also creates a `ComponentDescriptor.h` and an `RCTComponentViewHelpers.h` files: the first one is used by React Native and Fabric to properly get a reference to the Component, while the latter contains some helper methods and protocols that can be implemented by the Native View to properly respond to JSI invocations.

For further details about how Fabric works, have a look at the [Renderer](../../architecture/fabric-renderer) section.

### RCTThirdPartyFabricComponentsProvider

These are an interface and an implementation files for a registry. React Native access this registry at runtime to retrieve the right class for a required Fabric Component. Once React Native has an handle to that class, it can instantiate it.

# Android

## Running the CodeGen

Android `CodeGen` relies on a Gradle task to generate the required code. First, we need to configure the Android app to work with the New Architecture, otherwise the Gradle task will fail. We can configure the app by:

1. Open the `MyApp/android/gradle.properties` file.
1. Flip the `newArchEnabled` flag from `false` to `true`.

After that, we can navigate into the `MyApp/android` folder and run:

```sh
./gradlew generateCodegenArtifactsFromSchema --rerun-tasks
```

This tasks invokes the `generateCodegenArtifactsFromSchema` on all the the imported projects of the app (so, the app and all the node modules which are linked to it). It generates the code in the respective `node_modules/<dependency>` folder. So, for example, if we have a Fabric Component whose node module is called `my-fabric-component`, the generated code will be located in the `MyApp/node_modules/my-fabric-component/android/build/generated/source/codegen` path.

## The Generated Code

Once the Gradle task completes, we can see different structures for a TurboModule or for a Fabric Component. The following tab shows how they appear:

<Tabs groupId="android-codegen" defaultValue={constants.defaultNewArchFeature} values={constants.newArchFeatures}>
<TabItem value="turbomodules">

```sh
codegen
├── java
│ └── com
│ └── MyTurbomodule
│ └── MyTurbomodule.java
├── jni
│ ├── Android.mk
│ ├── MyTurbomodule-generated.cpp
│ ├── MyTurbomodule.h
│ └── react
│ └── renderer
│ └── components
│ └── MyTurbomodule
│ ├── ComponentDescriptors.h
│ ├── EventEmitters.cpp
│ ├── EventEmitters.h
│ ├── Props.cpp
│ ├── Props.h
│ ├── ShadowNodes.cpp
│ └── ShadowNodes.h
└── schema.json
```

</TabItem>
<TabItem value="fabric-components">

```sh
codegen
├── java
│ └── com
│ └── facebook
│ └── react
│ └── viewmanagers
│ ├── MyFabricComponentManagerDelegate.java
│ └── MyFabricComponentManagerInterface.java
├── jni
│ ├── Android.mk
│ ├── CMakeLists.txt
│ ├── MyFabricComponent-generated.cpp
│ ├── MyFabricComponent.h
│ └── react
│ └── renderer
│ └── components
│ └── MyFabricComponent
│ ├── ComponentDescriptors.h
│ ├── EventEmitters.cpp
│ ├── EventEmitters.h
│ ├── Props.cpp
│ ├── Props.h
│ ├── ShadowNodes.cpp
│ └── ShadowNodes.h
└── schema.json
```

</TabItem>
</Tabs>

Android has some peculiarity due to the fact that Java can't interoperate seamlessly with C++. To work properly, the **CodeGen** creates some bridging within the two worlds in the `jni` folder, where the Java Native Interfaces are defined.

Before describing the specific files, note that both TurboModules and Fabric Components comes with a couple of build file descriptors: the `Android.mk` and the `CMakeLists.txt`. These are used by the Android app to actually build the external modules.

### TurboModule

The **CodeGen** generates a java abstract class in the `java` package which the same name of the TurboModule. This abstract class has to be implemented by the JNI C++ implementation.

Then, it generates the C++ files in the `jni` folder. They follow the same iOS convention: it creates an interface called `MyTurbomodule.h` and an implementation file called `MyTurbomodule-generated.cpp`. The former is an interface that allows React Natvie to initialize the JSI interface for the TurboModule. The latter is the implementation file which contains the logic to invoke the native method from JS and viceversa.

### Fabric Component

The **CodeGen** for a Fabric Component contains a `MyFabricComponentManagerInterface.java` and a `MyFabricComponentManagerDelegate.java` in the `java` package. They are implemented and used by the native `MyFabricComponentManager` required to properly load the component at runtime (See the guide on how to create a [Fabric Component](./pillar-fabric-components) for details).

Then, it generates a layer of JNI C++ files that are used by Fabric to render the components. The basic element for a Fabric Componenent is the `ShadowNode`: it represents a node in the React absract tree. The `ShadowNode` represents a React entity, therefore it could need some props, which are defined in the `Props` files and, sometimes, an `EventEmitter`, defined in the corresponding file.

The **CodeGen** also creates a `ComponentDescriptor.h` which is required to get a proper handle to the Fabric Component.
9 changes: 9 additions & 0 deletions website/core/TabsConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ const oses = [
];
const defaultOs = isMacOS ? 'macos' : isWindows ? 'windows' : 'linux';

const newArchFeatures = [
{label: 'TurboModules', value: 'turbomodules'},
{label: 'Fabric Components', value: 'fabric-components'},
];

const defaultNewArchFeature = 'turbomodules';

const getDevNotesTabs = (tabs = ['android', 'ios', 'web', 'windows']) =>
[
tabs.includes('android') ? {label: 'Android', value: 'android'} : undefined,
Expand All @@ -59,11 +66,13 @@ export default {
defaultPlatform,
defaultSyntax,
defaultAndroidLanguage,
defaultNewArchFeature,
getDevNotesTabs,
guides,
oses,
packageManagers,
platforms,
syntax,
androidLanguages,
newArchFeatures,
};

0 comments on commit 613ac7b

Please sign in to comment.