From 5a9622f9c92870cabed4368e2b0392db7c5f8ba9 Mon Sep 17 00:00:00 2001 From: Riccardo Date: Tue, 29 Mar 2022 09:51:40 +0100 Subject: [PATCH 01/15] [Do Not Merge] Refactoring The New Architecture (#3029) --- ...ackward-compatibility-fabric-components.md | 12 ++++ .../backward-compatibility-troubleshooting.md | 6 ++ .../backward-compatibility-turbomodules.md | 12 ++++ .../backward-compatibility.md | 8 +++ docs/the-new-architecture/landing-page.md | 13 ++++ docs/the-new-architecture/pillars-codegen.md | 7 +++ .../pillars-fabric-components.md | 19 ++++++ .../pillars-turbomodule.md | 18 ++++++ docs/the-new-architecture/pillars.md | 10 +++ docs/the-new-architecture/use-app-template.md | 7 +++ docs/the-new-architecture/why.md | 7 +++ website/sidebars.json | 63 ++++++++++++++----- 12 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 docs/the-new-architecture/backward-compatibility-fabric-components.md create mode 100644 docs/the-new-architecture/backward-compatibility-troubleshooting.md create mode 100644 docs/the-new-architecture/backward-compatibility-turbomodules.md create mode 100644 docs/the-new-architecture/backward-compatibility.md create mode 100644 docs/the-new-architecture/landing-page.md create mode 100644 docs/the-new-architecture/pillars-codegen.md create mode 100644 docs/the-new-architecture/pillars-fabric-components.md create mode 100644 docs/the-new-architecture/pillars-turbomodule.md create mode 100644 docs/the-new-architecture/pillars.md create mode 100644 docs/the-new-architecture/use-app-template.md create mode 100644 docs/the-new-architecture/why.md diff --git a/docs/the-new-architecture/backward-compatibility-fabric-components.md b/docs/the-new-architecture/backward-compatibility-fabric-components.md new file mode 100644 index 00000000000..a496d5391f0 --- /dev/null +++ b/docs/the-new-architecture/backward-compatibility-fabric-components.md @@ -0,0 +1,12 @@ +--- +id: backward-compatibility-fabric-components +title: Fabric Components as Native Components +--- + +This section describes the required steps to ensure that a Fabric Component can be used as a Native Component. + +The section should explain: + +- How to avoid installing dependencies when they are not needed +- The usage of compilation pragmas to avoid compiling code that requires types from the codegen +- API uniformity in JS, so that they don’t have to import different files diff --git a/docs/the-new-architecture/backward-compatibility-troubleshooting.md b/docs/the-new-architecture/backward-compatibility-troubleshooting.md new file mode 100644 index 00000000000..d2091da5d91 --- /dev/null +++ b/docs/the-new-architecture/backward-compatibility-troubleshooting.md @@ -0,0 +1,6 @@ +--- +id: backward-compatibility-troubleshooting +title: Troubleshooting +--- + +This section contains solutions to common problems that can happen when developing a backward compatible module or component. diff --git a/docs/the-new-architecture/backward-compatibility-turbomodules.md b/docs/the-new-architecture/backward-compatibility-turbomodules.md new file mode 100644 index 00000000000..3fd61393243 --- /dev/null +++ b/docs/the-new-architecture/backward-compatibility-turbomodules.md @@ -0,0 +1,12 @@ +--- +id: backward-compatibility-turbomodules +title: TurboModules as Native Modules +--- + +This section describes the required steps to ensure that a TurboModule can be used as a Native Module. + +The section explains: + +- How to avoid installing dependencies when they are not needed +- The usage of compilation pragmas to avoid compiling code that requires types from the codegen +- API uniformity in JS, so that they don’t have to import different files diff --git a/docs/the-new-architecture/backward-compatibility.md b/docs/the-new-architecture/backward-compatibility.md new file mode 100644 index 00000000000..426094a7297 --- /dev/null +++ b/docs/the-new-architecture/backward-compatibility.md @@ -0,0 +1,8 @@ +--- +id: backward-compatibility +title: What Backward Compatibility Is +--- + +This section contains a brief paragraph explaining why library developers should be mindful about backward compatibility. + +It could contains shared section between the backward compatibility for TurboModules and Native Components. diff --git a/docs/the-new-architecture/landing-page.md b/docs/the-new-architecture/landing-page.md new file mode 100644 index 00000000000..dc83d361c85 --- /dev/null +++ b/docs/the-new-architecture/landing-page.md @@ -0,0 +1,13 @@ +--- +id: landing-page +title: Introduction +--- + +This section is the entry point for the new guide’s documentation. +It contains some basic information about the New Architecture: its pillars, the version from which it has been made available and other very high-level information. Then, it should present the guide’s structure itself. + +This section should also contains a sort of Driving guide based on the user use-cases: + +- New user: link to how to use the new [app template](use-app-template) +- New Library developer: link to the [pillars](pillars) +- Old library developer/app developer: link to [the migration guide](../new-architecture-intro) diff --git a/docs/the-new-architecture/pillars-codegen.md b/docs/the-new-architecture/pillars-codegen.md new file mode 100644 index 00000000000..0ccd35ec4db --- /dev/null +++ b/docs/the-new-architecture/pillars-codegen.md @@ -0,0 +1,7 @@ +--- +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. diff --git a/docs/the-new-architecture/pillars-fabric-components.md b/docs/the-new-architecture/pillars-fabric-components.md new file mode 100644 index 00000000000..72892522d16 --- /dev/null +++ b/docs/the-new-architecture/pillars-fabric-components.md @@ -0,0 +1,19 @@ +--- +id: pillars-fabric-components +title: Fabric Components +--- + +This section contains a high-level introduction to Fabric components. It provides enough context to understand when a Fabric component is needed and how it roughly works. +It points to the [Renderer](https://reactnative.dev/architecture/fabric-renderer) section of the [Architecture](https://reactnative.dev/architecture/overview) tab for a deep dive into the technical details. + +This section must have a warning that it works only with the new architecture enabled. It points to the [migration section](../new-architecture-intro). + +## How to Create a Fabric Components + +This section is a step-by-step guide to create a Fabric component from scratch. The list of subsections is roughly: + +- JS spec (with all the supported features) +- Configuration (package.json, cocoapods, gradle, …) and CodeGen +- Native code (one section for iOS and one for Android) +- Integration in an App (`yarn add` and how to connect the JS specs to the app itself) +- Troubleshooting (common issues and how to solve them) diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md new file mode 100644 index 00000000000..ee305ee1656 --- /dev/null +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -0,0 +1,18 @@ +--- +id: pillars-turbomodules +title: TurboModules +--- + +This section contains a high-level introduction to TurboModules. It provides enough context to understand when a TurboModule is needed and how it roughly works. + +This section must have a warning that it works only with the new architecture enabled. It points to the [migration section](../new-architecture-intro). + +## How to create a Turbomodule + +This section is a step-by-step guide to create a TurboModule from scratch. The list of subsections is roughly: + +- JS spec (with all the supported features) +- Configuration (package.json, cocoapods, gradle, …) and CodeGen +- Native code (one section for iOS and one for Android) +- Integration in an App (`yarn add` and how to connect the JS specs to the app itself) +- Troubleshooting (common issues and how to solve them) diff --git a/docs/the-new-architecture/pillars.md b/docs/the-new-architecture/pillars.md new file mode 100644 index 00000000000..d86eed0c527 --- /dev/null +++ b/docs/the-new-architecture/pillars.md @@ -0,0 +1,10 @@ +--- +id: pillars +title: What Compose the New Architecture +--- + +This section recalls the main pillars from the new-architecture-intro section. + +It contains a pointer to the `Migration` section and it specifies that these pillars work only when the new architecture is enabled. It also points to the prerequisites. + +It describes how the following sections are organized. diff --git a/docs/the-new-architecture/use-app-template.md b/docs/the-new-architecture/use-app-template.md new file mode 100644 index 00000000000..d575081f322 --- /dev/null +++ b/docs/the-new-architecture/use-app-template.md @@ -0,0 +1,7 @@ +--- +id: use-app-template +title: Using the New App Template +--- + +This section describes the new app template generated by the React Native CLI and how to use it properly. +It should explain how to create a new app, which command should be issued to install the dependencies and to run it on the different platforms. diff --git a/docs/the-new-architecture/why.md b/docs/the-new-architecture/why.md new file mode 100644 index 00000000000..81450add22c --- /dev/null +++ b/docs/the-new-architecture/why.md @@ -0,0 +1,7 @@ +--- +id: why +title: Why A New Architecture +--- + +This section briefly explains why we decided to move away from the old architecture, and it describes succinctly the main benefits of adopting the new architecture. +It points to the [Architecture](https://reactnative.dev/architecture/overview) tab of the website for a deep dive into the ‘why’s. diff --git a/website/sidebars.json b/website/sidebars.json index 3bb6ad0911d..88533332f39 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -82,34 +82,69 @@ "direct-manipulation" ], "The New Architecture": [ - "new-architecture-intro", + "the-new-architecture/landing-page", + "the-new-architecture/why", + "the-new-architecture/use-app-template", { "type": "category", - "label": "Supporting in your Library", + "label": "Pillars", "collapsible": false, "collapsed": false, "items": [ - "new-architecture-library-intro", - "new-architecture-library-android", - "new-architecture-library-ios" + "the-new-architecture/pillars", + "the-new-architecture/pillars-turbomodules", + "the-new-architecture/pillars-fabric-components", + "the-new-architecture/pillars-codegen" ] }, { "type": "category", - "label": "Supporting in your App", + "label": "Backward Compatibility", "collapsible": false, "collapsed": false, "items": [ - "new-architecture-app-intro", - "new-architecture-app-modules-android", - "new-architecture-app-modules-ios", - "new-architecture-app-renderer-android", - "new-architecture-app-renderer-ios" + "the-new-architecture/backward-compatibility", + "the-new-architecture/backward-compatibility-turbomodules", + "the-new-architecture/backward-compatibility-fabric-components", + "the-new-architecture/backward-compatibility-troubleshooting" ] }, - "react-18-and-react-native", - "new-architecture-troubleshooting", - "new-architecture-appendix" + { + "type": "category", + "label": "Migration", + "collapsible": false, + "collapsed": false, + "items": [ + "new-architecture-intro", + { + "type": "category", + "label": "Supporting in your Library", + "collapsible": false, + "collapsed": false, + "items": [ + "new-architecture-library-intro", + "new-architecture-library-android", + "new-architecture-library-ios" + ] + }, + { + "type": "category", + "label": "Supporting in your App", + "collapsible": false, + "collapsed": false, + "items": [ + "new-architecture-app-intro", + "new-architecture-app-modules-android", + "new-architecture-app-modules-ios", + "new-architecture-app-renderer-android", + "new-architecture-app-renderer-ios" + ] + }, + "react-18-and-react-native", + "new-architecture-troubleshooting", + "new-architecture-appendix" + ] + } ], "Android and iOS guides": [ { From b106cd6abea63f0f4120c69f9514594a53a93bd4 Mon Sep 17 00:00:00 2001 From: Riccardo Date: Fri, 8 Apr 2022 22:07:51 +0200 Subject: [PATCH 02/15] [Guide - The New Architecture] What Backward Compatibility Is (#3038) --- .../backward-compatibility.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/the-new-architecture/backward-compatibility.md b/docs/the-new-architecture/backward-compatibility.md index 426094a7297..91588284ca8 100644 --- a/docs/the-new-architecture/backward-compatibility.md +++ b/docs/the-new-architecture/backward-compatibility.md @@ -3,6 +3,20 @@ id: backward-compatibility title: What Backward Compatibility Is --- -This section contains a brief paragraph explaining why library developers should be mindful about backward compatibility. +Creating a backward compatible module is important to provide a library that works in both the **Old Architecture** and the **New Architecture**. Not all the users of your library will immediately jump on the new architecture ship: it is a good thing that they will be able to use your library even if they are still using the old architecture. -It could contains shared section between the backward compatibility for TurboModules and Native Components. +The trick to create a good backward compatible module is to minimize the changes required to adopt the new version. In that way, users of the module can smoothly move to the new version and migrate to the new architecture when they are ready, ideally by issueing one different command. + +To achieve this result, we have to perform few changes in our **TurboModule** and **Fabric Component** configurations. The steps we have to follow are: + +1. **Update the installation configuration** to avoid downloading dependencies that are not needed by the Old Architecture. +1. **Update the code** to support both architectures. Both Android and iOS build pipelines gives you mechanism to provide a library that will compile with the correct React Native Architecture. +1. **Configure the specs to load the proper implementation**, so that the JavaScript layer leverages the New Architecture whan it is available. + +:::info +The next sections requires that you are familiar with the [Pillars](pillars) of the **New Architecture**. +::: + +- To create a backward compatible **TurboModule**, follow [this guide](backward-compatibility-turbomodules). +- To create a backward compatible **Fabric Component**, follow [this guide](backward-compatibility-fabric-components). +- If you have troubles while creating a backward compatible module, look at our [troubleshooting guide](backward-compatibility-troubleshooting). From 38eb7f2eeaed9369aed696b8f2a59f1b0b764e63 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 20 Apr 2022 07:08:59 -0700 Subject: [PATCH 03/15] Guide to creating a New Architecture app from template (#3056) * Start new template guide This is a first iteration. I want to get feedback on a few aspects, so starting from here as a baseline. * Update title * Use tabs for target OS Set up matching the style of "Getting Started", except I kept the headers inside the tabs for now as it makes for a useful right-hand TOC. * Capitalize New Architecture, simplify * Preliminary section for Hermes Add section for recommending enabling Hermes. Not sure of contents yet, and still have to test. * Reword to emphasize importance of Hermes usage * Show new arch in use * Add build speed article link * Add pro tip for pod install alias * Restructure, repeat less Favor linking to original setup guide instead of repeating content. * Note about Expo * Include command on uninstalling global CLI * How to learn more * Remove headers in tabs They don't work correctly with the righthand TOC. * Make header more clear * Use quote block less often There was way too much yellow. * Opt for instructions using XCode Because `xcodebuild clean` already failed me once when XCode GUI clean worked. * Fix lint issue * Improve wording * Use product name * Fix line wraps * Reword based on feedback * Note use of bundle install * Pod removal instructions Also standardize on using yarn scripts from template for commands, it's a little confusing to see the mix of `npx` and `yarn` once we start referring to `yarn pod-install` * Convert quotes to admonitions * Convert Note: to admonitions * Feedback: Change admonitions to caution * PR feedback --- docs/_getting-started-linux-android.md | 4 +- docs/_getting-started-macos-android.md | 4 +- docs/_getting-started-macos-ios.md | 4 +- docs/_getting-started-windows-android.md | 4 +- docs/_remove-global-cli.md | 5 + docs/the-new-architecture/use-app-template.md | 129 +++++++++++++++++- website/static/docs/assets/metro-new-arch.png | Bin 0 -> 117503 bytes 7 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 docs/_remove-global-cli.md create mode 100644 website/static/docs/assets/metro-new-arch.png diff --git a/docs/_getting-started-linux-android.md b/docs/_getting-started-linux-android.md index 0f45f1bf05b..839c6882c65 100644 --- a/docs/_getting-started-linux-android.md +++ b/docs/_getting-started-linux-android.md @@ -1,3 +1,5 @@ +import RemoveGlobalCLI from './\_remove-global-cli.md'; + ## Installing dependencies You will need Node, the React Native command line interface, a JDK, and Android Studio. @@ -77,7 +79,7 @@ React Native has a built-in command line interface. Rather than install and mana

Creating a new application

-> If you previously installed a global `react-native-cli` package, please remove it as it may cause unexpected issues. + React Native has a built-in command line interface, which you can use to generate a new project. You can access it without installing anything globally using `npx`, which ships with Node.js. Let's create a new React Native project called "AwesomeProject": diff --git a/docs/_getting-started-macos-android.md b/docs/_getting-started-macos-android.md index 2d1d724a971..b5e1cdd134b 100644 --- a/docs/_getting-started-macos-android.md +++ b/docs/_getting-started-macos-android.md @@ -1,3 +1,5 @@ +import RemoveGlobalCLI from './\_remove-global-cli.md'; + ## Installing dependencies You will need Node, Watchman, the React Native command line interface, a JDK, and Android Studio. @@ -91,7 +93,7 @@ React Native has a built-in command line interface. Rather than install and mana

Creating a new application

-> If you previously installed a global `react-native-cli` package, please remove it as it may cause unexpected issues. + React Native has a built-in command line interface, which you can use to generate a new project. You can access it without installing anything globally using `npx`, which ships with Node.js. Let's create a new React Native project called "AwesomeProject": diff --git a/docs/_getting-started-macos-ios.md b/docs/_getting-started-macos-ios.md index 285f0fbb7eb..9860a4cc4ba 100644 --- a/docs/_getting-started-macos-ios.md +++ b/docs/_getting-started-macos-ios.md @@ -1,4 +1,4 @@ -import M1Cocoapods from './\_markdown-m1-cocoapods.mdx'; +import M1Cocoapods from './\_markdown-m1-cocoapods.mdx'; import RemoveGlobalCLI from './\_remove-global-cli.md'; ## Installing dependencies @@ -57,7 +57,7 @@ React Native has a built-in command line interface. Rather than install and mana ## Creating a new application -> If you previously installed a global `react-native-cli` package, please remove it as it may cause unexpected issues. + You can use React Native's built-in command line interface to generate a new project. Let's create a new React Native project called "AwesomeProject": diff --git a/docs/_getting-started-windows-android.md b/docs/_getting-started-windows-android.md index 9605977375d..fcffddeed0f 100644 --- a/docs/_getting-started-windows-android.md +++ b/docs/_getting-started-windows-android.md @@ -1,3 +1,5 @@ +import RemoveGlobalCLI from './\_remove-global-cli.md'; +

Installing dependencies

You will need Node, the React Native command line interface, a JDK, and Android Studio. @@ -108,7 +110,7 @@ React Native has a built-in command line interface. Rather than install and mana

Creating a new application

-> If you previously installed a global `react-native-cli` package, please remove it as it may cause unexpected issues. + React Native has a built-in command line interface, which you can use to generate a new project. You can access it without installing anything globally using `npx`, which ships with Node.js. Let's create a new React Native project called "AwesomeProject": diff --git a/docs/_remove-global-cli.md b/docs/_remove-global-cli.md new file mode 100644 index 00000000000..08d0d9ba3c1 --- /dev/null +++ b/docs/_remove-global-cli.md @@ -0,0 +1,5 @@ +> If you previously installed a global `react-native-cli` package, please remove it as it may cause unexpected issues: +> +> ```shell +> npm uninstall -g react-native-cli @react-native-community/cli +> ``` diff --git a/docs/the-new-architecture/use-app-template.md b/docs/the-new-architecture/use-app-template.md index d575081f322..49b1c1d02c0 100644 --- a/docs/the-new-architecture/use-app-template.md +++ b/docs/the-new-architecture/use-app-template.md @@ -1,7 +1,130 @@ --- id: use-app-template -title: Using the New App Template +title: Creating a New Architecture App --- -This section describes the new app template generated by the React Native CLI and how to use it properly. -It should explain how to create a new app, which command should be issued to install the dependencies and to run it on the different platforms. +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import constants from '@site/core/TabsConstants'; + +import RemoveGlobalCLI from '.././\_remove-global-cli.md'; + +This page will help you create a new React Native app that uses the New Architecture. + +## Development environment + +Before continuing, make sure you've followed all the steps in [Setting up the development environment](getting-started.md), under the **React Native CLI Quickstart** tab. + +If following the setup guide, stop when you reach the section **Running your React Native Application**, and resume following this guide. + +:::caution +If you're using Expo, you can't enable the New Architecture at the moment, and will have to wait for a future release of the Expo SDK. +::: + +## Creating a new application + + + +If you already have your development environment set up, create a new React Native project from the template: + +```shell +npx react-native init AwesomeProject +``` + +:::caution +The New Architecture is available in React Native version 0.68 or later. +::: + +## Configuration + +Follow the steps below to enable the New Architecture and build the app. + +### Enable Hermes + +Hermes is an open-source JavaScript engine optimized for React Native. [Hermes will be the default engine in the future](https://github.com/reactwg/react-native-new-architecture/discussions/4), and we highly recommend you use it. + +Please [follow the instructions on the React Native website](hermes.md) in order to enable Hermes in your application. + +### Enable the New Architecture + +#### Target OS + + + + +Navigate to the `ios` directory and run the following: + +```shell +# from `ios` directory +bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install +``` + +Then build and run the app as usual: + +```shell +yarn ios +``` + +:::note +You will need to run `pod install` each time a dependency with native code changes. Make this command easier to run by adding it to `scripts` to your project's `package.json` file: + +``` +"scripts": { + "pod-install": "RCT_NEW_ARCH_ENABLED=1 bundle exec pod install" +} +``` + +and run it with `yarn pod-install`. Note that `bundle install` does not need to run a second time, as long as the Gemfile has not changed. +::: + +#### Troubleshooting + +##### `react-native run-ios` fails + +If you see a build failure from `react-native run-ios`, there may be cached files from a previous build with the old architecture. Clean the build cache and try again: + +1. Open the project `ios/project.xcworkspace` in Xcode +2. In XCode, choose Product > Clean Build Folder +3. In the project directory, remove the `ios/Podfile.lock` file and `ios/Pods` directory: `rm -rf ios/Podfile.lock ios/Pods` +4. Re-run `yarn pod-install` and `yarn ios` + + + + +Set the `newArchEnabled` property to `true` by **either**: + +- Changing the corresponding line in `android/gradle.properties` +- Setting the environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` + +Then build and run the app as usual: + +```shell +yarn android +``` + +:::note +You may notice longer build times with the New Architecture, due to additional step of C++ compilation with the Android NDK. To improve your build time, see [Speeding Up Your Build Phase](docs/build-speed.md). +::: + + + + +### Confirming the New Architecture is in use + +After you build and run the app, when Metro serves the JavaScript bundle, you should see `"fabric": true` in the Metro logs: + +Metro shows fabric: true + +### Want to know more? + +If you'd like to view the code changes relevant for the New Architecture, take a look at the [upgrade helper from version 0.67.4 to 0.68.0](https://react-native-community.github.io/upgrade-helper/?from=0.67.4&to=0.68.0). Files that were added for the New Architecture are marked with a yellow banner. + +For further explanations of what each file is doing, check out these guides to walk through the changes step-by-step: + +#### Android + +- [Enabling TurboModules on Android](new-architecture-app-modules-android.md) +- [Enabling Fabric on Android](new-architecture-app-renderer-android.md) + +#### iOS + +- [Enabling TurboModules on iOS](new-architecture-app-modules-ios.md) +- [Enabling Fabric on iOS](new-architecture-app-renderer-ios.md) diff --git a/website/static/docs/assets/metro-new-arch.png b/website/static/docs/assets/metro-new-arch.png new file mode 100644 index 0000000000000000000000000000000000000000..b37d74b36b604db47b40f035c9be59e63bfd7cfa GIT binary patch literal 117503 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 literal 0 HcmV?d00001 From 5684e88e17b0e81bcdaca5e54a849f0b16529d19 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 20 Apr 2022 07:11:08 -0700 Subject: [PATCH 04/15] New Architecture landing page (#3072) * First draft of landing page * Add migration and backwards compatibility links * lint fix * Restructure slightly, leaning more on context from Why a New Architecture * Don't need md in links * Suggested rewording * Rephrase pillar summaries --- docs/the-new-architecture/landing-page.md | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/docs/the-new-architecture/landing-page.md b/docs/the-new-architecture/landing-page.md index dc83d361c85..3ab75dc7ad5 100644 --- a/docs/the-new-architecture/landing-page.md +++ b/docs/the-new-architecture/landing-page.md @@ -3,11 +3,26 @@ id: landing-page title: Introduction --- -This section is the entry point for the new guide’s documentation. -It contains some basic information about the New Architecture: its pillars, the version from which it has been made available and other very high-level information. Then, it should present the guide’s structure itself. +Starting in version 0.68, React Native provides the New Architecture, which offers developers new capabilities for building highly performant and responsive apps. Visit [Why a New Architecture](why) to learn more about what drove the decision to re-architect, and the benefits it provides. -This section should also contains a sort of Driving guide based on the user use-cases: +In order to achieve these benefits, we had to rethink how Native Modules and Native Components work. This led us to develop the [Pillars of the New Architecture](pillars): -- New user: link to how to use the new [app template](use-app-template) -- New Library developer: link to the [pillars](pillars) -- Old library developer/app developer: link to [the migration guide](../new-architecture-intro) +- [TurboModules](pillars-turbomodules), a framework to support efficient and flexible integration with native code +- [Fabric renderer and components](pillars-fabric-components), which offer improved capabilities, cross-platform consistency, and performance in rendering +- [CodeGen](pillars-codegen), which generates boilerplate C++ required by the New Architecture, via static typing in JavaScript + +## Get started with the New Architecture + +### For app developers + +To **create a new app** using the New Architecture, head over to [Creating a New Architecture App](use-app-template), which will get you up and running in a few quick steps with the new app template. + +To **migrate an existing app** to the New Architecture, follow [Adopting the New Architecture](../new-architecture-intro). + +### For library maintainers + +First, read up on the core concepts outlined in the [Pillars](pillars) section. + +Then, for a **how-to guide** on supporting the New Architecture, check out the [Migration](../new-architecture-library-info) guide. + +For information on **supporting both the Old and New Architectures**, see the [Backwards Compatibility](backward-compatibility) guide. From a869a53050674f3dfdd4ed719817c584debb9b3e Mon Sep 17 00:00:00 2001 From: Riccardo Date: Wed, 20 Apr 2022 16:11:39 +0200 Subject: [PATCH 05/15] [Guide - The New Architecture] Why A New Architecture (#3043) --- docs/the-new-architecture/why.md | 37 ++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/the-new-architecture/why.md b/docs/the-new-architecture/why.md index 81450add22c..fe981627ded 100644 --- a/docs/the-new-architecture/why.md +++ b/docs/the-new-architecture/why.md @@ -3,5 +3,38 @@ id: why title: Why A New Architecture --- -This section briefly explains why we decided to move away from the old architecture, and it describes succinctly the main benefits of adopting the new architecture. -It points to the [Architecture](https://reactnative.dev/architecture/overview) tab of the website for a deep dive into the ‘why’s. +The goal of the New Architecture is to solve some of the issues that afflicted the Old Architecture in terms of performance and flexibility. This section provides the basic context to understand the Old Architecture limitations and how it has been possible to overcome them with the New Architecture. + +This is not a technical deep dive: for further technical information, refer to the [Architecture](/architecture/overview) tab of the website. + +## Old Architecture's Issues + +The Old Architecture used to work by serializing all the data that has to be passed from the JS layer to the native layer using a component called _The Bridge_. _The Bridge_ can be imagined as a bus where the producer layer sent some data for the consumer layer. The consumer could read the data, deserialize it and execute the required operations. + +_The Bridge_ had some intrinsic limitations: + +- **It was asynchronous:** one layer submitted the data to the bridge and asynchronously "waited" for the other layer to process them, even when this was not really necessary. +- **It was single threaded:** JS used to work on a single thread, therefore the computation that happened in that world had to be performed on that single thread. +- **It imposed extra overheads:** everytime one layer had to use the other one, it had to serialize some data. The other layer had to deserialize them. The chosen format was JSON, for its simplicity and human-readability, but despite being lightweight, it was a cost to pay. + +## New Architecture's Improvements + +The New Architecture dropped the concept of _The Bridge_ in favor of another communication mechanism: the _JavaScript Interface (JSI)_. The _JSI_ is an interface that allows a JavaScript object to hold a reference to a C++ and viceversa. + +Once an object has a reference to the other one, it can directly invoke methods on it. So, for example, a C++ object can now asks a JavaScript object to execute a method in the JavaScript world and viceversa. + +This idea allowed to unlock several benefits: + +- **Synchronous execution:** it is now possibile to execute synchronously those functions that should not have been asynchronous in the first place. +- **Concurrency:** it is possible from JavaScript to invoke functions that are executed on different thread. +- **Lower overhead:** the New Architecture don't have to serialize/deserialize the data anymore, therefore there are no serialization taxes to pay. +- **Code sharing:** by introducing C++, it is now possible to abstract all the platform agnostic code and to share it with ease between the plaforms. +- **Type safety:** to make sure that JS can properly invoke methods on C++ objects and viceversa, a layer of code automatically generated has been added. The code is generated starting from some JS specification that must be typed through Flow or TypeScript. + +These advantages are the foundations of the [TurboModule](pillars-turbomodules) system and a jumping stone to further enhancements. For example, it has been possible to develop a new renderer which is faster and more performant: [Fabric](/architecture/fabric-renderer) and its [Fabric Components](pillars-fabric-components). + +## Further Reading + +For a technical overview of the New Architecture, have a look at the [Architecture tab](/architecture/overview). + +For more information on the Fabric Renderer, have a look at the [Fabric section](/architecture/fabric-renderer). From 559784538e36cf7e14110094615cb76504f28971 Mon Sep 17 00:00:00 2001 From: Riccardo Date: Thu, 21 Apr 2022 12:01:55 +0100 Subject: [PATCH 06/15] [Guide - The New Architecture] Pillars (#3046) --- docs/the-new-architecture/pillars.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/the-new-architecture/pillars.md b/docs/the-new-architecture/pillars.md index d86eed0c527..e390b16c47e 100644 --- a/docs/the-new-architecture/pillars.md +++ b/docs/the-new-architecture/pillars.md @@ -3,8 +3,27 @@ id: pillars title: What Compose the New Architecture --- -This section recalls the main pillars from the new-architecture-intro section. +The New Architecture is composed mainly by two pillars: -It contains a pointer to the `Migration` section and it specifies that these pillars work only when the new architecture is enabled. It also points to the prerequisites. +- [TurboModules](pillars-turbomodules) +- [Fabric Components](pillars-fabric-components). -It describes how the following sections are organized. +TurboModules are the preferred way to create libraries that leverage some platform specific API. Fabric Components are the preferred way to create reusable UI components, providing a native experience to the users. + +The main goal of this section is to drive the reader through a step-by-step guide to create their first TurboModule or Fabric Component. + +The next sections contain an high-level overview of the pillars, together with the steps to create them. To create one of these pillars, the steps are: + +1. Define a JavaScript specification using Flow or TypeScript. +1. Configure the dependencies management system to generate code from the provided spec. +1. Implement the Native code. +1. Integrate the code in the app. + +Finally, we dive a little deeper into the [CodeGen](pillars-codegen) process that is required to create all the C++ types and files used by our components, including some useful steps to work comfortably while developing the component. + +:::caution +To integrate a TurboModule or a Fabric Component in an app, the app has to run with the New Architecture enabled. + +To create a new app adopting the New Architecture, refer to the [Using the App Template](use-app-template) section. +To migrate an existing app to the New Architecture, refer to the [Migration](/docs/new-architecture-intro) guide. +::: From 61cd23036d1e7bcaefab69e2d34dc792c1faeb99 Mon Sep 17 00:00:00 2001 From: Riccardo Date: Tue, 5 Jul 2022 12:02:35 +0100 Subject: [PATCH 07/15] [Guide - The New Architecture] TurboModules as Native Modules (#3039) --- .../_markdown_beta_ts_support.mdx | 3 + .../backward-compatibility-turbomodules.md | 365 +++++++++++++++++- 2 files changed, 363 insertions(+), 5 deletions(-) create mode 100644 docs/the-new-architecture/_markdown_beta_ts_support.mdx diff --git a/docs/the-new-architecture/_markdown_beta_ts_support.mdx b/docs/the-new-architecture/_markdown_beta_ts_support.mdx new file mode 100644 index 00000000000..8ebff56f256 --- /dev/null +++ b/docs/the-new-architecture/_markdown_beta_ts_support.mdx @@ -0,0 +1,3 @@ +:::caution +The TypeScript support for the New Architecture is still in beta. +::: diff --git a/docs/the-new-architecture/backward-compatibility-turbomodules.md b/docs/the-new-architecture/backward-compatibility-turbomodules.md index 3fd61393243..3f4caa7b15c 100644 --- a/docs/the-new-architecture/backward-compatibility-turbomodules.md +++ b/docs/the-new-architecture/backward-compatibility-turbomodules.md @@ -3,10 +3,365 @@ id: backward-compatibility-turbomodules title: TurboModules as Native Modules --- -This section describes the required steps to ensure that a TurboModule can be used as a Native Module. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import constants from '@site/core/TabsConstants'; +import BetaTS from './\_markdown_beta_ts_support.mdx'; -The section explains: +:::info +The creation of a backward compatible TurboModule requires the knowledge of how to create a TurboModule. To recall these concepts, have a look at this [guide](pillars-turbomodules). -- How to avoid installing dependencies when they are not needed -- The usage of compilation pragmas to avoid compiling code that requires types from the codegen -- API uniformity in JS, so that they don’t have to import different files +TurboModules only works when the New Architecture is properly setup. If you already have a library that you want to migrate to the New Architecture, have a look at the [migration guide](../new-architecture-intro) as well. +::: + +Creating a backward compatible TurboModule lets your users continue leverage your library, independently from the architecture they use. The creation of such a module requires a few steps: + +1. Configure the library so that dependencies are prepared set up properly for both the Old and the New Architecture. +1. Update the codebase so that the New Architecture types are not compiled when not available. +1. Uniform the JavaScript API so that your user code won't need changes. + + + +While the last step is the same for all the platforms, the first two steps are different for iOS and Android. + +## Configure the TurboModule Dependencies + +### iOS + +The Apple platform installs TurboModules using [Cocoapods](https://cocoapods.org) as dependency manager. + +Every TurboModule defines a `podspec` that looks like this: + +```ruby +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +folly_version = '2021.06.28.00-v2' +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + # Default fields for a valid podspec + s.name = "" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.platforms = { :ios => "11.0" } + s.author = package["author"] + s.source = { :git => package["repository"], :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm,swift}" + # React Native Core dependency + s.dependency "React-Core" + + # The following lines are required by the New Architecture. + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + s.dependency "React-Codegen" + s.dependency "RCT-Folly", folly_version + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + +end +``` + +The **goal** is to avoid installing the dependencies when the app is prepared for the Old Architecture. + +When we want to install the dependencies we use the following commands, depending on the architecture: + +```sh +# For the Old Architecture, we use: +pod install + +# For the New Architecture, we use: +RCT_NEW_ARCH_ENABLED=1 pod install +``` + +Therefore, we can leverage this environment variable in the `podspec` to exclude the settings and the dependencies that are related to the New Architecture: + +```diff ++ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + # The following lines are required by the New Architecture. + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + # ... other dependencies ... + s.dependency "ReactCommon/turbomodule/core" ++ end +end +``` + +This `if` guard prevents the dependencies from being installed when the environment variable is not set. + +### Android + +To create a module that can work with both architectures, you need to configure Gradle to choose which files need to be compiled depending on the chosen architecture. This can be achieved by using **different source sets** in the Gradle configuration. + +:::note +Please note that this is currently the suggested approach. While it might lead to some code duplication, it will ensure the maximum compatibility with both architectures. You will see how to reduce the duplication in the next section. +::: + +To configure the TurboModule so that it picks the proper sourceset, you have to update the `build.gradle` file in the following way: + +```diff title="build.gradle" ++// Add this function in case you don't have it already ++ def isNewArchitectureEnabled() { ++ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" ++} + + +// ... other parts of the build file + +defaultConfig { + minSdkVersion safeExtGet('minSdkVersion', 21) + targetSdkVersion safeExtGet('targetSdkVersion', 31) ++ buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) ++ } ++ ++ sourceSets { ++ main { ++ if (isNewArchitectureEnabled()) { ++ java.srcDirs += ['src/newarch'] ++ } else { ++ java.srcDirs += ['src/oldarch'] ++ } ++ } + } +} +``` + +This changes do three main things: + +1. The first lines define a function that returns whether the New Architecture is enabled or not. +2. The `buildConfigField` line defines a build configuration boolean field called `IS_NEW_ARCHITECTURE_ENABLED`, and initialize it using the function declared in the first step. This allows you to check at runtime if a user has specified the `newArchEnabled` property or not. +3. The last lines leverage the function declared in step one to decide which source sets we need to build, depending on the choosen architecture. + +## Update the codebase + +### iOS + +The second step is to instruct Xcode to avoid compiling all the lines using the New Architecture types and files when we are building an app with the Old Architecture. + +The file to change is the module implementation file, which is usually a `.mm` file. That file is structured as follow: + +- Some `#import` statements, among which there is a `.h` file. +- The module implementation, using the various `RCT_EXPORT_xxx` and `RCT_REMAP_xxx` macros. +- The `getTurboModule:` function, which uses the `` type, generated by The New Architecture. + +The **goal** is to make sure that the `TurboModule` still builds with the Old Architecture. To achieve that, we can wrap the `#import ".h"` and the `getTurboModule:` function into an `#ifdef RCT_NEW_ARCH_ENABLED` compilation directive, as shown in the following example: + +```diff +#import ".h" ++ #ifdef RCT_NEW_ARCH_ENABLED +#import ".h" ++ #endif + +// ... rest of your module + ++ #ifdef RCT_NEW_ARCH_ENABLED + - (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params + { + return std::make_shared>(params); + } ++ #endif + +@end +``` + +This snippet uses the same `RCT_NEW_ARCH_ENABLED` flag used in the previous [section](#dependencies-ios). When this flag is not set, Xcode skips the lines within the `#ifdef` during compilation and it does not include them into the compiled binary. + +### Android + +As we can't use conditional compilation blocks on Android, we will define two different source sets. This will allow to create a backward compatible TurboModule with the proper source that is loaded and compiled depending on the used architecture. + +Therefore, you have to: + +1. Create a Native Module in the `src/oldarch` path. See [this guide](../native-modules-intro) to learn how to create a Native Module. +2. Create a TurboModule in the `src/newarch` path. See [this guide](./pillars-turbomodules) to learn how to create a TurboModule + +and then instruct Gradle to decide which implementation to pick. + +Some files can be shared between a Native Module and a TurboModule: these should be created or moved into a folder that is loaded by both the architectures. These files are: + +- the `Package.java` file used to load the module. +- a `Impl.java` file where we can put the code that both the Native Module and the TurboModule has to execute. + +The final folder structure looks like this: + +```sh +my-module +├── android +│   ├── build.gradle +│   └── src +│   ├── main +│   │ ├── AndroidManifest.xml +│   │ └── java +│   │ └── com +│   │ └── MyModule +│   │ ├── MyModuleImpl.java +│   │ └── MyModulePackage.java +│ ├── newarch +│ │ └── java +│   │ └── com +│ │ └── MyModule.java +│ └── oldarch +│ └── java +│   └── com +│ └── MyModule.java +├── ios +├── js +└── package.json +``` + +The code that should go in the `MyModuleImpl.java` and that can be shared by the Native Module and the TurboModule is, for example: + +```java title="example of MyModuleImple.java" +package com.MyModule; + +import androidx.annotation.NonNull; +import com.facebook.react.bridge.Promise; +import java.util.Map; +import java.util.HashMap; + +public class MyModuleImpl { + + public static final String NAME = "MyModule"; + + public void foo(double a, double b, Promise promise) { + // implement the logic for foo and then invoke promise.resolve or + // promise.reject. + } +} +``` + +Then, the Native Module and the TurboModule can be updated with the following steps: + +1. Create a private instance of the `MyModuleImpl` class. +2. Initialize the instance in the module constructor. +3. Use the private instance in the modules methods. + +For example, for a Native Module: + +```java title="Native Module using the Impl module" +public class MyModule extends ReactContextBaseJavaModule { + + // declare an instance of the implementation + private MyModuleImpl implementation; + + CalculatorModule(ReactApplicationContext context) { + super(context); + // initialize the implementation of the module + implementation = MyModuleImpl(); + } + + @Override + public String getName() { + // NAME is a static variable, so we can access it using the class name. + return MyModuleImpl.NAME; + } + + @ReactMethod + public void foo(int a, int b, Promise promise) { + // Use the implementation instance to execute the function. + implementation.foo(a, b, promise); + } +} +``` + +And, for a TurboModule: + +```java title="TurboModule using the Impl module" +public class MyModule extends MyModuleSpec { + // declare an instance of the implementation + private MyModuleImpl implementation; + + CalculatorModule(ReactApplicationContext context) { + super(context); + // initialize the implementation of the module + implementation = MyModuleImpl(); + } + + @Override + @NonNull + public String getName() { + // NAME is a static variable, so we can access it using the class name. + return MyModuleImpl.NAME; + } + + @Override + public void foo(double a, double b, Promise promise) { + // Use the implementation instance to execute the function. + implementation.foo(a, b, promise); + } +} +``` + +For a step-by-step example on how to achieve this, have a look at [this repo](https://github.com/react-native-community/RNNewArchitectureLibraries/tree/feat/back-turbomodule). + +## Unify the JavaScript specs + + + +The last step makes sure that the JavaScript behaves transparently to chosen architecture. + +For a TurboModule, the source of truth is the `Native.js` (or `.ts`) spec file. The app accesses the spec file like this: + +```ts +import MyModule from 'your-module/src/index'; +``` + +The **goal** is to conditionally `export` from the `index` file the proper object, given the architecture chosen by the user. We can achieve this with a code that looks like this: + + + + +```ts +// @flow + +import { NativeModules } from 'react-native' + +const isTurboModuleEnabled = global.__turboModuleProxy != null; + +const myModule = isTurboModuleEnabled ? + require('./Native').default : + NativeModules.; + +export default myModule; +``` + + + + +```ts +const isTurboModuleEnabled = global.__turboModuleProxy != null; + +const myModule = isTurboModuleEnabled + ? require('./Native').default + : require('./').default; + +export default myModule; +``` + + + + +:::note +If you are using TypeScript and you want to follow the example, make sure to `export` the `NativeModule` in a separate `ts` file called `.ts`. +::: + +Whether you are using Flow or TypeScript for your specs, we understand which architecture is running by checking whether the `global.__turboModuleProxy` object has been set or not. + +:::caution +The `global.__turboModuleProxy` API may change in the future for a function that encapsulate this check. +::: + +- If that object is `null`, the app has not enabled the TurboModule feature. It's running on the Old Architecture, and the fallback is to use the default [`NativeModule` implementation](../native-modules-intro). +- If that object is set, the app is running with the TurboModules enabled and it should use the `Native` spec to access the TurboModule. From 8a9b70fa795553faeab55d1cf3d088c0eae6d3de Mon Sep 17 00:00:00 2001 From: Riccardo Date: Tue, 5 Jul 2022 12:35:37 +0100 Subject: [PATCH 08/15] [Guide - The New Architecture] Fabric Components as Native Components (#3040) --- ...ackward-compatibility-fabric-components.md | 430 +++++++++++++++++- docs/the-new-architecture/landing-page.md | 2 +- docs/the-new-architecture/use-app-template.md | 2 +- 3 files changed, 427 insertions(+), 7 deletions(-) diff --git a/docs/the-new-architecture/backward-compatibility-fabric-components.md b/docs/the-new-architecture/backward-compatibility-fabric-components.md index a496d5391f0..233da8f0764 100644 --- a/docs/the-new-architecture/backward-compatibility-fabric-components.md +++ b/docs/the-new-architecture/backward-compatibility-fabric-components.md @@ -3,10 +3,430 @@ id: backward-compatibility-fabric-components title: Fabric Components as Native Components --- -This section describes the required steps to ensure that a Fabric Component can be used as a Native Component. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import constants from '@site/core/TabsConstants'; +import BetaTS from './\_markdown_beta_ts_support.mdx'; -The section should explain: +:::info +The creation of a backward compatible Fabric Component requires the knowledge of how to create a Fabric Component. To recall these concepts, have a look at this [guide](pillars-fabric-components). -- How to avoid installing dependencies when they are not needed -- The usage of compilation pragmas to avoid compiling code that requires types from the codegen -- API uniformity in JS, so that they don’t have to import different files +Fabric Components only work when the New Architecture is properly setup. If you already have a library that you want to migrate to the New Architecture, have a look at the [migration guide](../new-architecture-intro) as well. +::: + +Creating a backward compatible Fabric Component lets your users continue leverage your library, independently from the architecture they use. The creation of such a component requires a few steps: + +1. Configure the library so that dependencies are prepared set up properly for both the Old and the New Architecture. +1. Update the codebase so that the New Architecture types are not compiled when not available. +1. Uniform the JavaScript API so that your user code won't need changes. + + + +While the last step is the same for all the platforms, the first two steps are different for iOS and Android. + +## Configure the Fabric Component Dependencies + +### iOS + +The Apple platform installs Fabric Components using [Cocoapods](https://cocoapods.org) as dependency manager. + +Every Fabric Component defines a `podspec` that looks like this: + +```ruby +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +folly_version = '2021.06.28.00-v2' +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + # Default fields for a valid podspec + s.name = "" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.platforms = { :ios => "11.0" } + s.author = package["author"] + s.source = { :git => package["repository"], :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm,swift}" + # React Native Core dependency + s.dependency "React-Core" + + # The following lines are required by the New Architecture. + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + s.dependency "React-RCTFabric" + s.dependency "React-Codegen" + s.dependency "RCT-Folly", folly_version + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" +end +``` + +The **goal** is to avoid installing the dependencies when the app is prepared for the Old Architecture. + +When we want to install the dependencies, we use the following commands depending on the architecture: + +```sh +# For the Old Architecture, we use: +pod install + +# For the New Architecture, we use: +RCT_NEW_ARCH_ENABLED=1 pod install +``` + +Therefore, we can leverage this environment variable in the `podspec` to exclude the settings and the dependencies that are related to the New Architecture: + +```diff ++ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + # The following lines are required by the New Architecture. + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + # ... other dependencies ... + s.dependency "ReactCommon/turbomodule/core" ++ end +end +``` + +This `if` guard prevents the dependencies from being installed when the environment variable is not set. + +### Android + +To create a module that can work with both architectures, you need to configure Gradle to choose which files need to be compiled depending on the chosen architecture. This can be achieved by using **different source sets** in the Gradle configuration. + +:::note +Please note that this is currently the suggested approach. While it might lead to some code duplication, it will ensure the maximum compatibility with both architectures. You will see how to reduce the duplication in the next section. +::: + +To configure the Fabric Component so that it picks the proper sourceset, you have to update the `build.gradle` file in the following way: + +```diff title="build.gradle" ++// Add this function in case you don't have it already ++ def isNewArchitectureEnabled() { ++ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" ++} +// ... other parts of the build file +defaultConfig { + minSdkVersion safeExtGet('minSdkVersion', 21) + targetSdkVersion safeExtGet('targetSdkVersion', 31) ++ buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) ++ } ++ ++ sourceSets { ++ main { ++ if (isNewArchitectureEnabled()) { ++ java.srcDirs += ['src/newarch'] ++ } else { ++ java.srcDirs += ['src/oldarch'] ++ } ++ } + } +} +``` + +This changes do three main things: + +1. The first lines define a function that returns whether the New Architecture is enabled or not. +2. The `buildConfigField` line defines a build configuration boolean field called `IS_NEW_ARCHITECTURE_ENABLED`, and initialize it using the function declared in the first step. This allows you to check at runtime if a user has specified the `newArchEnabled` property or not. +3. The last lines leverage the function declared in step one to decide which source sets we need to build, depending on the choosen architecture. + +## Update the codebase + +### iOS + +The second step is to instruct Xcode to avoid compiling all the lines using the New Architecture types and files when we are building an app with the Old Architecture. + +A Fabric Component requires an header file and an implementation file to add the actual `View` to the module. + +For example, the `RNMyComponentView.h` header file could look like this: + +```objective-c +#import +#import + +#ifndef NativeComponentExampleComponentView_h +#define NativeComponentExampleComponentView_h + +NS_ASSUME_NONNULL_BEGIN + +@interface RNMyComponentView : RCTViewComponentView +@end + +NS_ASSUME_NONNULL_END + +#endif /* NativeComponentExampleComponentView_h */ +``` + +The implementation `RNMyComponentView.mm` file, instead, could look like this: + +```objective-c +#import "RNMyComponentView.h" + +// + +#import "RCTFabricComponentsPlugins.h" + +using namespace facebook::react; + +@interface RNMyComponentView () + +@end + +@implementation RNMyComponentView { + UIView * _view; +} + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + // ... return the descriptor ... +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + // ... initialize the object ... +} + +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + // ... set up the props ... + + [super updateProps:props oldProps:oldProps]; +} + +Class MyComponentViewCls(void) +{ + return RNMyComponentView.class; +} + +@end +``` + +To make sure that Xcode skips these files, we can wrap **both** of them in some `#ifdef RCT_NEW_ARCH_ENABLED` compilation pragma. For example, the header file could change as follows: + +```diff ++ #ifdef RCT_NEW_ARCH_ENABLED +#import +#import + +// ... rest of the header file ... + +#endif /* NativeComponentExampleComponentView_h */ ++ #endif +``` + +The same two lines should be added in the implementation file, as first and last lines. + +The above snippet uses the same `RCT_NEW_ARCH_ENABLED` flag used in the previous [section](#dependencies-ios). When this flag is not set, Xcode skips the lines within the `#ifdef` during compilation and it does not include them into the compiled binary. The compiled binary will have a the `RNMyComponentView.o` object but it will be an empty object. + +### Android + +As we can't use conditional compilation blocks on Android, we will define two different source sets. This will allow to create a backward compatible TurboModule with the proper source that is loaded and compiled depending on the used architecture. + +Therefore, you have to: + +1. Create a Native Component in the `src/oldarch` path. See [this guide](../native-components-android) to learn how to create a Native Component. +2. Create a Fabric Component in the `src/newarch` path. See [this guide](pillars-fabric-components) to learn how to create a Fabric Component. + +and then instruct Gradle to decide which implementation to pick. + +Some files can be shared between a Native and a Fabric Component: these should be created or moved into a folder that is loaded by both the architectures. These files are: + +- the `.java` that instantiate and configure the Android View for both the components. +- the `ManagerImpl.java` file where which contains the logic of the ViewManager that can be shared between the Native and the Fabric Component. +- the `Package.java` file used to load the component. + +The final folder structure looks like this: + +```sh +my-component +├── android +│   ├── build.gradle +│   └── src +│   ├── main +│   │ ├── AndroidManifest.xml +│   │ └── java +│   │ └── com +│   │ └── MyComponent +│   │ ├── MyComponentView.java +│   │ ├── MyComponentViewManagerImpl.java +│   │ └── MyComponentViewPackage.java +│ ├── newarch +│ │ └── java +│   │ └── com +│ │ └── MyComponentViewManager.java +│ └── oldarch +│ └── java +│   └── com +│ └── MyComponentViewManager.java +├── ios +├── js +└── package.json +``` + +The code that should go in the `MyComponentViewManagerImpl.java` and that can be shared between the Native Component and the Fabric Component is, for example: + +```java title="example of MyComponentViewManager.java" +package com.MyComponent; +import androidx.annotation.Nullable; +import com.facebook.react.uimanager.ThemedReactContext; + +public class MyComponentViewManagerImpl { + + public static final String NAME = "MyComponent"; + + public static MyComponentView createViewInstance(ThemedReactContext context) { + return new MyComponentView(context); + } + + public static void setFoo(MyComponentView view, String param) { + // implement the logic of the foo function using the view and the param passed. + } +} +``` + +Then, the Native Component and the Fabric Component can be updated using the function declared in the shared manager. + +For example, for a Native Component: + +```java title="Native Component using the ViewManagerImpl" +public class MyComponentViewManager extends SimpleViewManager { + + ReactApplicationContext mCallerContext; + + public MyComponentViewManager(ReactApplicationContext reactContext) { + mCallerContext = reactContext; + } + + @Override + public String getName() { + // static NAME property from the shared implementation + return MyComponentViewManagerImpl.NAME; + } + + @Override + public MyComponentView createViewInstance(ThemedReactContext context) { + // static createViewInstance function from the shared implementation + return MyComponentViewManagerImpl.createViewInstance(context); + } + + @ReactProp(name = "foo") + public void setFoo(MyComponentView view, String param) { + // static custom function from the shared implementation + MyComponentViewManagerImpl.setFoo(view, param); + } + +} +``` + +And, for a Fabric Component: + +```java title="Fabric Component using the ViewManagerImpl" +// Use the static NAME property from the shared implementation +@ReactModule(name = MyComponentViewManagerImpl.NAME) +public class MyComponentViewManager extends SimpleViewManager + implements MyComponentViewManagerInterface { + + private final ViewManagerDelegate mDelegate; + + public MyComponentViewManager(ReactApplicationContext context) { + mDelegate = new MyComponentViewManagerDelegate<>(this); + } + + @Nullable + @Override + protected ViewManagerDelegate getDelegate() { + return mDelegate; + } + + @NonNull + @Override + public String getName() { + // static NAME property from the shared implementation + return MyComponentViewManagerImpl.NAME; + } + + @NonNull + @Override + protected MyComponentView createViewInstance(@NonNull ThemedReactContext context) { + // static createViewInstance function from the shared implementation + return MyComponentViewManagerImpl.createViewInstance(context); + } + + @Override + @ReactProp(name = "foo") + public void setFoo(MyComponentView view, @Nullable String param) { + // static custom function from the shared implementation + MyComponentViewManagerImpl.setFoo(view, param]); + } +} +``` + +For a step-by-step example on how to achieve this, have a look at [this repo](https://github.com/react-native-community/RNNewArchitectureLibraries/tree/feat/back-fabric-comp). + +## Unify the JavaScript specs + + + +The last step makes sure that the JavaScript behaves transparently to chosen architecture. + +For a Fabric Component, the source of truth is the `NativeComponent.js` (or `.ts`) spec file. The app accesses the spec file like this: + +```ts +import MyComponent from 'your-component/src/index'; +``` + +The **goal** is to conditionally `export` from the `index` file the proper object, given the architecture chosen by the user. We can achieve this with a code that looks like this: + + + + +```ts +// @flow +import { requireNativeComponent } from 'react-native'; + +const isFabricEnabled = global.nativeFabricUIManager != null; + +const myComponent = isFabricEnabled + ? require('./MyComponentNativeComponent').default + : requireNativeComponent('MyComponent'); + +export default myComponent; +``` + + + + +```ts +import requireNativeComponent from 'react-native/Libraries/ReactNative/requireNativeComponent'; + +const isFabricEnabled = global.nativeFabricUIManager != null; + +const myComponent = isFabricEnabled + ? require('./MyComponentNativeComponent').default + : requireNativeComponent('MyComponent'); + +export default myComponent; +``` + + + + +Whether you are using Flow or TypeScript for your specs, we understand which architecture is running by checking if the `global.nativeFabricUIManager` object has been set or not. + +:::caution +Please note that the New Architecture is still experimental. The `global.nativeFabricUIManager` API might change in the future for a function that encapsulate this check. +::: + +- If that object is `null`, the app has not enabled the Fabric feature. It's running on the Old Architecture, and the fallback is to use the default Native Components implementation ([iOS](../native-components-ios) or [Android](../native-components-android)). +- If that object is set, the app is running with Fabric enabled and it should use the `NativeComponent` spec to access the Fabric Component. diff --git a/docs/the-new-architecture/landing-page.md b/docs/the-new-architecture/landing-page.md index 3ab75dc7ad5..1471c2cf416 100644 --- a/docs/the-new-architecture/landing-page.md +++ b/docs/the-new-architecture/landing-page.md @@ -23,6 +23,6 @@ To **migrate an existing app** to the New Architecture, follow [Adopting the New First, read up on the core concepts outlined in the [Pillars](pillars) section. -Then, for a **how-to guide** on supporting the New Architecture, check out the [Migration](../new-architecture-library-info) guide. +Then, for a **how-to guide** on supporting the New Architecture, check out the [Migration](../new-architecture-library-intro) guide. For information on **supporting both the Old and New Architectures**, see the [Backwards Compatibility](backward-compatibility) guide. diff --git a/docs/the-new-architecture/use-app-template.md b/docs/the-new-architecture/use-app-template.md index 49b1c1d02c0..56fe73d8267 100644 --- a/docs/the-new-architecture/use-app-template.md +++ b/docs/the-new-architecture/use-app-template.md @@ -101,7 +101,7 @@ yarn android ``` :::note -You may notice longer build times with the New Architecture, due to additional step of C++ compilation with the Android NDK. To improve your build time, see [Speeding Up Your Build Phase](docs/build-speed.md). +You may notice longer build times with the New Architecture, due to additional step of C++ compilation with the Android NDK. To improve your build time, see [Speeding Up Your Build Phase](build-speed.md). ::: From 8ad2465d979872e11383c24e05fc6be262bb09ff Mon Sep 17 00:00:00 2001 From: Riccardo Date: Wed, 20 Jul 2022 14:42:25 +0100 Subject: [PATCH 09/15] [FEAT][TNA] Fabric Component Guide (#3132) * [Feat] Add intro for Fabric Components * feat: add guide to create a Fabric Component --- .../pillars-fabric-components.md | 918 +++++++++++++++++- docs/the-new-architecture/pillars.md | 2 +- website/core/TabsConstants.js | 2 +- website/package.json | 2 +- 4 files changed, 912 insertions(+), 12 deletions(-) diff --git a/docs/the-new-architecture/pillars-fabric-components.md b/docs/the-new-architecture/pillars-fabric-components.md index 72892522d16..949205e4c03 100644 --- a/docs/the-new-architecture/pillars-fabric-components.md +++ b/docs/the-new-architecture/pillars-fabric-components.md @@ -3,17 +3,917 @@ id: pillars-fabric-components title: Fabric Components --- -This section contains a high-level introduction to Fabric components. It provides enough context to understand when a Fabric component is needed and how it roughly works. -It points to the [Renderer](https://reactnative.dev/architecture/fabric-renderer) section of the [Architecture](https://reactnative.dev/architecture/overview) tab for a deep dive into the technical details. +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import constants from '@site/core/TabsConstants'; -This section must have a warning that it works only with the new architecture enabled. It points to the [migration section](../new-architecture-intro). +A Fabric Component is a UI component rendered on the screen using the [Fabric Renderer](https://reactnative.dev/architecture/fabric-renderer). Using Fabric Components instead of Native Components allows us to reap all the [benefits](./why) of the **New Architecture**: + +- Strongly typed interfaces that are consistent across platforms. +- The ability to write your code in C++, either exclusively or integrated with another native platform language, reducing the need to duplicate implementations across platforms. +- The use of JSI, a JavaScript interface for native code, which allows for more efficient communication between native and JavaScript code than the bridge. + +A Fabric Component is created starting from a **JavaScript specification**. Then [**Codegen**](./pillars-codegen) creates some C++ scaffolding code to connect the component-specific logic (for example, accessing some native platform capability) to the rest of the React Native infrastructure. The C++ code is the same for all the platforms. Once the component is properly connected with the scaffolding code, it is ready to be imported and used by an app. + +The following section guides you through the creation of a Fabric Component, step-by-step. + +:::caution +Fabric Components only works with the **New Architecture** enabled. +To migrate to the **New Architecture**, follow the [Migration guide](../new-architecture-intro) +::: ## How to Create a Fabric Components -This section is a step-by-step guide to create a Fabric component from scratch. The list of subsections is roughly: +To create a Fabric Component, you have to follow these steps: + +1. Define a set of JavaScript specifications. +2. Configure the component so that **Codegen** can create the shared code and it can be added as a dependency for an app. +3. Write the required native code. + +Once these steps are done, the component is ready to be consumed by an app. The guide shows how to add it to an app, leveraging _autolinking_, and how to reference it from the JavaScript code. + +## 1. Folder Setup + +In order to keep the component decoupled from the app, it's a good idea to define the module separately from the app, and then add it as a dependency to your app later. This is also what you'll do for writing Fabric Component that can be released as open-source libraries later. + +For this guide, you are going to create a Fabric Component that centers some text on the screen. + +Create a new folder at the same level of the app and call it `RTNCenteredText`. + +In this folder, create three subfolders: `js`, `ios` and `android`. + +The final result should look like this: + +```sh +. +├── MyApp +└── RTNCenteredText + ├── android + ├── ios + └── js +``` + +## 2. JavaScript Specification + +The **New Architecture** requires interfaces specified in a typed dialect of JavaScript (either [Flow](https://flow.org/) or [TypeScript](https://www.typescriptlang.org/)). **Codegen** uses these specifications to generate code in strongly-typed languages, including C++, Objective-C++, and Java. + +There are two requirements the file containing this specification must meet: + +1. The file **must** be named `NativeComponent`, with a `.js` or `.jsx` extension when using Flow, or a `.ts`, or `.tsx` extension when using TypeScript. **Codegen** only looks for files matching this pattern. +2. The file must export a `HostComponent` object. + +Below are specifications of the `RTNCenteredText` component in both Flow and TypeScript. Create a `RTNCenteredText` file with the proper extension in the `js` folder. + + + + +```typescript +// @flow strict-local + +import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; +import type {HostComponent} from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; + +type NativeProps = $ReadOnly<{| + ...ViewProps, + text: ?string, + // add other props here +|}>; + +export default (codegenNativeComponent( + 'RTNCenteredText', +): HostComponent); +``` + + + + +```typescript +import type { ViewProps } from 'ViewPropTypes'; +import type { HostComponent } from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; + +export interface NativeProps extends ViewProps { + ...ViewProps, + text: string | null | undefined, + // add other props here +} + +export default codegenNativeComponent( + 'RTNCenteredText' +) as HostComponent; +``` + + + + +At the beginning of the spec files, there are the imports. The most important imports, required by every Fabric Component, are: + +- The `HostComponent`: type the exported component needs to conform to. +- The `codegenNativeComponent` function: responsible to actually register the component in the JavaScript runtime. + +The second section of the files contains the **props** of the component. [Props](https://reactnative.dev/docs/next/intro-react#props) (short for "properties") are component-specific information that let you customize React components. In this case, you want to control the `text` property of the component. + +Finally, the spec file exports the returned value of the `codegenNativeComponent` generic function, invoked passing the name of the component. + +:::caution +The JavaScript files imports types from libraries, without setting up a proper node module and installing its dependencies. The outcome of this is that the IDE may have troubles resolving the import statements and it can output errors and warnings. +These will disappear as soon as the Fabric Component is added as a dependency of a React Native app. +::: + +## 3. Component Configuration + +Next, you need to add some configuration for [**Codegen**](pillars-codegen.md) and auto-linking. + +Some of these configuration files are shared between iOS and Android, while the others are platform-specific. + +### Shared + +The shared configuration is a `package.json` file that will be used by yarn when installing your module. Create the `package.json` file in the root of the `RTNCenteredText` directory. + +```json title="package.json" +{ + "name": "rtn-centered-text", + "version": "0.0.1", + "description": "Showcase a Fabric Component with a centered text", + "react-native": "js/index", + "source": "js/index", + "files": [ + "js", + "android", + "ios", + "rtn-centered-text.podspec", + "!android/build", + "!ios/build", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": ["react-native", "ios", "android"], + "repository": "https://github.com//rtn-centered-text", + "author": " (https://github.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com//rtn-centered-text/issues" + }, + "homepage": "https://github.com//rtn-centered-text#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "codegenConfig": { + "libraries": [ + { + "name": "RTNCenteredTextSpecs", + "type": "components", + "jsSrcsDir": "js" + } + ] + } +} +``` + +The upper part of the file contains some descriptive information like the name of the component, its version and its source files. Make sure to update the various placeholders which are wrapped in `<>`: replace all the occurrences of the ``, ``, and `` tokens. + +Then there are the dependencies for this package. For this guide, you need `react` and `react-native`. + +Finally, the **Codegen** configuration is specified by the `codegenConfig` field. It contains an array of libraries, each of which is defined by three other fields: + +- `name`: The name of the library. By convention, you should add the `Spec` suffix. +- `type`: The type of module contained by this package. In this case, it is a Fabric Component, thus the value to use is `components`. +- `jsSrcsDir`: the relative path to access the `js` specification that is parsed by **Codegen**. + +### iOS: Create the `.podspec` file + +For iOS, you'll need to create a `rtn-centered-text.podspec` file which will define the module as a dependency for your app. It will stay in the root of `RTNCenteredText`, alongside the `ios` folder. + +The file will look like this: + +```ruby title="rtn-centered-text.podspec" +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +folly_version = '2021.06.28.00-v2' +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + s.name = "rtn-centered-text" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.platforms = { :ios => "11.0" } + s.author = package["author"] + s.source = { :git => package["repository"], :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm,swift}" + + s.dependency "React-Core" + + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + s.dependency "React-RCTFabric" + s.dependency "React-Codegen" + s.dependency "RCT-Folly", folly_version + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" +end +``` + +The `.podspec` file has to be a sibling of the `package.json` file and its name is the one we set in the `package.json`'s `name` property: `rtn-centered-text`. + +The first part of the file prepares some variables we will use throughout the rest of it. Then, there is a section that contains some information used to configure the pod, like its name, version, and description. Finally, we have a set of dependencies that are required by the New Architecture. + +### Android: `build.gradle`, `AndroidManifest.xml`, a `ReactPackage` class + +To prepare Android to run **Codegen** you have to create three files: + +1. The `build.gradle` with the **Codegen** configuration +1. The `AndroidManifest.xml` file +1. A java class that implements the `ReactPackage` interface. + +At the end of these steps, the `android` folder should look like this: + +```title="Android Folder Structure" +android +├── build.gradle +└── src + └── main + ├── AndroidManifest.xml + └── java + └── com + └── rtncenteredtext + └── RTNCenteredTextPackage.java +``` + +#### The `build.gradle` file + +First, create a `build.gradle` file in the `android` folder, with the following contents: + +```kotlin title="build.gradle" +buildscript { + ext.safeExtGet = {prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + repositories { + google() + gradlePluginPortal() + } + dependencies { + classpath("com.android.tools.build:gradle:7.1.1") + } +} + +apply plugin: 'com.android.library' +apply plugin: 'com.facebook.react' + +android { + compileSdkVersion safeExtGet('compileSdkVersion', 31) + + defaultConfig { + minSdkVersion safeExtGet('minSdkVersion', 21) + targetSdkVersion safeExtGet('targetSdkVersion', 31) + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true") + } +} + +repositories { + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$projectDir/../node_modules/react-native/android" + } + mavenCentral() + google() +} + +dependencies { + implementation 'com.facebook.react:react-native:+' +} + +react { + jsRootDir = file("../js/") + libraryName = "RTNCenteredText" + codegenJavaPackageName = "com.RTNCenteredText" +} +``` + +Of interest in the `build.gradle` file: + +- The `react` block configures the CodeGen process. For Android, we need to specify: + - the `jsRootDir`, which contains the relative path to the JavaScript specs + - the `libraryName` we will use to link the library in the app. + - the `codegenJavaPackageName` which corresponds to the name of the Java package we will use for the code generated by **CodeGen**. + +#### The `AndroidManifest.xml` + +Second, create an `android/src/main` folder. Inside that folder, create a `AndroidManifest.xml` file, with the following code: + +```xml title="AndroidManifest.xml" + + +``` + +This is a small manifest file that defines the package for your module. + +#### The `ReactPackage` class + +Finally, you need a class that implements the `ReactPackage` interface. To run the **Codegen** process, you don't have to completely implement the Package class: an empty implementation is enough for the app to pick up the module as a proper React Native dependency and to try and generate the scaffolding code. + +Create an `android/src/main/java/com/rtncenteredtext` folder and, inside that folder, create a `RTNCenteredTextPackage.java` file. + +```java title="RTNCenteredTextPackage" +package com.rtncenteredtext; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Collections; +import java.util.List; + +public class RTNCenteredTextPackage implements ReactPackage { + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + +} +``` + +The `ReactPackage` interface is used by React Native to understand what native classes the app has to use for the `ViewManager` and `Native Modules` exported by the library. + +## 4. Native Code + +The last step requires you to write some native code to connect the JavaScript side of the Component to what is offered by the platforms. This process requires two main steps: + +1. Run **Codegen** to see what would be generated. +2. Write the native code that will make it work. + +When developing a React Native app that uses a Fabric Component, it is responsibility of the app to actually generate the code using **Codegen**. However, when developing a Fabric Component as a library, it needs to reference the generated code and it is useful to see what the app will generate. + +As first step for both iOS and Android, this guide shows how to execute manually the scripts used by **Codegen** to generate the required code. Further information on **Codegen** can be found [here](./pillars-codegen.md) + +:::caution +The code generated by **Codegen** in this step should not be committed to the versioning system. React Native apps are able to generate the code when the app is built. This allows an app to ensure that all libraries have code generated for the correct version of React Native. +::: + +### iOS + +#### Generate the code - iOS + +To run Codegen for the iOS platform, open a terminal and run the following command: + +```sh +cd MyApp +yarn add ../RTNCenteredText +cd .. +node MyApp/node_modules/react-native/scripts/generate-artifacts.js \ + --path MyApp/ \ + --outputPath RTNCenteredText/generated/ +``` + +This script first adds the `RTNCenteredText` module to the app with `yarn add`. Then, it invokes **Codegen** via the `generate-artifacts.js` script. + +The `--path` option specifies the path to the app, while the `--outputPath` option tells the script where to output the generated code. + +The output of this process is the following folder structure: + +```sh +generated +└── build + └── generated + └── ios + ├── FBReactNativeSpec + │ ├── FBReactNativeSpec-generated.mm + │ └── FBReactNativeSpec.h + ├── RCTThirdPartyFabricComponentsProvider.h + ├── RCTThirdPartyFabricComponentsProvider.mm + └── react + └── renderer + └── components + ├── RTNCenteredTextSpecs + │ ├── 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 +``` + +The relevant path for the component is `generated/build/generated/ios/react/renderer/components/RTNCenteredTextSpecs`. +This folder contains all the generated code required by your Component. + +See the [Codegen](./pillars-codegen) section for further details on the generated files. + +:::note +When generating the scaffolding code using **Codegen**, iOS does not clean the `build` folder automatically. If you changed a the Spec name, for example, and then run **Codegen** again, the old files will be retained. +If that happens, remember to remove the `build` folder before running the **Codegen** again. + +``` +cd MyApp/ios +rm -rf build +``` + +::: + +#### Write the Native iOS Code + +Now that the scaffolding code has been generated, it's time to write the Native code for your Fabric Component. +You need to create three files in the `RTNCenteredText/ios` folder: + +1. The `RTNCenteredTextManager.mm`, an Objective-C++ file that declares what the Component exports. +2. The `RTNCenteredText.h`, a header file for the actual view. +3. The `RTNCenteredText.mm`, the implementation of the view. + +##### RTNCenteredTextManager.mm + +```objc title="RTNCenteredTextManager.mm" +#import +#import +#import + +@interface RTNCenteredTextManager : RCTViewManager +@end + +@implementation RTNCenteredTextManager + +RCT_EXPORT_MODULE(RTNCenteredText) + +RCT_EXPORT_VIEW_PROPERTY(text, NSString) + +@end +``` + +This file is the manager for the Fabric Component. The manager objects are used by the React Native runtime to register the modules, the properties and the methods so that they are available to the JavaScript side. + +The most important call is to the `RCT_EXPORT_MODULE` which is required to export the module so that Fabric can retrieve and instantiate it. + +Then, you have to expose the `text` property for the Fabric Component. This is done with the `RCT_EXPORT_VIEW_PROPERTY` macro, specifying a name and a type. + +:::info +There are other macros that can be used to export custom properties, emitters and other constructs. You can view the code that specifies them [here](https://github.com/facebook/react-native/blob/main/React/Views/RCTViewManager.h) +::: + +##### RTNCenteredText.h + +```objc title="RTNCenteredText.h" +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RTNCenteredText : RCTViewComponentView + +@end + +NS_ASSUME_NONNULL_END +``` + +This file defines the interface for the `RTNCenteredText` view. Here, you can add any native method you may want to invoke on the view. For this guide, you don't need anything, therefore the interface is empty. + +##### RTNCenteredText.mm + +```cpp title="RTNCenteredText.mm" +#import "RTNCenteredText.h" + +#import +#import +#import +#import + +#import "RCTFabricComponentsPlugins.h" + +using namespace facebook::react; + +@interface RTNCenteredText () +@end + +@implementation RTNCenteredText { + UIView *_view; + UILabel *_label; +} + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + _view = [[UIView alloc] init]; + _view.backgroundColor = [UIColor redColor]; + + _label = [[UILabel alloc] init]; + _label.text = @"Initial value"; + [_view addSubview:_label]; + + _label.translatesAutoresizingMaskIntoConstraints = false; + [NSLayoutConstraint activateConstraints:@[ + [_label.leadingAnchor constraintEqualToAnchor:_view.leadingAnchor], + [_label.topAnchor constraintEqualToAnchor:_view.topAnchor], + [_label.trailingAnchor constraintEqualToAnchor:_view.trailingAnchor], + [_label.bottomAnchor constraintEqualToAnchor:_view.bottomAnchor], + ]]; + + _label.textAlignment = NSTextAlignmentCenter; + + self.contentView = _view; + } + + return self; +} + +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + const auto &oldViewProps = *std::static_pointer_cast(_props); + const auto &newViewProps = *std::static_pointer_cast(props); + + if (oldViewProps.text != newViewProps.text) { + _label.text = [[NSString alloc] initWithCString:newViewProps.text.c_str() encoding:NSASCIIStringEncoding]; + } + + [super updateProps:props oldProps:oldProps]; +} + +@end + +Class RTNCenteredTextCls(void) +{ + return RTNCenteredText.class; +} +``` + +This file contains the actual implementation of the view. + +It starts with some imports which require you to read the files generated by **Codegen**. + +The component has to conform to a specific protocol generated by **Codegen**, in this case `RCTRTNCenteredTextViewProtocol`. + +Then, the file defines a static `(ComponentDescriptorProvider)componentDescriptorProvider` method which is used by Fabric to retrieve the descriptor provider to instantiate the object. + +Then, there is the constructor of the view: the `init` method. In this method, it is important to create a `defaultProps` struct using the `RTNCenteredTextProps` type from **Codegen**. You need to assign it to the private `_props` property to correctly initialize the Fabric Component. The remaining part of the initializer is standard Objective-C code to create views and layout them with AutoLayout. + +The last two pieces are the `updateProps` method and the `RTNCenteredTextCls` method. + +The `updateProps` method is invoked by Fabric every time a prop changes in JavaScript. The props passed as parameters are downcasted to the proper `RTNCenteredTextProps` type and then they are used to update the native code if needed. Notice that the superclass method `[super updateProps]` must be invoked as the last statement of this method, otherwise the `props` and `oldProps` struct will have the same values and you'll not be able to use them to make decisions and to update the component. + +Finally, the `RTNCenteredTextCls` is another static method used to retrieve the correct instance of the class at runtime. + +:::caution +Differently from Native Components, Fabric requires to manually implement the `updateProps` method. It's not enough to export properties with the `RCT_EXPORT_XXX` and `RCT_REMAP_XXX` macros. +::: + +### Android + +Android follows some similar steps to iOS. You have to generate the code, and then you have to write some native code to make it works. + +#### Generate the Code - Android + +To generate the code, you need to manually invoke **Codegen**. This is done similarly to what you need to do for iOS: first, you need to add the package to the app and then you need to invoke a script. + +```sh title="Running Codegen for Android" +cd MyApp +yarn add ../RTNCenteredText +cd android +./gradlew generateCodegenArtifactsFromSchema --rerun-tasks +``` + +This script first adds the package to the app, in the same way iOS does. Then, after moving to the `android` folder, it invokes a Gradle task to generate the scaffolding code. + +:::note +To run **Codegen**, you need to enable the **New Architecture** in the Android app. This can be done by opening the `gradle.properties` files and by switching the `newArchEnabled` property from `false` to `true`. +::: + +The generated code is stored in the `MyApp/node_modules/rtn-centered-text/android/build/generated/source/codegen` folder and it has this structure: + +```title="Android generated code" +codegen +├── java +│ └── com +│ └── facebook +│ └── react +│ └── viewmanagers +│ ├── RTNCenteredTextManagerDelegate.java +│ └── RTNCenteredTextManagerInterface.java +├── jni +│ ├── Android.mk +│ ├── CMakeLists.txt +│ ├── RTNCenteredText-generated.cpp +│ ├── RTNCenteredText.h +│ └── react +│ └── renderer +│ └── components +│ └── RTNCenteredText +│ ├── ComponentDescriptors.h +│ ├── EventEmitters.cpp +│ ├── EventEmitters.h +│ ├── Props.cpp +│ ├── Props.h +│ ├── ShadowNodes.cpp +│ └── ShadowNodes.h +└── schema.json +``` + +You can see that the content of the `codegen/jni/react/renderer/components/RTNCenteredTextSpecs` looks similar to the files created by the iOS counterpart. The `Android.mk` and `CMakeList.txt` files configure the Fabric Component in the app, while the `RTNCenteredTextManagerDelegate.java` and `RTNCenteredTextManagerInterface.java` files are meant use in your manager. + +See the [Codegen](./pillars-codegen) section for further details on the generated files. + +#### Write the Native Android Code + +The native code for the Android side of a Fabric Components requires three pieces: + +1. A `RTNCenteredText.java` that represents the actual view. +2. A `RTNCenteredTextManager.java` to instantiate the view. +3. Finally, you have to fill the implementation of the `RTNCenteredTextPackage.java` created in the previous step. + +The final structure within the Android library should be like this. + +```title="Android Folder Structure" +android +├── build.gradle +└── src + └── main + ├── AndroidManifest.xml + └── java + └── com + └── rtncenteredtext + ├── RTNCenteredText.java + ├── RTNCenteredTextManager.java + └── RTNCenteredTextPackage.java +``` + +##### RTNCenteredText.java + +```java title="RTNCenteredText" +package com.rtncenteredtext; + +import androidx.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.graphics.Color; + +import android.widget.TextView; +import android.view.Gravity; + +public class RTNCenteredText extends TextView { + + public RTNCenteredText(Context context) { + super(context); + this.configureComponent(); + } + + public RTNCenteredText(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.configureComponent(); + } + + public RTNCenteredText(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.configureComponent(); + } + + private void configureComponent() { + this.setBackgroundColor(Color.RED); + this.setGravity(Gravity.CENTER_HORIZONTAL); + } +} +``` + +This class represents the actual view Android is going to represent on screen. It inherit from `TextView` and it configures the basic aspects of itself using a private `configureComponent()` function. + +##### RTNCenteredTextManager.java + +```java title="RTNCenteredTextManager.java" +package com.rtncenteredtext; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManagerDelegate; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.viewmanagers.RTNCenteredTextManagerInterface; +import com.facebook.react.viewmanagers.RTNCenteredTextManagerDelegate; + + +@ReactModule(name = RTNCenteredTextManager.NAME) +public class RTNCenteredTextManager extends SimpleViewManager + implements RTNCenteredTextManagerInterface { + + private final ViewManagerDelegate mDelegate; + + static final String NAME = "RTNCenteredText"; + + public RTNCenteredTextManager(ReactApplicationContext context) { + mDelegate = new RTNCenteredTextManagerDelegate<>(this); + } + + @Nullable + @Override + protected ViewManagerDelegate getDelegate() { + return mDelegate; + } + + @NonNull + @Override + public String getName() { + return RTNCenteredTextManager.NAME; + } + + @NonNull + @Override + protected RTNCenteredText createViewInstance(@NonNull ThemedReactContext context) { + return new RTNCenteredText(context); + } + + @Override + @ReactProp(name = "text") + public void setText(RTNCenteredText view, @Nullable String text) { + view.setText(text); + } +} +``` + +The `RTNCenteredTextManager` is a class used by React Native to instantiate the native component. It is the class that implements the interfaces generated by **Codegen** (see the `RTNCenteredTextManagerInterface` interface in the `implements` clause) and it uses the `RTNCenteredTextManagerDelegate` class. + +It is also responsible for exporting all the constructs required by React Native: the class itself is annotated with `@ReactModule` and the `setText` method is annotated with `@ReactProp`. + +##### RTNCenteredTextPackage.java + +Finally, open the `RTNCenteredTextPackage.java` file in the `android/src/main/java/com/rtncenteredtext` folder and update it with the following lines + +```diff title="RTNCenteredTextPackage update" +package com.rtncenteredtext; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Collections; +import java.util.List; + +public class RTNCenteredTextPackage implements ReactPackage { + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { ++ return Collections.singletonList(new RTNCenteredTextManager(reactContext));; + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + +} +``` + +The added lines instantiate a new `RTNCenteredTextManager` object so that the React Native runtime can use it to render our Fabric Component. + +## 5. Adding the Fabric Component To Your App + +This is the last step to finally see your Fabric Component running on your app. + +### Shared + +First of all, you need to add the NPM package which contains the Component to the app. This can be done with the following command: + +```sh +cd MyApp +yarn add ../RTNCenteredText +``` + +This command adds the `RTNCenteredText` Component to the `node_modules` of your app. + +### iOS + +Then, you need to install the new dependencies in your iOS project. To do so, you need to run these commands: + +```sh +cd ios +RCT_NEW_ARCH_ENABLED=1 bundle exec pod install +``` + +This command installs the iOS dependencies for the project. The `RCT_NEW_ARCH_ENABLED=1` flag instructs **Cocoapods** that it has to execute some additional operations to run **Codegen**. + +:::note +You may have to run `bundle install` once before you can use `RCT_NEW_ARCH_ENABLED=1 bundle exec pod install`. You won't need to run `bundle install` anymore, unless you need to change the ruby dependencies. +::: + +### Android + +Android configuration requires more steps to use your new Component. First, you need to enable the **New Architecture**. + +1. Open the `android/gradle.properties` file +2. Scroll down to the end of the file and switch the `newArchEnabled` property from `false` to `true`. + +Then, you need to instruct the `Android.mk` file that it needs to build also the new library. +This can be with these steps: + +1. Open the `android/app/src/main/jni/Android.mk` file +1. Add this line to include the library at the beginning of the file: + + ```diff + include $(REACT_ANDROID_DIR)/Android-prebuilt.mk + + # If you wish to add a custom TurboModule or Fabric component in your app you + # will have to include the following autogenerated makefile. + # include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk + + +include $(NODE_MODULES_DIR)/rtn-centered-text/android/build/generated/source/codegen/jni/Android.mk + include $(CLEAR_VARS) + ``` + +1. In the same file, scroll down until you find a list of `libreact` libraries. There, you have to add the the library that has been generated. To do so, you need to add this line: + ```diff + libreact_codegen_rncore \ + +libreact_codegen_RTNCenteredText \ + libreact_debug \ + ``` + +:::note +The name of the library is `libreact_codegen_` where `` is the value that has been set in the config. +Also, this step won't be necessary anymore as soon as a version of React Native that supports autolinking for Android is released. +::: + +Finally, you need to configure the Fabric component registry to load the Fabric Component at runtime. + +1. Open the `MyApp/android/app/src/main/jni/MainComponentsRegistry.cpp` +1. Add the following include: + + ```c++ + #include + ``` + +1. Update the `sharedProviderRegistry` with this line: + + ```diff + auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry(); + + +providerRegistry->add(concreteComponentDescriptorProvider()); + + // Custom Fabric Components go here. You can register custom + ``` + +### JavaScript + +Finally, you can read the Component in your JavaScript application. +To do so, you have to: + +1. Import the Component in the js file that uses it. So, if you want to use it in the `App.js`, you need to add this line: + + ```js title="App.js" + import RTNCenteredText from 'rtn-centered-text/js/RTNCenteredTextNativeComponent'; + ``` + +2. Then, you need to use it in another React Native component. The syntax is the same as for any other component: + ```js title="App.js" + // ... other code + const App: () => Node = () => { + // ... other App code ... + return ( + // ...other React Native elements... + + // ...other React Native Elements + ); + }; + ``` -- JS spec (with all the supported features) -- Configuration (package.json, cocoapods, gradle, …) and CodeGen -- Native code (one section for iOS and one for Android) -- Integration in an App (`yarn add` and how to connect the JS specs to the app itself) -- Troubleshooting (common issues and how to solve them) +Now, you can run the React Native app and see your Component on the screen. diff --git a/docs/the-new-architecture/pillars.md b/docs/the-new-architecture/pillars.md index e390b16c47e..25ab983e22e 100644 --- a/docs/the-new-architecture/pillars.md +++ b/docs/the-new-architecture/pillars.md @@ -25,5 +25,5 @@ Finally, we dive a little deeper into the [CodeGen](pillars-codegen) process tha To integrate a TurboModule or a Fabric Component in an app, the app has to run with the New Architecture enabled. To create a new app adopting the New Architecture, refer to the [Using the App Template](use-app-template) section. -To migrate an existing app to the New Architecture, refer to the [Migration](/docs/new-architecture-intro) guide. +To migrate an existing app to the New Architecture, refer to the [Migration](../new-architecture-intro) guide. ::: diff --git a/website/core/TabsConstants.js b/website/core/TabsConstants.js index f94d13b8ed7..4fcbd4b4a54 100644 --- a/website/core/TabsConstants.js +++ b/website/core/TabsConstants.js @@ -65,7 +65,6 @@ export default { defaultPlatform, defaultSyntax, defaultAndroidLanguage, - javaScriptSpecLanguages, defaultJavaScriptSpecLanguages, getDevNotesTabs, guides, @@ -74,4 +73,5 @@ export default { platforms, syntax, androidLanguages, + javaScriptSpecLanguages, }; diff --git a/website/package.json b/website/package.json index 99980232a14..ccb3d6f4a11 100644 --- a/website/package.json +++ b/website/package.json @@ -15,7 +15,7 @@ "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "serve": "docusaurus serve", - "clean": "docusaurus clean", + "clean": "docusaurus clear", "test": "yarn build", "version:cut": "docusaurus docs:version", "format:source": "prettier --write {{core,src}/**/*.js,*.js}", From 449c3ea96b15e3335e3104138248cfe779993b60 Mon Sep 17 00:00:00 2001 From: Riccardo Date: Wed, 20 Jul 2022 14:54:36 +0100 Subject: [PATCH 10/15] Add page on codegen (#3155) --- docs/the-new-architecture/pillars-codegen.md | 226 ++++++++++++++++++- 1 file changed, 223 insertions(+), 3 deletions(-) diff --git a/docs/the-new-architecture/pillars-codegen.md b/docs/the-new-architecture/pillars-codegen.md index 0ccd35ec4db..e6cb79240b5 100644 --- a/docs/the-new-architecture/pillars-codegen.md +++ b/docs/the-new-architecture/pillars-codegen.md @@ -1,7 +1,227 @@ --- id: pillars-codegen -title: 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 that can be used to avoid writing of a lot of repetitive code. Using the **Codegen** is not mandatory: all the code that is generated by it can also be written manually. However, it generates scaffolding code that could save you a lot of time. + +The **Codegen** is invoked automatically by React Native every time an iOS or an Android app is built. Occasionally, you would like to run the scripts that generate the code manually to know which types and files are actually generated: this is a common scenario when developing [**TurboModules**](pillars-turbomodules) and [**Fabric Components**](pillars-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 + +You always need a React Native app to generate the code properly, even when invoking the **Codegen** manually. + +The **Codegen** process is tightly coupled with the build of the app and the scripts are located in the `react-native` NPM package. + +For the sake of this guide, create a project using the React Native CLI as follows: + +```sh +npx react-native init SampleApp --version 0.70.0 +``` + +:::note +This guide assumes that the React Native version in use is 0.70.0 or greater. +Previous versions of React Native uses a version of **Codegen** that requires a slightly different setup. +::: + +Then, add the module that requires the **Codegen** as an NPM dependency of the app: + +```sh +yarn add +``` + +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 that you have 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 it generates the required code. + +To invoke the script you can run this command from the root folder of your app: + +```sh +node node_modules/react_native/scripts/generate-artifacts.js \ + --path SampleApp/ \ + --outputPath \ +``` + +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 you run the **Codegen** in your app with an output path of `codegen`, for example, you 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 +``` + +The `codegen` folder sits at the root of the hierarchy, as expected. Nested into it there are two more folders: `build/generated`. + +Then, there is 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 the example above, there are both a TurboModule and a set of Fabric Components. These are generated by React Native itself: `FBReactNativeSpec` and `rncore`. These modules will always appear even if you don't have any extra TurboModule or Fabric Component: React Native requires them in order to work properly. + +### TurboModules + +Each TurboModule's folder contains two files: 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 uses 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, you need to configure the Android app to work with the New Architecture, otherwise the Gradle task fails. + +1. Open the `MyApp/android/gradle.properties` file. +1. Flip the `newArchEnabled` flag from `false` to `true`. + +After that, you can navigate into the `SampleApp/android` folder and run: + +```sh +./gradlew generateCodegenArtifactsFromSchema --rerun-tasks +``` + +This tasks invokes the `generateCodegenArtifactsFromSchema` on all the the imported projects of the app (the app and all the node modules which are linked to it). It generates the code in the corresponding `node_modules/` folder. So, for example, if you have a Fabric Component whose node module is called `my-fabric-component`, the generated code is located in the `SampleApp/node_modules/my-fabric-component/android/build/generated/source/codegen` path. + +## The Generated Code + +Once the Gradle task completes, you can see different structures for a TurboModule or for a Fabric Component. The following tab shows how they appear: + + + + +```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 +``` + + + + +```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 +``` + + + + +Java can't interoperate seamlessly with C++ as Objective-C++ does. To work properly, the **Codegen** creates some bridging between the Java and the C++ world in the `jni` folder, where the Java Native Interfaces are defined. + +Notice that both TurboModules and Fabric Components comes with two 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: there is 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, there is 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. From 444ec53f866cd45e0e7d180bf8e25233f205fe43 Mon Sep 17 00:00:00 2001 From: Lizzi Lindboe Date: Wed, 20 Jul 2022 07:51:37 -0700 Subject: [PATCH 11/15] [FEAT] TurboModules guide (#3168) * [Feat] Add intro for Fabric Components * feat: add guide to create a Fabric Component * Beginning of guide/folder structure * WIP JS Spec * specification section * Configuration * native code intro * Must be named Spec * Best stab at iOS native code, but I don't know how to describe what's going on in the code very well. Extrapolated what I could. * Android instructions iOS isn't working for me. Builds, but can't load module. Writing up Android auto-linking next because the steps I tested did work. * Include linking instructions from RNNArch repo * Add example JavaScript * native modules link * Address quick feedback items * Remove, fix for rebased branch * fix TM parameter on Android * Revert to 'Codegen' casing * Revert folly version change 2021.07.22 is for current version on main * fix typo * getTurboModule explainer * Sentence edits - Fix acronym bolding - Change wording to "recommended" because "standard" has other connotations of possibly being required - Parentheses unnecessary, distracting * Remove TODO for now Getting inconsistent results here, not sure if this is wrong or not; removing TODO for now so it doesn't block anything * ABI rephrase, more in line with new Fabric guide wording * Explain shared C++ code more * feat: add guide to create a Fabric Component * feat: add guide to create a Fabric Component * package.json description * Lint fixes * fix: Move JS constants to reduce changes * fix: Remove newline * feat: add required step for Android Codegen * fix: use the proper links Co-authored-by: Riccardo Cipolleschi --- docs/the-new-architecture/pillars-codegen.md | 10 +- .../pillars-fabric-components.md | 2 +- .../pillars-turbomodule.md | 766 +++++++++++++++++- 3 files changed, 763 insertions(+), 15 deletions(-) diff --git a/docs/the-new-architecture/pillars-codegen.md b/docs/the-new-architecture/pillars-codegen.md index e6cb79240b5..ec6532e0e6c 100644 --- a/docs/the-new-architecture/pillars-codegen.md +++ b/docs/the-new-architecture/pillars-codegen.md @@ -7,7 +7,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import con The **Codegen** is not a proper pillar, but it is a tool that can be used to avoid writing of a lot of repetitive code. Using the **Codegen** is not mandatory: all the code that is generated by it can also be written manually. However, it generates scaffolding code that could save you a lot of time. -The **Codegen** is invoked automatically by React Native every time an iOS or an Android app is built. Occasionally, you would like to run the scripts that generate the code manually to know which types and files are actually generated: this is a common scenario when developing [**TurboModules**](pillars-turbomodules) and [**Fabric Components**](pillars-fabric-components), for example. +The **Codegen** is invoked automatically by React Native every time an iOS or an Android app is built. Occasionally, you would like to run the scripts that generate the code manually to know which types and files are actually generated: this is a common scenario when developing [**TurboModules**](./pillars-turbomodules) and [**Fabric Components**](./pillars-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. @@ -34,7 +34,7 @@ Then, add the module that requires the **Codegen** as an NPM dependency of the a yarn add ``` -See how to create a [TurboModule](pillars-turbomodule) or a [Fabric Component](pillar-fabric-components) to get more information on how to configure them. +See how to create a [TurboModule](pillars-turbomodules) or a [Fabric Component](pillars-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. @@ -44,7 +44,7 @@ The rest of this guide assumes that you have a `TurboModule` and/or a `Fabric Co 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 that you have 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 it generates the required code. +The script that you have 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-turbomodules) and [Fabric Components](pillars-fabric-components) sections for details) and it generates the required code. To invoke the script you can run this command from the root folder of your app: @@ -120,7 +120,7 @@ The content of each Fabric Component folder contains several files. The basic el 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. +For further details about how Fabric works, have a look at the [Renderer](/architecture/fabric-renderer) section. ### RCTThirdPartyFabricComponentsProvider @@ -220,7 +220,7 @@ Then, it generates the C++ files in the `jni` folder. They follow the same iOS c ### 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). +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](./pillars-fabric-components) for details). Then, there is 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. diff --git a/docs/the-new-architecture/pillars-fabric-components.md b/docs/the-new-architecture/pillars-fabric-components.md index 949205e4c03..f04f75d9387 100644 --- a/docs/the-new-architecture/pillars-fabric-components.md +++ b/docs/the-new-architecture/pillars-fabric-components.md @@ -605,7 +605,7 @@ To generate the code, you need to manually invoke **Codegen**. This is done simi cd MyApp yarn add ../RTNCenteredText cd android -./gradlew generateCodegenArtifactsFromSchema --rerun-tasks +./gradlew generateCodegenArtifactsFromSchema ``` This script first adds the package to the app, in the same way iOS does. Then, after moving to the `android` folder, it invokes a Gradle task to generate the scaffolding code. diff --git a/docs/the-new-architecture/pillars-turbomodule.md b/docs/the-new-architecture/pillars-turbomodule.md index ee305ee1656..07155eec315 100644 --- a/docs/the-new-architecture/pillars-turbomodule.md +++ b/docs/the-new-architecture/pillars-turbomodule.md @@ -3,16 +3,764 @@ id: pillars-turbomodules title: TurboModules --- -This section contains a high-level introduction to TurboModules. It provides enough context to understand when a TurboModule is needed and how it roughly works. +import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import constants from '@site/core/TabsConstants'; -This section must have a warning that it works only with the new architecture enabled. It points to the [migration section](../new-architecture-intro). +If you've worked with React Native, you may be familiar with the concept of [Native Modules](../native-modules-intro.md), which allow JavaScript and platform-native code to communicate over the React Native "bridge", which handles cross-platform serialization via JSON. -## How to create a Turbomodule +TurboModules are the next iteration on Native Modules that provide a few extra [benefits](./why): -This section is a step-by-step guide to create a TurboModule from scratch. The list of subsections is roughly: +- Strongly typed interfaces that are consistent across platforms +- The ability to write your code in C++, either exclusively or integrated with another native platform language, reducing the need to duplicate implementations across platforms +- Lazy loading of modules, allowing for faster app startup +- The use of JSI, a JavaScript interface for native code, which allows for more efficient communication between native and JavaScript code than the bridge -- JS spec (with all the supported features) -- Configuration (package.json, cocoapods, gradle, …) and CodeGen -- Native code (one section for iOS and one for Android) -- Integration in an App (`yarn add` and how to connect the JS specs to the app itself) -- Troubleshooting (common issues and how to solve them) +This guide will show you how to create a basic TurboModule. + +:::caution +TurboModules only work with the **New Architecture** enabled. +To migrate to the **New Architecture**, follow the [Migration guide](../new-architecture-intro) +::: + +## How to Create a TurboModule + +To create a TurboModule, we need to: + +1. Define the JavaScript specification. +2. Configure the module so that Codegen can generate the scaffolding. +3. Write the native code to finish implementing the module. + +## 1. Folder Setup + +In order to keep the module decoupled from the app, it's a good idea to define the module separately from the app, and then add it as a dependency to your app later. This is also what you'll do for writing TurboModules that can be released as open-source libraries later. + +Next to your application, create a folder called `RTNCalculator`. **RTN** stands for "**R**eac**t** **N**ative", and is a recommended prefix for React Native modules. + +Within `RTNCalculator`, create three subfolders: `js`, `ios`, and `android`. + +The final result should look like this: + +```sh +TurboModulesGuide +├── MyApp +└── RTNCalculator + ├── android + ├── ios + └── js +``` + +## 2. JavaScript Specification + +The **New Architecture** requires interfaces specified in a typed dialect of JavaScript (either [Flow](https://flow.org/) or [TypeScript](https://www.typescriptlang.org/)). **Codegen** will use these specifications to generate code in strongly-typed languages, including C++, Objective-C++, and Java. + +There are two requirements the file containing this specification must meet: + +1. The file **must** be named `Native`, with a `.js` or `.jsx` extension when using Flow, or a `.ts`, or `.tsx` extension when using TypeScript. Codegen will only look for files matching this pattern. +2. The file must export a `TurboModuleRegistrySpec` object. + + + + +```typescript title="NativeCalculator.js" +// @flow +import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + add(a: number, b: number): Promise; +} +export default (TurboModuleRegistry.get( + 'RTNCalculator' +): ?Spec); +``` + + + + +```typescript title="NativeCalculator.ts" +import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + add(a: number, b: number): Promise; +} + +export default TurboModuleRegistry.get( + 'RTNCalculator' +) as Spec | null; +``` + + + + +At the beginning of the spec files are the imports: + +- The `TurboModule` type, which defines the base interface for all TurboModules +- The `TurboModuleRegistry` JavaScript module, which contains functions for loading TurboModules + +The second section of the file contains the interface specification for the TurboModule. In this case, the interface defines the `add` function which takes two numbers and returns a promise that resolves to a number. This interface type **must** be named `Spec` for a TurboModule. + +Finally, we invoke `TurboModuleRegistry.get`, passing the module's name, which will load the TurboModule if it's available. + +:::caution +We are writing JavaScript files importing types from libraries, without setting up a proper node module and installing its dependencies. Your IDE will not be able to resolve the import statements and you may see errors and warnings. This is expected and will not cause problems when you add the module to your app. +::: + +## 3. Module Configuration + +Next, you need to add some configuration for [**Codegen**](pillars-codegen.md) and auto-linking. + +Some of these configuration files are shared between iOS and Android, while the others are platform-specific. + +### Shared + +The shared configuration is a `package.json` file that will be used by yarn when installing your module. Create the `package.json` file in the root of the `RTNCalculator` directory. + +```json title="package.json" +{ + "name": "rtn-calculator", + "version": "0.0.1", + "description": "Add numbers with TurboModules", + "react-native": "js/index", + "source": "js/index", + "files": [ + "js", + "android", + "ios", + "rtn-calculator.podspec", + "!android/build", + "!ios/build", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "keywords": ["react-native", "ios", "android"], + "repository": "https://github.com//rtn-calculator", + "author": " (https://github.com/)", + "license": "MIT", + "bugs": { + "url": "https://github.com//rtn-calculator/issues" + }, + "homepage": "https://github.com//rtn-calculator#readme", + "devDependencies": {}, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "codegenConfig": { + "libraries": [ + { + "name": "RTNCalculatorSpec", + "type": "modules", + "jsSrcsDir": "js" + } + ] + } +} +``` + +The upper part of the file contains some descriptive information like the name of the component, its version and its source files. Make sure to update the various placeholders which are wrapped in `<>`: replace all the occurrences of the ``, ``, and `` tokens. + +Then there are the dependencies for this package. For this guide, you need `react` and `react-native`. + +Finally, the **Codegen** configuration is specified by the `codegenConfig` field. It contains an array of libraries, each of which is defined by three other fields: + +- `name`: The name of the library. By convention, you should add the `Spec` suffix. +- `type`: The type of module contained by this package. In this case, it is a TurboModule, thus the value to use is `modules`. +- `jsSrcsDir`: the relative path to access the `js` specification that is parsed by **Codegen**. + +### iOS: Create the `podspec` file + +For iOS, you'll need to create a `rtn-calculator.podspec` file which will define the module as a dependency for your app. It will stay in the root of `RTNCalculator`, alongside the `ios` folder. + +The file will look like this: + +```ruby title="rtn-calculator.podspec" +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +folly_version = '2021.06.28.00-v2' +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +Pod::Spec.new do |s| + s.name = "rtn-calculator" + s.version = package["version"] + s.summary = package["description"] + s.description = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.platforms = { :ios => "11.0" } + s.author = package["author"] + s.source = { :git => package["repository"], :tag => "#{s.version}" } + + s.source_files = "ios/**/*.{h,m,mm,swift}" + + s.dependency "React-Core" + + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + s.dependency "React-Codegen" + s.dependency "RCT-Folly", folly_version + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" +end +``` + +The `.podspec` file has to be a sibling of the `package.json` file and its name is the one we set in the `package.json`'s `name` property: `rtn-calculator`. + +The first part of the file prepares some variables we will use throughout the rest of it. Then, there is a section that contains some information used to configure the pod, like its name, version, and description. Finally, we have a set of dependencies that are required by the New Architecture. + +### Android: `build.gradle`, `AndroidManifest.xml`, a `ReactPackage` class + +To prepare Android to run **Codegen** you have to create three files: + +1. The `build.gradle` with the **Codegen** configuration +1. The `AndroidManifest.xml` file +1. A java class that implements the `ReactPackage` interface. + +At the end of these steps, the `android` folder should look like this: + +```title="Android Folder Structure" +android +├── build.gradle +└── src + └── main + ├── AndroidManifest.xml + └── java + └── com + └── rtncalculator + └── RTNCalculatorPackage.java +``` + +#### The `build.gradle` file + +First, create a `build.gradle` file in the `android` folder, with the following contents: + +```kotlin title="build.gradle" +buildscript { + ext.safeExtGet = {prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + repositories { + google() + gradlePluginPortal() + } + dependencies { + classpath("com.android.tools.build:gradle:7.1.1") + } +} + +apply plugin: 'com.android.library' +apply plugin: 'com.facebook.react' + +android { + compileSdkVersion safeExtGet('compileSdkVersion', 31) +} + +repositories { + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$projectDir/../node_modules/react-native/android" + } + mavenCentral() + google() +} + +dependencies { + implementation 'com.facebook.react:react-native:+' +} + +react { + jsRootDir = file("../js/") + libraryName = "RTNCalculator" + codegenJavaPackageName = "com.RTNCalculator" +} +``` + +Of interest in the `build.gradle` file: + +- The `react` block configures the Codegen process. For Android, we need to specify: + - the `jsRootDir`, which contains the relative path to the JavaScript specs + - the `libraryName` we will use to link the library in the app. + - the `codegenJavaPackageName` which corresponds to the name of the Java package we will use for the code generated by **Codegen**. + +#### The `AndroidManifest.xml` + +Second, create an `android/src/main` folder. Inside that folder, create a `AndroidManifest.xml` file, with the following code: + +```xml title="AndroidManifest.xml" + + +``` + +This is a small manifest file that defines the package for your module. + +#### The `ReactPackage` class + +Finally, you need a class that extends the `TurboReactPackage` interface. To run the **Codegen** process, you don't have to completely implement the package class: an empty implementation is enough for the app to pick up the module as a proper React Native dependency and to try and generate the scaffolding code. + +Create an `android/src/main/java/com/rtncalculator` folder and, inside that folder, create a `RTNCalculatorPackage.java` file. + +```java title="RTNCalculatorPackage.java" +package com.RTNCalculator; + +import androidx.annotation.Nullable; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.model.ReactModuleInfoProvider; +import com.facebook.react.TurboReactPackage; + +import java.util.Collections; +import java.util.List; + +public class CalculatorPackage extends TurboReactPackage { + + @Nullable + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { + return null; + } + + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return null; + } +} +``` + +The `ReactPackage` interface is used by React Native to understand what native classes the app has to use for the `ViewManager` and `Native Modules` exported by the library. + +## 4. Native Code + +For the final step in getting your TurboModule ready to go, you'll need to write some native code to connect the JavaScript side to the native platforms. This process requires two main steps: + +- Run **Codegen** to see what it generates. +- Write your native code, implementing the generated interfaces. + +When developing a React Native app that uses a TurboModule, it is responsibility of the app to actually generate the code using **Codegen**. However, when developing a TurboModule as a library, we need to reference the generated code, and it is therefore useful to see what the app will generate. + +As first step for both iOS and Android, this guide shows how to execute manually the scripts used by **Codegen** to generate the required code. Further information on **Codegen** can be found [here](pillars-codegen.md) + +:::caution +The code generated by **Codegen** in this step should not be committed to the versioning system. React Native apps are able to generate the code when the app is built. This allows an app to ensure that all libraries have code generated for the correct version of React Native. +::: + +### iOS + +#### Generate the code - iOS + +To run Codegen for the iOS platform, we need to open a terminal and run the following command: + +```sh title="Running Codegen for iOS" +cd MyApp +yarn add ../RTNCalculator +cd .. +node MyApp/node_modules/react-native/scripts/generate-artifacts.js \ + --path MyApp/ \ + --outputPath RTNCalculator/generated/ +``` + +This script first adds the `RTNCalculator` module to the app with `yarn add`. Then, it invokes Codegen via the `generate-artifacts.js` script. + +The `--path` option specifies the path to the app, while the `--outputPath` option tells Codegen where to output the generated code. + +The output of this process is the following folder structure: + +```sh +generated +└── build + └── generated + └── ios + ├── FBReactNativeSpec + │  ├── FBReactNativeSpec-generated.mm + │  └── FBReactNativeSpec.h + ├── RCTThirdPartyFabricComponentsProvider.h + ├── RCTThirdPartyFabricComponentsProvider.mm + ├── RTNCalculatorSpec + │  ├── RTNCalculatorSpec-generated.mm + │  └── RTNCalculatorSpec.h + └── react + └── renderer + └── components + └── rncore + ├── ComponentDescriptors.h + ├── EventEmitters.cpp + ├── EventEmitters.h + ├── Props.cpp + ├── Props.h + ├── RCTComponentViewHelpers.h + ├── ShadowNodes.cpp + └── ShadowNodes.h +``` + +The relevant path for the TurboModule interface is `generated/build/generated/ios/RTNCalculatorSpec`. + +See the [Codegen](./pillars-codegen) section for further details on the generated files. + +:::note +When generating the scaffolding code using **Codegen**, iOS does not clean the `build` folder automatically. If you changed a the Spec name, for example, and then run **Codegen** again, the old files will be retained. +If that happens, remember to remove the `build` folder before running the **Codegen** again. + +``` +cd MyApp/ios +rm -rf build +``` + +::: + +#### Write the Native iOS Code + +Now add the Native code for your TurboModule. Create two files in the `RTNCalculator/ios` folder: + +1. The `RTNCalculator.h`, a header file for the module. +2. The `RTNCalculator.mm`, the implementation of the module. + +##### RTNCalculator.h + +```objc title="RTNCalculator.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RTNCalculator : NSObject + +@end + +NS_ASSUME_NONNULL_END +``` + +This file defines the interface for the `RTNCalculator` module. Here, we can add any native method we may want to invoke on the view. For this guide, we don't need anything, therefore the interface is empty. + +##### RTNCalculator.mm + +```objc title="RTNCalculator.mm" +#import "RTNCalculatorSpec.h" +#import "RTNCalculator.h" + +@implementation RTNCalculator + +RCT_EXPORT_MODULE(RTNCalculator) + +RCT_REMAP_METHOD(add, addA:(NSInteger)a + andB:(NSInteger)b + withResolver:(RCTPromiseResolveBlock) resolve + withRejecter:(RCTPromiseRejectBlock) reject) +{ + NSNumber *result = [[NSNumber alloc] initWithInteger:a+b]; + resolve(result); +} + +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} + +@end +``` + +The most important call is to the `RCT_EXPORT_MODULE`, which is required to export the module so that React Native can load the TurboModule. + +Then the `RCT_REMAP_METHOD` macro is used to expose the `add` method. + +Finally, the `getTurboModule` method gets an instance of the TurboModule so that the JavaScript side can invoke its methods. The function is defined in (and requested by) the `RTNCalculatorSpec.h` file that was generated earlier by Codegen. + +:::info +There are other macros that can be used to export modules and methods. You view the code that specifies them [here](https://github.com/facebook/react-native/blob/main/React/Base/RCTBridgeModule.h). +::: + +### Android + +Android follows similar steps to iOS. We have to generate the code for Android, and then we have to write some native code to make it work. + +#### Generate the Code - Android + +To generate the code for Android, we need to manually invoke Codegen. This is done similarly to what we did for iOS: first, we need to add the package to the app and then we need to invoke a script. + +```sh title="Running Codegen for Android" +cd MyApp +yarn add ../RTNCalculator +cd android +./gradlew generateCodegenArtifactsFromSchema +``` + +This script first adds the package to the app, in the same way iOS does. Then, after moving to the `android` folder, it invokes a Gradle task to create the generated code. + +:::note +To run **Codegen**, you need to enable the **New Architecture** in the Android app. This can be done by opening the `gradle.properties` files and by switching the `newArchEnabled` property from `false` to `true`. +::: + +The generated code is stored in the `MyApp/node_modules/rtn-calculator/android/build/generated/source/codegen` folder and it has this structure: + +```title="Android generated code" +codegen +├── java +│  └── com +│  └── RTNCalculator +│  └── NativeCalculatorSpec.java +├── jni +│  ├── Android.mk +│  ├── RTNCalculator-generated.cpp +│  ├── RTNCalculator.h +│  └── react +│  └── renderer +│  └── components +│  └── RTNCalculator +│  ├── ComponentDescriptors.h +│  ├── EventEmitters.cpp +│  ├── EventEmitters.h +│  ├── Props.cpp +│  ├── Props.h +│  ├── ShadowNodes.cpp +│  └── ShadowNodes.h +└── schema.json +``` + +#### Write the Native Android Code + +The native code for the Android side of a TurboModule requires: + +1. to create a `RTNCalculatorModule.java` that implements the module. +2. to update the `RTNCalculatorPackage.java` created in the previous step. + +The final structure within the Android library should look like this: + +```title="Android Folder Structure" +android +├── build.gradle +└── src + └── main + ├── AndroidManifest.xml + └── java + └── com + └── RTNCalculator + ├── CalculatorModule.java + └── CalculatorPackage.java +``` + +##### Creating the `CalculatorModule.java` + +```java title="CalculatorModule.java" +package com.RTNCalculator; + +import androidx.annotation.NonNull; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import java.util.Map; +import java.util.HashMap; + +public class CalculatorModule extends NativeCalculatorSpec { + + public static String NAME = "RTNCalculator"; + + CalculatorModule(ReactApplicationContext context) { + super(context); + } + + @Override + @NonNull + public String getName() { + return NAME; + } + + @Override + public void add(double a, double b, Promise promise) { + promise.resolve(a + b); + } +} +``` + +This class implements the module itself, which extends the `NativeCalculatorSpec` that was generated from the `NativeCalculator` JavaScript specification file. + +##### Updating the `CalculatorPackage.java` + +```diff title="CalculatorPackage.java" +package com.RTNCalculator; + +import androidx.annotation.Nullable; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; ++ import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; +import com.facebook.react.TurboReactPackage; + +import java.util.Collections; +import java.util.List; ++ import java.util.HashMap; ++ import java.util.Map; + +public class CalculatorPackage extends TurboReactPackage { + + @Nullable + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { ++ if (name.equals(CalculatorModule.NAME)) { ++ return new CalculatorModule(reactContext); ++ } else { + return null; ++ } + } + + + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { +- return null; ++ return () -> { ++ final Map moduleInfos = new HashMap<>(); ++ moduleInfos.put( ++ CalculatorModule.NAME, ++ new ReactModuleInfo( ++ CalculatorModule.NAME, ++ CalculatorModule.NAME, ++ false, // canOverrideExistingModule ++ false, // needsEagerInit ++ true, // hasConstants ++ false, // isCxxModule ++ true // isTurboModule ++ )); ++ return moduleInfos; ++ }; + } +} +``` + +This is the last piece of Native Code for Android. It defines the `TurboReactPackage` object that will be used by the app to load the module. + +## 5. Adding the TurboModule to your App + +Now you can install and use the TurboModule in your app. + +### Shared + +First of all, we need to add the NPM package which contains the Component to the app. This can be done with the following command: + +```sh +cd MyApp +yarn add ../RTNCalculator +``` + +This command will add the `RTNCalculator` module to the `node_modules` of your app. + +### iOS + +Then, you need to install the new dependencies in your iOS project. To do so, run these commands: + +```sh +cd ios +RCT_NEW_ARCH_ENABLED=1 bundle exec pod install +``` + +This command will look for all the dependencies of the project and it will install the iOS ones. The `RCT_NEW_ARCH_ENABLED=1` instruct **Cocoapods** that it has to run some additional operations to run **Codegen**. + +:::note +You may have to run `bundle install` once before you can use `RCT_NEW_ARCH_ENABLED=1 bundle exec pod install`. You won't need to run `bundle install` anymore, unless you need to change the Ruby dependencies. +::: + +### Android + +Android configuration requires slightly more steps in order to be able to use your new TurboModule. + +First, to enable the **New Architecture**: + +1. Open the `android/gradle.properties` file +2. Scroll down to the end of the file and switch the `newArchEnabled` property from `false` to `true`. + +Then, to manually link your new TurboModule: + +1. Open the `NewArchitecture/android/app/src/main/jni/Android.mk` file and update the file as it follows: + ```diff + # If you wish to add a custom TurboModule or Fabric component in your app you + # will have to include the following autogenerated makefile. + # include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk + + + + include $(NODE_MODULES_DIR)/rtn-calculator/android/build/generated/source/codegen/jni/Android.mk + include $(CLEAR_VARS) + ``` +1. In the same file above, go to the `LOCAL_SHARED_LIBRARIES` setting and add the following line: + ```diff + libreact_codegen_rncore \ + + libreact_codegen_RTNCalculator \ + libreact_debug \ + ``` +1. Open the `NewArchitecture/android/app/src/main/jni/MainApplicationModuleProvider.cpp` file and update the file as it follows: + + 1. Add the import for the calculator: + ```diff + #include + + #include + ``` + 1. Add the following check in the `MainApplicationModuleProvider` constructor: + + ```diff + // auto module = samplelibrary_ModuleProvider(moduleName, params); + // if (module != nullptr) { + // return module; + // } + + + auto module = RTNCalculator_ModuleProvider(moduleName, params); + + if (module != nullptr) { + + return module; + + } + + return rncore_ModuleProvider(moduleName, params); + } + ``` + +### JavaScript + +Now you can use your TurboModule calculator in your app! + +Here's an example App.js file using the `add` method: + +```js title="App.js" +/** + * Sample React Native App + * https://github.com/facebook/react-native + * + * @format + * @flow strict-local + */ +import React from 'react'; +import { useState } from 'react'; +import type { Node } from 'react'; +import { + SafeAreaView, + StatusBar, + Text, + Button +} from 'react-native'; +import RTNCalculator from 'rtn-calculator/js/NativeCalculator.js'; + +const App: () => Node = () => { + const [result, setResult] = useState(null); + return ( + + + + 3+7={result ?? '??'} + +