├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md └── docs ├── appendix.md ├── assets └── metro-new-arch.png ├── backwards-compat-fabric-component.md ├── backwards-compat-turbo-modules.md ├── backwards-compat.md ├── codegen.md ├── cxx-custom-types.md ├── enable-apps.md ├── enable-libraries-android.md ├── enable-libraries-ios.md ├── enable-libraries-prerequisites.md ├── enable-libraries.md ├── fabric-native-components.md ├── react-18.md ├── troubleshooting.md ├── turbo-modules-xplat.md └── turbo-modules.md /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when there is a 56 | reasonable belief that an individual's behavior may have a negative impact on 57 | the project or its community. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported by contacting the project team at . All 63 | complaints will be reviewed and investigated and will result in a response that 64 | is deemed necessary and appropriate to the circumstances. The project team is 65 | obligated to maintain confidentiality with regard to the reporter of an incident. 66 | Further details of specific enforcement policies may be posted separately. 67 | 68 | Project maintainers who do not follow or enforce the Code of Conduct in good 69 | faith may face temporary or permanent repercussions as determined by other 70 | members of the project's leadership. 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 75 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 76 | 77 | [homepage]: https://www.contributor-covenant.org 78 | 79 | For answers to common questions about this code of conduct, see 80 | https://www.contributor-covenant.org/faq 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Meta Platforms, Inc. and affiliates. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native New Architecture Working Group 2 | 3 | The intention of this repository is to coordinate and support the rollout of the **[React Native New Architecture](https://reactnative.dev/docs/the-new-architecture/landing-page)**. We have provided [guides](#guides) and a [discussion](#discussion) space for this purpose. 4 | 5 | You can find New Architecture updates [here](https://github.com/reactwg/react-native-new-architecture/discussions/categories/releases). 6 | 7 | ## Guides 8 | 9 | > [!Caution] 10 | > These guides are deprecated. 11 | > 12 | > You can find the most updated versions of them in the [React Native website](https://reactnative.dev). 13 | 14 | - How to enable the New Architecture 15 | - For Apps 16 | - [Enable the New Architecture for Apps](./docs/enable-apps.md) 17 | - For Libraries 18 | - [Make your library compatible with the New Architecture](./docs/enable-libraries.md) 19 | - [Convert Library to TurboModules/Fabric APIs](./docs/enable-libraries-prerequisites.md) 20 | - [Additional information for Android](./docs/enable-libraries-android.md) 21 | - [Additional information for iOS](./docs/enable-libraries-ios.md) 22 | - New Architecture Workflows 23 | - [Create a Fabric Native Component](./docs/fabric-native-components.md) 24 | - [Create a Turbo Native Module](./docs/turbo-modules.md) 25 | - [Using Codegen](./docs/codegen.md) to write type-safe Fabric Components and Turbo Modules 26 | - Writing [cross-platform TurboModules](./docs/turbo-modules-xplat.md) with C++ 27 | - Supporting [custom C++ types](./docs/cxx-custom-types.md) 28 | - [Using React 18](./docs/react-18.md) features 29 | - [Backwards compatibility](./docs/backwards-compat.md) 30 | - For [Legacy Native Modules](./docs/backwards-compat-turbo-modules.md) 31 | - For [Legacy Native Components](./docs/backwards-compat-fabric-component.md) 32 | - [Troubleshooting](./docs/troubleshooting.md) 33 | - [Appendix](./docs/appendix.md) 34 | 35 | ## Discussion 36 | 37 | This repository is also a place for discussion and feedback on the New Architecture. You can access it by heading over to the [Discussions Tab on Github](https://github.com/reactwg/react-native-new-architecture/discussions). 38 | 39 | 40 | ### Sections 41 | 42 | We've created some sections to keep the discussion focused. 43 | 44 | | Title | Topic | 45 | | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | 46 | | [Announcements 📣](https://github.com/reactwg/react-native-new-architecture/discussions/categories/announcements) | General announcements about this working group. | 47 | | [Deep Dive 🐳](https://github.com/reactwg/react-native-new-architecture/discussions/categories/deep-dive) | Sharing deep dives and technical-specific topics | 48 | | [Documentation 📚](https://github.com/reactwg/react-native-new-architecture/discussions/categories/documentation) | A place to chat about the New Architecture documentation and migration material | 49 | | [Libraries 🛠](https://github.com/reactwg/react-native-new-architecture/discussions/categories/libraries) | A place to chat about 3rd party libraries and their migration story to the New Architecture | 50 | | [Q&A 🤝](https://github.com/reactwg/react-native-new-architecture/discussions/categories/q-a) | A place to ask the community for help on the New Architecture topics | 51 | | [Releases 🏁](https://github.com/reactwg/react-native-new-architecture/discussions/categories/releases) | Updates on New Architecture in each release | 52 | -------------------------------------------------------------------------------- /docs/appendix.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Appendix 4 | 5 | ## I. Terminology 6 | 7 | The whole New Architecture related guides will stick to the following **terminology**: 8 | 9 | - **Legacy Native Components** - To refer to Components which are running on the old React Native architecture. 10 | - **Legacy Native Modules** - To refer to Modules which are running on the old React Native architecture. 11 | - **Fabric Native Components** - To refer to Components which have been adapted to work well with the New Architecture, namely the new renderer. For brevity you might find them referred as **Fabric Components**. 12 | - **Turbo Native Modules** - To refer to Modules which have been adapted to work well with the New Architecture, namely the new Native Module System. For brevity you might find them referred as **Turbo Modules** 13 | 14 | ## II. Flow Type to Native Type Mapping 15 | 16 | You may use the following table as a reference for which types are supported and what they map to in each platform: 17 | 18 | ### `string` 19 | | | | 20 | |---|---| 21 | | Nullable Support? | `?string` | 22 | | Android (Java) | `string` | 23 | | iOS | `NSString` | 24 | 25 | ### `boolean` 26 | | | | 27 | |---|---| 28 | | Nullable Support? | `?boolean` | 29 | | Android (Java) | `Boolean` | 30 | | iOS | `NSNumber` | 31 | 32 | ### Object literal 33 | 34 | This is recommended over using plain `Object`, for type safety. 35 | 36 | **Example:** `{| foo: string, ... |}` 37 | | | | 38 | |---|---| 39 | | Nullable Support? | ?{| foo: string, ...|} | 40 | | Android (Java) | - | 41 | | iOS | - | 42 | 43 | ### `Object` 44 | 45 | > [!NOTE] 46 | > Recommended to use [Object literal](#object-literal) instead. 47 | 48 | | | | 49 | |---|---| 50 | | Nullable Support? | `?Object` | 51 | | Android (Java) | `ReadableMap` | 52 | | iOS | `@` (untyped dictionary) | 53 | 54 | ### `Array<*>` 55 | | | | 56 | |---|---| 57 | | Nullable Support? | `?Array<*>` | 58 | | Android (Java) | `ReadableArray` | 59 | | iOS | `NSArray` (or `RCTConvertVecToArray` when used inside objects) | 60 | 61 | ### `Function` 62 | | | | 63 | |---|---| 64 | | Nullable Support? | `?Function` | 65 | | Android (Java) | - | 66 | | iOS | - | 67 | 68 | ### `Promise<*>` 69 | | | | 70 | |---|---| 71 | | Nullable Support? | `?Promise<*>` | 72 | | Android (Java) | `com.facebook.react.bridge.Promise` | 73 | | iOS | `RCTPromiseResolve` and `RCTPromiseRejectBlock` | 74 | 75 | ### Type Unions 76 | 77 | Type unions are only supported as callbacks. 78 | 79 | **Example:** `'SUCCESS' | 'FAIL'` 80 | | | | 81 | |---|---| 82 | | Nullable Support? | Only as callbacks | 83 | | Android (Java) | - | 84 | | iOS | - | 85 | 86 | ### Callbacks 87 | 88 | Callback functions are not type checked, and are generalized as `Object`s. 89 | 90 | **Example:** `() =>` 91 | | | | 92 | |---|---| 93 | | Nullable Support? | Yes | 94 | | Android (Java) | `com.facebook.react.bridge.Callback` | 95 | | iOS | `RCTResponseSenderBlock` | 96 | 97 | > [!Tip] 98 | > You may also find it useful to refer to the JavaScript specifications for the core modules in React Native. These are located inside the `Libraries/` directory in the React Native repository. 99 | 100 | ## III. TypeScript to Native Type Mapping 101 | 102 | You may use the following table as a reference for which types are supported and what they map to in each platform: 103 | 104 | ### `string` 105 | | | | 106 | |---|---| 107 | | Nullable Support? | string | null | 108 | | Android (Java) | `String` | 109 | | iOS | `NSString` | 110 | 111 | ### `boolean` 112 | | | | 113 | |---|---| 114 | | Nullable Support? | boolean | null | 115 | | Android (Java) | `Boolean` | 116 | | iOS | `NSNumber` | 117 | 118 | ### `number` 119 | | | | 120 | |---|---| 121 | | Nullable Support? | No | 122 | | Android (Java) | `double` | 123 | | iOS | `NSNumber` | 124 | 125 | ### Object literal 126 | 127 | This is recommended over using plain `Object`, for type safety. 128 | 129 | **Example:** `{| foo: string, ... |}` 130 | | | | 131 | |---|---| 132 | | Nullable Support? | ?{| foo: string, ...|} | null | 133 | | Android (Java) | - | 134 | | iOS | - | 135 | 136 | ### `Object` 137 | 138 | > [!NOTE] 139 | > Recommended to use [Object literal](#object-literal-1) instead. 140 | 141 | | | | 142 | |---|---| 143 | | Nullable Support? | Object | null | 144 | | Android (Java) | `ReadableMap` | 145 | | iOS | `@` (untyped dictionary) | 146 | 147 | ### `Array<*>` 148 | | | | 149 | |---|---| 150 | | Nullable Support? | Array<*> | null | 151 | | Android (Java) | `ReadableArray` | 152 | | iOS | `NSArray` (or `RCTConvertVecToArray` when used inside objects) | 153 | 154 | ### `Function` 155 | | | | 156 | |---|---| 157 | | Nullable Support? | Function | null | 158 | | Android (Java) | - | 159 | | iOS | - | 160 | 161 | ### `Promise<*>` 162 | | | | 163 | |---|---| 164 | | Nullable Support? | Promise<*> | null | 165 | | Android (Java) | `com.facebook.react.bridge.Promise` | 166 | | iOS | `RCTPromiseResolve` and `RCTPromiseRejectBlock` | 167 | 168 | ### Type Unions 169 | 170 | Type unions are only supported as callbacks. 171 | 172 | **Example:** `'SUCCESS' | 'FAIL'` 173 | | | | 174 | |---|---| 175 | | Nullable Support? | Only as callbacks | 176 | | Android (Java) | - | 177 | | iOS | - | 178 | 179 | ### Callbacks 180 | 181 | Callback functions are not type checked, and are generalized as `Object`s. 182 | 183 | **Example:** `() =>` 184 | | | | 185 | |---|---| 186 | | Nullable Support? | Yes | 187 | | Android (Java) | `com.facebook.react.bridge.Callback` | 188 | | iOS | `RCTResponseSenderBlock` | 189 | 190 | > [!Tip] 191 | > You may also find it useful to refer to the JavaScript specifications for the core modules in React Native. These are located inside the `Libraries/` directory in the React Native repository. 192 | 193 | ## IV. Invoking the code-gen during development 194 | 195 | The Codegen is typically invoked at build time, but you may find it useful to generate your native interface code on demand for troubleshooting. 196 | 197 | If you wish to invoke the Codegen manually, you have three options: 198 | 199 | 1. Invoking a Gradle task directly (Android). 200 | 2. Invoking a script manually. 201 | 3. Use the Codegen CLI. 202 | 203 | ### Using the CLI 204 | 205 | For the simplest and most common use cases you can use the Codegen CLI. Call the following command in your project directory: 206 | 207 | ```sh 208 | npx react-native codegen 209 | ``` 210 | 211 | This will produce Codegen artefacts in their default locations. You can provide additional options to customize input and output paths, as well as the target platform. 212 | 213 | See full description in [The Codegen CLI](./codegen.md#the-codegen-cli) section. 214 | 215 | ### Android - Invoking a Gradle task directly 216 | 217 | You can trigger the Codegen by invoking the following task: 218 | 219 | ```bash 220 | ./gradlew generateCodegenArtifactsFromSchema --rerun-tasks 221 | ``` 222 | 223 | The extra `--rerun-tasks` flag is added to make sure Gradle is ignoring the `UP-TO-DATE` checks for this task. You should not need it during normal development. 224 | 225 | The `generateCodegenArtifactsFromSchema` task normally runs before the `preBuild` task, so you should not need to invoke it manually, but it will be triggered before your builds. 226 | 227 | ### Invoking the script manually 228 | 229 | Alternatively, you can invoke the Codegen directly, bypassing the Gradle Plugin or CocoaPods infrastructure. 230 | This can be done with the following commands. 231 | 232 | The parameters to provide will look quite familiar to you now that you have already configured the Gradle plugin or CocoaPods library. 233 | 234 | #### Generating the schema file 235 | 236 | First, you’ll need to generate a schema file from your JavaScript sources. You only need to do this whenever your JavaScript specs change. The script to generate this schema is provided as part of the `react-native-codegen` package. If running this from within your React Native application, you can use the package from `node_modules` directly: 237 | 238 | ```bash 239 | node node_modules/react-native-codegen/lib/cli/combine/combine-js-to-schema-cli.js \ 240 | 241 | ``` 242 | 243 | > The source for the `react-native-codegen` is available in the React Native repository, under `packages/react-native-codegen`. Run `yarn install` and `yarn build` in that directory to build your own `react-native-codegen` package from source. In most cases, you will not want to do this as the guide assumes the use of the `react-native-codegen` package version that is associated with the relevant React Native nightly release. 244 | 245 | #### Generating the native code artifacts 246 | 247 | Once you have a schema file for your native modules or components, you can use a second script to generate the actual native code artifacts for your library. You can use the same schema file generated by the previous script. 248 | 249 | ```bash 250 | node node_modules/react-native/scripts/generate-specs-cli.js \ 251 | --platform \ 252 | --schemaPath \ 253 | --outputDir \ 254 | [--libraryName library_name] \ 255 | [--javaPackageName java_package_name] \ 256 | [--libraryType all(default)|modules|components] 257 | ``` 258 | 259 | > [!Note] 260 | > By default, the output artifacts of the Codegen are written to the build folder and should not be committed. They should be considered only for reference. 261 | > It is also possible to include the codegen output into your library. [Read more](./codegen.md#including-generated-code-into-libraries). 262 | 263 | ##### Example 264 | 265 | The following is a basic example of invoking the Codegen script to generate native iOS interface code for a library that provides native modules. The JavaScript spec sources for this library are located in a `js/` subdirectory, and this library’s native code expects the native interfaces to be available in the `ios` subdirectory. 266 | 267 | ```bash 268 | # Generate schema - only needs to be done whenever JS specs change 269 | node node_modules/react-native-codegen/lib/cli/combine/combine-js-to-schema-cli.js /tmp/schema.json ./js 270 | 271 | # Generate native code artifacts 272 | node node_modules/react-native/scripts/generate-specs-cli.js \ 273 | --platform ios \ 274 | --schemaPath /tmp/schema.json \ 275 | --outputDir ./ios \ 276 | --libraryName MyLibSpecs \ 277 | --libraryType modules 278 | ``` 279 | 280 | In the above example, the code-gen script will generate several files: `MyLibSpecs.h` and `MyLibSpecs-generated.mm`, as well as a handful of `.h` and `.cpp` files, all located in the `ios` directory. 281 | 282 | ## V. Note on Existing Apps 283 | 284 | This guide provides instructions for migrating an application that is based on the default app template that is provided by React Native. If your app has deviated from the template, or you are working with an application that was never based off the template, then the following sections might help. 285 | 286 | --- 287 | 288 | > [!IMPORTANT] 289 | > This documentation is still experimental and details are subject to changes as we iterate. 290 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 291 | > 292 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 293 | -------------------------------------------------------------------------------- /docs/assets/metro-new-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reactwg/react-native-new-architecture/07a7abd76ae41e7c018acfae306bd2568f41f7cc/docs/assets/metro-new-arch.png -------------------------------------------------------------------------------- /docs/backwards-compat-fabric-component.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Fabric Components as Legacy Native Components 4 | 5 | > [!Note] 6 | > Creating a backward compatible Fabric Native Component requires the knowledge of how to create a Legacy Native Component. To recall these concepts, have a look at this [guide](./fabric-native-components.md). 7 | 8 | Creating a backward compatible Fabric Native Component lets your users continue to leverage your library independently from the architecture they use. The creation of such a component requires a few steps: 9 | 10 | 1. Configure the library so that dependencies are prepared to set up properly for both the Old and the New Architecture. 11 | 1. Update the codebase so that the New Architecture types are not compiled when not available. 12 | 1. Uniform the JavaScript API so that your user code won't need changes. 13 | 14 | > [!Note] 15 | > For the sake of this guide we're going to use the following **terminology**: 16 | > 17 | > - **Legacy Native Components** - To refer to Components which are running on the old React Native architecture. 18 | > - **Fabric Native Components** - To refer to Components which have been adapted to work well with the New Native Renderer, Fabric. For brevity you might find them referred as **Fabric Components**. 19 | 20 | > [!Warning] 21 | > The TypeScript support for the New Architecture is still in beta. 22 | 23 | While the last step is the same for all the platforms, the first two steps are different for iOS and Android. 24 | 25 | ## iOS 26 | ### Project Dependencies 27 | 28 | The Apple platform installs Fabric Native Components using [CocoaPods](https://cocoapods.org) as a dependency manager. 29 | 30 | If you are already using the [`install_module_dependencies`](https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L198) function, then **there is nothing to do**. The function already takes care of installing the proper dependencies when the New Architecture is enabled and avoiding them when it is not enabled. 31 | 32 | Otherwise, your Fabric Native Component's `podspec` should look like this: 33 | 34 | ```ruby 35 | require "json" 36 | 37 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 38 | 39 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' 40 | 41 | Pod::Spec.new do |s| 42 | # Default fields for a valid podspec 43 | s.name = "" 44 | s.version = package["version"] 45 | s.summary = package["description"] 46 | s.description = package["description"] 47 | s.homepage = package["homepage"] 48 | s.license = package["license"] 49 | s.platforms = { :ios => "11.0" } 50 | s.author = package["author"] 51 | s.source = { :git => package["repository"], :tag => "#{s.version}" } 52 | 53 | s.source_files = "ios/**/*.{h,m,mm,swift}" 54 | # React Native Core dependency 55 | s.dependency "React-Core" 56 | 57 | # The following lines are required by the New Architecture. 58 | s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" 59 | s.pod_target_xcconfig = { 60 | "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", 61 | "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", 62 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 63 | } 64 | 65 | s.dependency "React-RCTFabric" 66 | s.dependency "React-Codegen" 67 | s.dependency "RCT-Folly" 68 | s.dependency "RCTRequired" 69 | s.dependency "RCTTypeSafety" 70 | s.dependency "ReactCommon/turbomodule/core" 71 | end 72 | ``` 73 | 74 | You should install the extra dependencies when the New Architecture is enabled, and avoid installing them when it's not. 75 | To achieve this, you can use the [`install_modules_dependencies`](https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L198). Update the `.podspec` file as it follows: 76 | 77 | ```diff 78 | require "json" 79 | 80 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 81 | 82 | - folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' 83 | 84 | Pod::Spec.new do |s| 85 | # Default fields for a valid podspec 86 | s.name = "" 87 | s.version = package["version"] 88 | s.summary = package["description"] 89 | s.description = package["description"] 90 | s.homepage = package["homepage"] 91 | s.license = package["license"] 92 | s.platforms = { :ios => "11.0" } 93 | s.author = package["author"] 94 | s.source = { :git => package["repository"], :tag => "#{s.version}" } 95 | 96 | s.source_files = "ios/**/*.{h,m,mm,swift}" 97 | # React Native Core dependency 98 | + install_modules_dependencies(s) 99 | - s.dependency "React-Core" 100 | - # The following lines are required by the New Architecture. 101 | - s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" 102 | - s.pod_target_xcconfig = { 103 | - "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", 104 | - "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", 105 | - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 106 | - } 107 | - 108 | - s.dependency "React-RCTFabric" 109 | - s.dependency "React-Codegen" 110 | - s.dependency "RCT-Folly" 111 | - s.dependency "RCTRequired" 112 | - s.dependency "RCTTypeSafety" 113 | - s.dependency "ReactCommon/turbomodule/core" 114 | end 115 | ``` 116 | 117 | ### Update the codebase 118 | 119 | 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. 120 | 121 | A Fabric Native Component requires a header file and an implementation file to add the actual `View` to the module. 122 | 123 | For example, the `RNMyComponentView.h` header file could look like this: 124 | 125 | ```objectivec title='RNMyComponentView.h' 126 | #import 127 | #import 128 | 129 | #ifndef NativeComponentExampleComponentView_h 130 | #define NativeComponentExampleComponentView_h 131 | 132 | NS_ASSUME_NONNULL_BEGIN 133 | 134 | @interface RNMyComponentView : RCTViewComponentView 135 | @end 136 | 137 | NS_ASSUME_NONNULL_END 138 | 139 | #endif /* NativeComponentExampleComponentView_h */ 140 | ``` 141 | 142 | The implementation `RNMyComponentView.mm` file, instead, could look like this: 143 | 144 | ```objectivec title='RNMyComponentView.mm' 145 | #import "RNMyComponentView.h" 146 | 147 | // 148 | 149 | #import "RCTFabricComponentsPlugins.h" 150 | 151 | using namespace facebook::react; 152 | 153 | @interface RNMyComponentView () 154 | 155 | @end 156 | 157 | @implementation RNMyComponentView { 158 | UIView * _view; 159 | } 160 | 161 | + (ComponentDescriptorProvider)componentDescriptorProvider 162 | { 163 | // ... return the descriptor ... 164 | } 165 | 166 | - (instancetype)initWithFrame:(CGRect)frame 167 | { 168 | // ... initialize the object ... 169 | } 170 | 171 | - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps 172 | { 173 | // ... set up the props ... 174 | 175 | [super updateProps:props oldProps:oldProps]; 176 | } 177 | 178 | Class MyComponentViewCls(void) 179 | { 180 | return RNMyComponentView.class; 181 | } 182 | 183 | @end 184 | ``` 185 | 186 | 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: 187 | 188 | ```diff 189 | + #ifdef RCT_NEW_ARCH_ENABLED 190 | #import 191 | #import 192 | 193 | // ... rest of the header file ... 194 | 195 | #endif /* NativeComponentExampleComponentView_h */ 196 | + #endif 197 | ``` 198 | 199 | The same two lines should be added in the implementation file, as first and last lines. 200 | 201 | 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. 202 | 203 | After wrapping the above components with a `#ifdef` pragma, you need to implement the component for the legacy architecture, following [the legacy Native Component documentation](https://reactnative.dev/docs/native-components-ios). This is needed because the New Renderer works in a different way from the legacy one, and it is not able to follow the new code's paths of the New Renderer. 204 | 205 | ## Android 206 | 207 | > [!Note] 208 | > 209 | > You can configure your library to [include codegen artifacts](https://reactnative.dev/docs/the-new-architecture/codegen-cli#including-generated-code-into-libraries). Doing this will make it easier to offer backward compatibility with old arch, but has [implications on libraries](https://reactnative.dev/docs/the-new-architecture/codegen-cli#including-generated-code-into-libraries) that you need to be aware. 210 | > 211 | > The following instructions are only for libraries that chose to rely on the app-level codegen. 212 | 213 | ### Project Dependencies 214 | 215 | To create a Native Component 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. 216 | 217 | To configure the Fabric Native Component so that it picks the proper sourceset, you have to update the `build.gradle` file in the following way: 218 | 219 | ```diff title="build.gradle" 220 | +// Add this function in case you don't have it already 221 | + def isNewArchitectureEnabled() { 222 | + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" 223 | +} 224 | // ... other parts of the build file 225 | defaultConfig { 226 | minSdkVersion safeExtGet('minSdkVersion', 21) 227 | targetSdkVersion safeExtGet('targetSdkVersion', 31) 228 | + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) 229 | + } 230 | + 231 | + sourceSets { 232 | + main { 233 | + if (isNewArchitectureEnabled()) { 234 | + java.srcDirs += ['src/newarch'] 235 | + } else { 236 | + java.srcDirs += ['src/oldarch'] 237 | + } 238 | + } 239 | } 240 | } 241 | ``` 242 | 243 | These changes do three main things: 244 | 245 | 1. The first lines define a function that returns whether the New Architecture is enabled or not. 246 | 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. 247 | 3. The last lines leverage the function declared in step one to decide which source sets we need to build, depending on the chosen architecture. 248 | 249 | ### Update the codebase 250 | 251 | 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. 252 | 253 | Therefore, you have to: 254 | 255 | 1. Create a Legacy Native Component in the `src/oldarch` path. See [this guide](https://reactnative.dev/docs/native-components-android) to learn how to create a Legacy Native Component. 256 | 2. Create a Fabric Native Component in the `src/newarch` path. See [this guide](./fabric-native-components.md) to learn how to create a Fabric Native Component. 257 | 258 | and then instruct Gradle to decide which implementation to pick. 259 | 260 | Some files can be shared between a Legacy and a Fabric Component: these should be created or moved into a folder that is loaded by both the architectures. These files are: 261 | 262 | - the `.java` that instantiate and configure the Android View for both the components. 263 | - the `ManagerImpl.java` file where which contains the logic of the ViewManager that can be shared between the Legacy and the Fabric Component. 264 | - the `Package.java` file used to load the component. 265 | 266 | The final folder structure looks like this: 267 | 268 | ```sh 269 | my-component 270 | ├── android 271 | │ ├── build.gradle 272 | │ └── src 273 | │ ├── main 274 | │ │ ├── AndroidManifest.xml 275 | │ │ └── java 276 | │ │ └── com 277 | │ │ └── mycomponent 278 | │ │ ├── MyComponentView.java 279 | │ │ ├── MyComponentViewManagerImpl.java 280 | │ │ └── MyComponentViewPackage.java 281 | │ ├── newarch 282 | │ │ └── java 283 | │ │ └── com 284 | │ │ └── MyComponentViewManager.java 285 | │ └── oldarch 286 | │ └── java 287 | │ └── com 288 | │ └── MyComponentViewManager.java 289 | ├── ios 290 | ├── js 291 | └── package.json 292 | ``` 293 | 294 | The code that should go in the `MyComponentViewManagerImpl.java` and that can be shared between the Native Component and the Fabric Native Component is, for example: 295 | 296 |
297 | Java 298 | 299 | ```java title="example of MyComponentViewManager.java" 300 | package com.mycomponent; 301 | 302 | import androidx.annotation.Nullable; 303 | import com.facebook.react.uimanager.ThemedReactContext; 304 | 305 | public class MyComponentViewManagerImpl { 306 | 307 | public static final String NAME = "MyComponent"; 308 | 309 | public static MyComponentView createViewInstance(ThemedReactContext context) { 310 | return new MyComponentView(context); 311 | } 312 | 313 | public static void setFoo(MyComponentView view, String param) { 314 | // implement the logic of the foo function using the view and the param passed. 315 | } 316 | } 317 | ``` 318 | 319 |
320 | 321 |
322 | Kotlin 323 | 324 | ```kotlin title="example of MyComponentViewManager.kt" 325 | package com.mycomponent 326 | 327 | import com.facebook.react.uimanager.ThemedReactContext 328 | 329 | object MyComponentViewManagerImpl { 330 | const val NAME = "MyComponent" 331 | fun createViewInstance(context: ThemedReactContext?) = MyComponentView(context) 332 | 333 | fun setFoo(view: MyComponentView, param: String) { 334 | // implement the logic of the foo function using the view and the param passed. 335 | } 336 | } 337 | ``` 338 | 339 |
340 | 341 | Then, the Native Component and the Fabric Native Component can be updated using the function declared in the shared manager. 342 | 343 | For example, for a Native Component: 344 | 345 |
346 | Java 347 | 348 | ```java title="Native Component using the ViewManagerImpl" 349 | public class MyComponentViewManager extends SimpleViewManager { 350 | 351 | ReactApplicationContext mCallerContext; 352 | 353 | public MyComponentViewManager(ReactApplicationContext reactContext) { 354 | mCallerContext = reactContext; 355 | } 356 | 357 | @Override 358 | public String getName() { 359 | // static NAME property from the shared implementation 360 | return MyComponentViewManagerImpl.NAME; 361 | } 362 | 363 | @Override 364 | public MyComponentView createViewInstance(ThemedReactContext context) { 365 | // static createViewInstance function from the shared implementation 366 | return MyComponentViewManagerImpl.createViewInstance(context); 367 | } 368 | 369 | @ReactProp(name = "foo") 370 | public void setFoo(MyComponentView view, String param) { 371 | // static custom function from the shared implementation 372 | MyComponentViewManagerImpl.setFoo(view, param); 373 | } 374 | 375 | } 376 | ``` 377 | 378 |
379 |
380 | Kotlin 381 | 382 | ```kotlin title="Native Component using the ViewManagerImpl" 383 | class MyComponentViewManager(var context: ReactApplicationContext) : SimpleViewManager() { 384 | // Use the static NAME property from the shared implementation 385 | override fun getName() = MyComponentViewManagerImpl.NAME 386 | 387 | public override fun createViewInstance(context: ThemedReactContext): MyComponentView = 388 | // static createViewInstance function from the shared implementation 389 | MyComponentViewManagerImpl.createViewInstance(context) 390 | 391 | @ReactProp(name = "foo") 392 | fun setFoo(view: MyComponentView, param: String) { 393 | // static custom function from the shared implementation 394 | MyComponentViewManagerImpl.setFoo(view, param) 395 | } 396 | } 397 | ``` 398 | 399 |
400 | 401 | And, for a Fabric Native Component: 402 | 403 |
404 | Java 405 | 406 | ```java title="Fabric Component using the ViewManagerImpl" 407 | // Use the static NAME property from the shared implementation 408 | @ReactModule(name = MyComponentViewManagerImpl.NAME) 409 | public class MyComponentViewManager extends SimpleViewManager 410 | implements MyComponentViewManagerInterface { 411 | 412 | private final ViewManagerDelegate mDelegate; 413 | 414 | public MyComponentViewManager(ReactApplicationContext context) { 415 | mDelegate = new MyComponentViewManagerDelegate<>(this); 416 | } 417 | 418 | @Nullable 419 | @Override 420 | protected ViewManagerDelegate getDelegate() { 421 | return mDelegate; 422 | } 423 | 424 | @NonNull 425 | @Override 426 | public String getName() { 427 | // static NAME property from the shared implementation 428 | return MyComponentViewManagerImpl.NAME; 429 | } 430 | 431 | @NonNull 432 | @Override 433 | protected MyComponentView createViewInstance(@NonNull ThemedReactContext context) { 434 | // static createViewInstance function from the shared implementation 435 | return MyComponentViewManagerImpl.createViewInstance(context); 436 | } 437 | 438 | @Override 439 | @ReactProp(name = "foo") 440 | public void setFoo(MyComponentView view, @Nullable String param) { 441 | // static custom function from the shared implementation 442 | MyComponentViewManagerImpl.setFoo(view, param); 443 | } 444 | } 445 | ``` 446 | 447 |
448 |
449 | Kotlin 450 | 451 | ```kotlin title="Fabric Component using the ViewManagerImpl" 452 | // Use the static NAME property from the shared implementation 453 | @ReactModule(name = MyComponentViewManagerImpl.NAME) 454 | class MyComponentViewManager(context: ReactApplicationContext) : SimpleViewManager(), MyComponentViewManagerInterface { 455 | private val delegate: ViewManagerDelegate = MyComponentViewManagerDelegate(this) 456 | 457 | override fun getDelegate(): ViewManagerDelegate = delegate 458 | 459 | // Use the static NAME property from the shared implementation 460 | override fun getName(): String = MyComponentViewManagerImpl.NAME 461 | 462 | override fun createViewInstance(context: ThemedReactContext): MyComponentView = 463 | // static createViewInstance function from the shared implementation 464 | MyComponentViewManagerImpl.createViewInstance(context) 465 | 466 | @ReactProp(name = "foo") 467 | override fun setFoo(view: MyComponentView, text: String) { 468 | // static custom function from the shared implementation 469 | MyComponentViewManagerImpl.setFoo(view, param); 470 | } 471 | } 472 | ``` 473 | 474 |
475 | 476 | 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). 477 | 478 | ## Unify the JavaScript specs 479 | 480 | > [!Warning] 481 | > The TypeScript support for the New Architecture is still in beta. 482 | 483 | The last step makes sure that the JavaScript behaves transparently to chosen architecture. 484 | 485 | For a Fabric Native Component, the source of truth is the `NativeComponent.js` (or `.ts`) spec file. The app accesses the spec file like this: 486 | 487 | ```ts 488 | import MyComponent from "your-component/src/index"; 489 | ``` 490 | 491 | Since `codegenNativeComponent` is calling the `requireNativeComponent` under the hood, we need to re-export our component, to avoid registering it multiple times. 492 | 493 |
Flow 501 |
TypeScript 508 | 509 | --- 510 | 511 | > [!IMPORTANT] 512 | > This documentation is still experimental and details are subject to changes as we iterate. 513 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 514 | > 515 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 516 | -------------------------------------------------------------------------------- /docs/backwards-compat-turbo-modules.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Turbo Modules as Legacy Native Modules 4 | 5 | > [!Note] 6 | > Creating a backward compatible Turbo Native Module requires the knowledge of how to create a Legacy Native Module. To recall these concepts, have a look at this [guide](./turbo-modules.md). 7 | 8 | Creating a backward compatible TurboModule lets your users continue to leverage your library, independently from the architecture they use. The creation of such a module requires a few steps: 9 | 10 | 1. Configure the library so that dependencies are prepared set up properly for both the Old and the New Architecture. 11 | 1. Update the codebase so that the New Architecture types are not compiled when not available. 12 | 1. Uniform the JavaScript API so that your user code won't need changes. 13 | 14 | > [!Note] 15 | > For the sake of this guide we're going to use the following **terminology**: 16 | > 17 | > - **Legacy Native Modules** - To refer to Modules which are running on the old React Native architecture. 18 | > - **Turbo Native Modules** - To refer to Modules which have been adapted to work well with the New Native Module System. For brevity you might find them referred as **Turbo Modules**. 19 | 20 | > [!Warning] 21 | > The TypeScript support for the New Architecture is still in beta. 22 | 23 | While the last step is the same for all the platforms, the first two steps are different for iOS and Android. 24 | 25 | ## iOS 26 | ### Dependencies 27 | 28 | The Apple platform installs Turbo Native Modules using [CocoaPods](https://cocoapods.org) as a dependency manager. 29 | 30 | If you are already using the [`install_module_dependencies`](https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L198) function, then **there is nothing to do**. The function already takes care of installing the proper dependencies when the New Architecture is enabled and avoids them when it is not enabled. 31 | 32 | Otherwise, your Turbo Native Module's `podspec` should look like this: 33 | 34 | ```ruby 35 | require "json" 36 | 37 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 38 | 39 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' 40 | 41 | Pod::Spec.new do |s| 42 | # Default fields for a valid podspec 43 | s.name = "" 44 | s.version = package["version"] 45 | s.summary = package["description"] 46 | s.description = package["description"] 47 | s.homepage = package["homepage"] 48 | s.license = package["license"] 49 | s.platforms = { :ios => "11.0" } 50 | s.author = package["author"] 51 | s.source = { :git => package["repository"], :tag => "#{s.version}" } 52 | 53 | s.source_files = "ios/**/*.{h,m,mm,swift}" 54 | # React Native Core dependency 55 | s.dependency "React-Core" 56 | 57 | # The following lines are required by the New Architecture. 58 | s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" 59 | s.pod_target_xcconfig = { 60 | "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", 61 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 62 | } 63 | 64 | s.dependency "React-Codegen" 65 | s.dependency "RCT-Folly" 66 | s.dependency "RCTRequired" 67 | s.dependency "RCTTypeSafety" 68 | s.dependency "ReactCommon/turbomodule/core" 69 | 70 | end 71 | ``` 72 | 73 | You should install the extra dependencies when the New Architecture is enabled, and avoid installing them when it's not. 74 | To achieve this, you can use the [`install_modules_dependencies`](https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L198). Update the `.podspec` file as it follows: 75 | 76 | ```diff 77 | require "json" 78 | 79 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 80 | 81 | -folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' 82 | 83 | Pod::Spec.new do |s| 84 | # Default fields for a valid podspec 85 | s.name = "" 86 | s.version = package["version"] 87 | s.summary = package["description"] 88 | s.description = package["description"] 89 | s.homepage = package["homepage"] 90 | s.license = package["license"] 91 | s.platforms = { :ios => "11.0" } 92 | s.author = package["author"] 93 | s.source = { :git => package["repository"], :tag => "#{s.version}" } 94 | 95 | s.source_files = "ios/**/*.{h,m,mm,swift}" 96 | # React Native Core dependency 97 | + install_modules_dependencies(s) 98 | - s.dependency "React-Core" 99 | - 100 | - # The following lines are required by the New Architecture. 101 | - s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" 102 | - s.pod_target_xcconfig = { 103 | - "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", 104 | - "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 105 | - } 106 | - 107 | - s.dependency "React-Codegen" 108 | - s.dependency "RCT-Folly" 109 | - s.dependency "RCTRequired" 110 | - s.dependency "RCTTypeSafety" 111 | - s.dependency "ReactCommon/turbomodule/core" 112 | end 113 | ``` 114 | 115 | ### Update the codebase 116 | 117 | 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. 118 | 119 | There are two files to change. The module implementation file, which is usually a `.mm` file, and the module header, which is usually a `.h` file. 120 | 121 | That implementation file is structured as follows: 122 | 123 | - Some `#import` statements, among which there is a `.h` file. 124 | - The module implementation, using the various `RCT_EXPORT_xxx` and `RCT_REMAP_xxx` macros. 125 | - The `getTurboModule:` function, which uses the `` type, generated by The New Architecture. 126 | 127 | The **goal** is to make sure that the `Turbo Native Module` 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: 128 | 129 | ```diff 130 | #import ".h" 131 | + #ifdef RCT_NEW_ARCH_ENABLED 132 | #import ".h" 133 | + #endif 134 | 135 | // ... rest of your module 136 | 137 | + #ifdef RCT_NEW_ARCH_ENABLED 138 | - (std::shared_ptr)getTurboModule: 139 | (const facebook::react::ObjCTurboModule::InitParams &)params 140 | { 141 | return std::make_shared>(params); 142 | } 143 | + #endif 144 | 145 | @end 146 | ``` 147 | 148 | A similar thing needs to be done for the header file. Add the following lines at the bottom of your module header. You need to first import the header and then, if the New Architecture is enabled, make it conform to the Spec protocol. 149 | 150 | ```diff 151 | #import 152 | + #ifdef RCT_NEW_ARCH_ENABLED 153 | + #import 154 | + #endif 155 | 156 | @interface YourModule: NSObject 157 | 158 | @end 159 | 160 | + #ifdef RCT_NEW_ARCH_ENABLED 161 | + @interface YourModule () 162 | 163 | + @end 164 | + #endif 165 | 166 | ``` 167 | 168 | This snippets 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. 169 | 170 | ## Android 171 | ### Dependencies 172 | 173 | 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. 174 | 175 | > [!note] 176 | > 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. 177 | 178 | To configure the Turbo Native Module so that it picks the proper sourceset, you have to update the `build.gradle` file in the following way: 179 | 180 | ```diff title="build.gradle" 181 | +// Add this function in case you don't have it already 182 | + def isNewArchitectureEnabled() { 183 | + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" 184 | +} 185 | 186 | 187 | // ... other parts of the build file 188 | 189 | defaultConfig { 190 | minSdkVersion safeExtGet('minSdkVersion', 21) 191 | targetSdkVersion safeExtGet('targetSdkVersion', 31) 192 | + buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()) 193 | + } 194 | + 195 | + sourceSets { 196 | + main { 197 | + if (isNewArchitectureEnabled()) { 198 | + java.srcDirs += ['src/newarch'] 199 | + } else { 200 | + java.srcDirs += ['src/oldarch'] 201 | + } 202 | + } 203 | } 204 | } 205 | ``` 206 | 207 | This changes do three main things: 208 | 209 | 1. The first lines define a function that returns whether the New Architecture is enabled or not. 210 | 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. 211 | 3. The last lines leverage the function declared in step one to decide which source sets we need to build, depending on the chosen architecture. 212 | 213 | ### Update the codebase 214 | 215 | > [!Note] 216 | > 217 | > You can configure your library to [include codegen artifacts](https://reactnative.dev/docs/the-new-architecture/codegen-cli#including-generated-code-into-libraries). Doing this will make it easier to offer backward compatibility with old arch, but has [implications on libraries](https://reactnative.dev/docs/the-new-architecture/codegen-cli#including-generated-code-into-libraries) that you need to be aware. 218 | > 219 | > The following instructions are only for libraries that chose to rely on the app-level codegen. 220 | 221 | 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 Turbo Native Module with the proper source that is loaded and compiled depending on the used architecture. 222 | 223 | Therefore, you have to: 224 | 225 | 1. Create a Legacy Native Module in the `src/oldarch` path. See [this guide](https://reactnative.dev/docs/native-modules-intro) to learn how to create a Legacy Native Module. 226 | 2. Create a Turbo Native Module in the `src/newarch` path. See [this guide](./turbo-modules.md) to learn how to create a Turbo Native Module 227 | 228 | and then instruct Gradle to decide which implementation to pick. 229 | 230 | Some files can be shared between a Legacy Native Module and a Turbo Native Module: these should be created or moved into a folder that is loaded by both the architectures. These files are: 231 | 232 | - the `Package.java` file used to load the module. 233 | - a `Impl.java` file where we can put the code that both the Legacy Native Module and the Turbo Native Module has to execute. 234 | 235 | The final folder structure looks like this: 236 | 237 | ```sh 238 | my-module 239 | ├── android 240 | │ ├── build.gradle 241 | │ └── src 242 | │ ├── main 243 | │ │ ├── AndroidManifest.xml 244 | │ │ └── java 245 | │ │ └── com 246 | │ │ └── mymodule 247 | │ │ ├── MyModuleImpl.java 248 | │ │ └── MyModulePackage.java 249 | │ ├── newarch 250 | │ │ └── java 251 | │ │ └── com 252 | │ │ └── MyModule.java 253 | │ └── oldarch 254 | │ └── java 255 | │ └── com 256 | │ └── MyModule.java 257 | ├── ios 258 | ├── js 259 | └── package.json 260 | ``` 261 | 262 | The code that should go in the `MyModuleImpl.java`, and that can be shared by the Legacy Native Module and the Turbo Native Module is, for example: 263 | 264 |
265 | Java 266 | 267 | ```java title="example of MyModuleImpl.java" 268 | package com.mymodule; 269 | 270 | import androidx.annotation.NonNull; 271 | import com.facebook.react.bridge.Promise; 272 | import java.util.Map; 273 | import java.util.HashMap; 274 | 275 | public class MyModuleImpl { 276 | 277 | public static final String NAME = "MyModule"; 278 | 279 | public void foo(double a, double b, Promise promise) { 280 | // implement the logic for foo and then invoke promise.resolve or 281 | // promise.reject. 282 | } 283 | } 284 | ``` 285 | 286 |
287 |
288 | Kotlin 289 | 290 | ```kotlin title="example of MyModuleImpl.kt" 291 | package com.mymodule; 292 | 293 | import com.facebook.react.bridge.Promise 294 | 295 | class MyModuleImpl { 296 | fun foo(a: Double, b: Double, promise: Promise) { 297 | // implement the logic for foo and then invoke 298 | // promise.resolve or promise.reject. 299 | } 300 | 301 | companion object { 302 | const val NAME = "MyModule" 303 | } 304 | } 305 | ``` 306 | 307 |
308 | 309 | Then, the Legacy Native Module and the Turbo Native Module can be updated with the following steps: 310 | 311 | 1. Create a private instance of the `MyModuleImpl` class. 312 | 2. Initialize the instance in the module constructor. 313 | 3. Use the private instance in the modules methods. 314 | 315 | For example, for a Legacy Native Module: 316 | 317 |
318 | Java 319 | 320 | ```java title="Native Module using the Impl module" 321 | public class MyModule extends ReactContextBaseJavaModule { 322 | 323 | // declare an instance of the implementation 324 | private MyModuleImpl implementation; 325 | 326 | MyModule(ReactApplicationContext context) { 327 | super(context); 328 | // initialize the implementation of the module 329 | implementation = MyModuleImpl(); 330 | } 331 | 332 | @Override 333 | public String getName() { 334 | // NAME is a static variable, so we can access it using the class name. 335 | return MyModuleImpl.NAME; 336 | } 337 | 338 | @ReactMethod 339 | public void foo(int a, int b, Promise promise) { 340 | // Use the implementation instance to execute the function. 341 | implementation.foo(a, b, promise); 342 | } 343 | } 344 | ``` 345 | 346 |
347 |
348 | Kotlin 349 | 350 | ```kotlin title="Native Module using the Impl module" 351 | class MyModule(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) { 352 | // declare an instance of the implementation and use it in all the methods 353 | private var implementation: MyModuleImpl = MyModuleImpl() 354 | 355 | override fun getName(): String = MyModuleImpl.NAME 356 | 357 | @ReactMethod 358 | fun foo(a: Double, b: Double, promise: Promise) { 359 | // Use the implementation instance to execute the function. 360 | implementation.foo(a, b, promise) 361 | } 362 | } 363 | ``` 364 | 365 |
366 | 367 | And, for a Turbo Native Module: 368 | 369 |
370 | Java 371 | 372 | ```java title="TurboModule using the Impl module" 373 | public class MyModule extends MyModuleSpec { 374 | // declare an instance of the implementation 375 | private MyModuleImpl implementation; 376 | 377 | MyModule(ReactApplicationContext context) { 378 | super(context); 379 | // initialize the implementation of the module 380 | implementation = MyModuleImpl(); 381 | } 382 | 383 | @Override 384 | @NonNull 385 | public String getName() { 386 | // NAME is a static variable, so we can access it using the class name. 387 | return MyModuleImpl.NAME; 388 | } 389 | 390 | @Override 391 | public void foo(double a, double b, Promise promise) { 392 | // Use the implementation instance to execute the function. 393 | implementation.foo(a, b, promise); 394 | } 395 | 396 | } 397 | 398 | ``` 399 | 400 |
401 |
402 | Kotlin 403 | 404 | ```kotlin title="TurboModule using the Impl module" 405 | class MyModule(reactContext: ReactApplicationContext) : MyModuleSpec(reactContext) { 406 | // declare an instance of the implementation and use it in all the methods 407 | private var implementation: MyModuleImpl = MyModuleImpl() 408 | 409 | override fun getName(): String = MyModuleImpl.NAME 410 | 411 | override fun foo(a: Double, b: Double, promise: Promise) { 412 | // Use the implementation instance to execute the function. 413 | implementation.foo(a, b, promise) 414 | } 415 | } 416 | ``` 417 | 418 |
419 | 420 | 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). 421 | 422 | ## Unify the JavaScript specs 423 | 424 | > [!Warning] 425 | > The TypeScript support for the New Architecture is still in beta. 426 | 427 | The last step makes sure that the JavaScript behaves transparently to chosen architecture. 428 | 429 | For a Turbo Native Module, the source of truth is the `Native.js` (or `.ts`) spec file. The app accesses the spec file like this: 430 | 431 | ```ts 432 | import MyModule from "your-module/src/index"; 433 | ``` 434 | 435 | Since `TurboModuleRegistry.get` taps into the old Native Modules API under the hood, we need to re-export our module, to avoid registering it multiple times. 436 | 437 |
438 | Flow 439 | 440 | ```ts 441 | // @flow 442 | export default require("./Native").default; 443 | ``` 444 | 445 |
446 |
447 | TypeScript 448 | 449 | ```ts 450 | export default require("./Native").default; 451 | ``` 452 | 453 |
454 | 455 | --- 456 | 457 | > [!IMPORTANT] 458 | > This documentation is still experimental and details are subject to changes as we iterate. 459 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 460 | > 461 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 462 | -------------------------------------------------------------------------------- /docs/backwards-compat.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Backwards Compatibility 4 | 5 | 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. 6 | 7 | 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 issuing one different command. 8 | 9 | To achieve this result, we have to perform few changes in our **Turbo Native Module** and **Fabric Native Component** configurations. The steps we have to follow are: 10 | 11 | 1. **Update the installation configuration** to avoid using code that is not needed by the Old Architecture. 12 | 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. 13 | 1. **Configure the specs to load the proper implementation**, so that the JavaScript layer leverages the New Architecture when it is available. 14 | 15 | > [!Note] 16 | > The next sections requires that you are familiar with the [Turbo Modules](./turbo-modules.md) and [Fabric Native Components](./fabric-native-components.md). 17 | 18 | - To create a backward compatible **Turbo Native Module**, follow [this guide](./backwards-compat-turbo-modules.md). 19 | - To create a backward compatible **Fabric Native Component**, follow [this guide](./backwards-compat-fabric-component.md). 20 | 21 | --- 22 | 23 | > [!IMPORTANT] 24 | > This documentation is still experimental and details are subject to changes as we iterate. 25 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 26 | > 27 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 28 | -------------------------------------------------------------------------------- /docs/codegen.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Using Codegen 4 | 5 | **Codegen** is a tool that can be used to avoid writing a lot of repetitive code. Using **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. 6 | 7 | The **Codegen** is invoked automatically by React Native every time an iOS or 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 [**Turbo Native Modules**](./turbo-modules.md) and [**Fabric Native Components**](./fabric-native-components.md), for example. 8 | 9 | This guide teaches how to configure the **Codegen**, and how to invoke it manually for each platform, and describes the generated code. 10 | 11 | # Prerequisites 12 | 13 | You always need a React Native app to generate the code properly, even when invoking the **Codegen** manually. 14 | 15 | The **Codegen** process is tightly coupled with the build of the app, and the scripts are located in the `react-native` NPM package. 16 | 17 | For the sake of this guide, create a project using the React Native CLI as follows: 18 | 19 | ```sh 20 | npx react-native init SampleApp --version 0.70.0 21 | ``` 22 | 23 | > [!Note] 24 | > This guide assumes that the React Native version in use is 0.70.0 or greater. 25 | > Previous versions of React Native uses a version of **Codegen** that requires a slightly different setup. 26 | 27 | Then, add the module that requires the **Codegen** as an NPM dependency of the app: 28 | 29 | ```sh 30 | yarn add 31 | ``` 32 | 33 | See how to create a [Turbo Native Module](./turbo-modules.md) or a [Fabric Native Component](./fabric-native-components.md) to get more information on how to configure them. 34 | 35 | The rest of this guide assumes that you have a `Turbo Native Module` and/or a `Fabric Native Component` properly set up. 36 | 37 | # iOS 38 | 39 | ## Running the Codegen 40 | 41 | 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. 42 | 43 | The script that you have to run is the `generate-codegen-artifacts.js` script. This searches among all the dependencies of the app, looking for JS files that respects some specific conventions (look at [TurboModules](./turbo-modules.md) and [Fabric Components](./fabric-native-components.md) sections for details), and it generates the required code. 44 | 45 | To invoke the script, you can run this command from the root folder of your app: 46 | 47 | ```sh 48 | node node_modules/react-native/scripts/generate-codegen-artifacts.js \ 49 | --path SampleApp/ \ 50 | --outputPath \ 51 | ``` 52 | 53 | Given that the app has `Turbo Native Modules` and/or `Fabric Native Components` configured as a dependency, **Codegen** looks for all of them and generates the code in the path you provided. 54 | 55 | ## The Generated Code 56 | 57 | If you run the **Codegen** in your app with an output path of `codegen`, for example, you obtain the following structure: 58 | 59 | ```title="iOS Codegen output" 60 | codegen 61 | └── build 62 | └── generated 63 | └── ios 64 | ├── MyTurboModuleSpecs 65 | │ ├── MyTurboModuleSpecs-generated.mm 66 | │ └── MyTurboModuleSpecs.h 67 | ├── FBReactNativeSpec 68 | │ ├── FBReactNativeSpec-generated.mm 69 | │ └── FBReactNativeSpec.h 70 | ├── RCTThirdPartyFabricComponentsProvider.h 71 | ├── RCTThirdPartyFabricComponentsProvider.mm 72 | └── react 73 | └── renderer 74 | └── components 75 | ├── MyFabricComponent 76 | │ ├── ComponentDescriptors.h 77 | │ ├── EventEmitters.cpp 78 | │ ├── EventEmitters.h 79 | │ ├── Props.cpp 80 | │ ├── Props.h 81 | │ ├── RCTComponentViewHelpers.h 82 | │ ├── ShadowNodes.cpp 83 | │ └── ShadowNodes.h 84 | └── rncore 85 | ├── ComponentDescriptors.h 86 | ├── EventEmitters.cpp 87 | ├── EventEmitters.h 88 | ├── Props.cpp 89 | ├── Props.h 90 | ├── RCTComponentViewHelpers.h 91 | ├── ShadowNodes.cpp 92 | └── ShadowNodes.h 93 | ``` 94 | 95 | The `codegen` folder sits at the root of the hierarchy, as expected. Nested into it, there are two more folders: `build/generated`. 96 | 97 | Then, there is an `ios` folder that contains: 98 | 99 | - A custom folder for each TurboModule. 100 | - The header (`.h`) and implementation (`.mm`) files for the `RCTThirdPartyFabricComponentsProvider`. 101 | - A base `react/renderer/components` folder which contains a custom folder for each `Fabric Native Component`. 102 | 103 | In the example above, there are both a TurboModule and a set of Fabric Native 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 Native Component: React Native requires them in order to work properly. 104 | 105 | ### Turbo Native Modules 106 | 107 | Each folder contains two files: an interface file and an implementation file. 108 | 109 | The interface files have the same name as that of the Turbo Native Module and contain methods to initialize the JSI interface. 110 | 111 | The implementation files, instead, have the `-generated` suffix and contain the logic to invoke the native methods from JS and vice-versa. 112 | 113 | ### Fabric Native Components 114 | 115 | The content of each Fabric Native Component folder contains several files. The basic element for a Fabric Native Component is the `ShadowNode`: it represents a node in the React abstract 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. 116 | 117 | 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 Fabric Native Component, while the latter contains some helper methods and protocols that can be implemented by the Native View to properly respond to JSI invocations. 118 | 119 | For further details about how Fabric works, have a look at the [Renderer](/architecture/fabric-renderer) section. 120 | 121 | ### RCTThirdPartyFabricComponentsProvider 122 | 123 | These are interface and implementation files for a registry. React Native uses this registry at runtime to retrieve the right class for a required Fabric Native Component. Once React Native has a handle to that class, it can instantiate it. 124 | 125 | # Android 126 | 127 | ## Running the Codegen 128 | 129 | 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. 130 | 131 | 1. Open the `MyApp/android/gradle.properties` file. 132 | 1. Flip the `newArchEnabled` flag from `false` to `true`. 133 | 134 | After that, you can navigate into the `SampleApp/android` folder and run: 135 | 136 | ```sh 137 | ./gradlew generateCodegenArtifactsFromSchema 138 | ``` 139 | 140 | These tasks invoke 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 Native 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. 141 | 142 | ## The Generated Code 143 | 144 | Once the Gradle task completes, you can see different structures for a Turbo Native Module or for a Fabric Native Component. Expand the following to see how they appear: 145 | 146 |
147 | Turbo Native Module 148 | 149 | ```sh 150 | codegen 151 | ├── java 152 | │ └── com 153 | │ └── MyTurbomodule 154 | │ └── MyTurbomodule.java 155 | ├── jni 156 | │ ├── Android.mk 157 | │ ├── CMakeLists.txt 158 | │ ├── MyTurbomodule-generated.cpp 159 | │ ├── MyTurbomodule.h 160 | │ └── react 161 | │ └── renderer 162 | │ └── components 163 | │ └── MyTurbomodule 164 | │ ├── ComponentDescriptors.h 165 | │ ├── EventEmitters.cpp 166 | │ ├── EventEmitters.h 167 | │ ├── Props.cpp 168 | │ ├── Props.h 169 | │ ├── ShadowNodes.cpp 170 | │ └── ShadowNodes.h 171 | └── schema.json 172 | ``` 173 | 174 |
175 |
176 | Fabric Native Component 177 | 178 | ```sh 179 | codegen 180 | ├── java 181 | │ └── com 182 | │ └── facebook 183 | │ └── react 184 | │ └── viewmanagers 185 | │ ├── MyFabricComponentManagerDelegate.java 186 | │ └── MyFabricComponentManagerInterface.java 187 | ├── jni 188 | │ ├── Android.mk 189 | │ ├── CMakeLists.txt 190 | │ ├── MyFabricComponent-generated.cpp 191 | │ ├── MyFabricComponent.h 192 | │ └── react 193 | │ └── renderer 194 | │ └── components 195 | │ └── MyFabricComponent 196 | │ ├── ComponentDescriptors.h 197 | │ ├── EventEmitters.cpp 198 | │ ├── EventEmitters.h 199 | │ ├── Props.cpp 200 | │ ├── Props.h 201 | │ ├── ShadowNodes.cpp 202 | │ └── ShadowNodes.h 203 | └── schema.json 204 | ``` 205 | 206 |
207 | 208 | Java can't interoperate seamlessly with C++ as Objective-C++ does. To work properly, **Codegen** creates some bridging between the Java and the C++ world in the `jni` folder, where the Java Native Interfaces are defined. 209 | 210 | Notice that both Turbo Native Modules and Fabric Native Components come 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. 211 | 212 | ### Turbo Native Module 213 | 214 | The **Codegen** generates a Java abstract class in the `java` package with the same name as that of the TurboModule. This abstract class has to be implemented by the JNI C++ implementation. 215 | 216 | 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 Native 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 vice-versa. 217 | 218 | ### Fabric Native Component 219 | 220 | The **Codegen** for a Fabric Native 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 Native Component](./fabric-native-components.md) for details). 221 | 222 | Then, there is a layer of JNI C++ files that are used by Fabric to render the components. The basic element for a Fabric Component is the `ShadowNode`: it represents a node in the React abstract 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. 223 | 224 | The **Codegen** also creates a `ComponentDescriptor.h`, which is required to get a proper handle on the Fabric Native Component. 225 | 226 | ## The Codegen CLI 227 | 228 | **npx react-native codegen** [**--path** *path*] [**--platform** *string*] [**--outputPath** *path*] 229 | 230 | #### DESCRIPTION 231 | 232 | This command runs `react-native-codegen` for your project. 233 | The Codegen must be configured as described in [Configure Codegen](./enable-libraries-prerequisites.md#configure-codegen). 234 | 235 | The following options are available: 236 | 237 | - `--path` - Path to `package.json`. The default path is the current working directory. 238 | - `--platform` - Target platform. Supported values: `android`, `ios`, `all`. The default value is `all`. 239 | - `--outputPath` - Output path. The default value is the value defined in `codegenCofig.outputDir`. 240 | 241 | #### EXAMPLES 242 | 243 | - Read `package.json` from the current working directory, generate code based on its `codegenConfig`. 244 | 245 | ```sh 246 | npx react-native codegen 247 | ``` 248 | 249 | - Read `package.json` from the current working directory, generate iOS code in the location defined in the `codegenConfig`. 250 | 251 | ```sh 252 | npx react-native codegen --platform ios 253 | ``` 254 | 255 | - Read `package.json` from `third-party/some-library`, generate Android code in `third-party/some-library/android/generated`. 256 | 257 | ```sh 258 | npx react-native codegen \ 259 | --path third-party/some-library \ 260 | --platform android \ 261 | --outputPath third-party/some-library/android/generated 262 | ``` 263 | 264 | ## Including Generated Code into Libraries 265 | 266 | If you are working on a library, you can include Codegen artefacts in it. This setup has a number of benefits: 267 | 268 | - No need to rely on the app to run Codegen for you, the generated code is always there. 269 | - The implementation files are always consistent with the generated interfaces. 270 | - No need to [include two sets of files](backward-compatibility-turbomodules#android-1) to support the Old and the New Architecture on Android. You can only keep the New Architecture one, and it is guaranteed to be backwards compatible. 271 | - No need to worry about Codegen version mismatch between what is used by the app, and what was used during library development. 272 | - Since all native code is there, it is possible to ship the native part of the library as a prebuild. 273 | 274 | Here is how you can enable this setup: 275 | 276 | - Define `includesGeneratedCode: true` and `outputDir` in the `codegenConfig`, as described in [Configure Codegen](./enable-libraries-prerequisites.md#configure-codegen). 277 | - Run Codegen locally with [the codegen CLI](./codegen.md#the-codegen-cli). 278 | - Update your `package.json` to include the generated code. 279 | - Update your [podspec](./turbo-modules.md#ios-create-the-podspec-file) to include the generated code. 280 | - Update your [build.gradle file](./turbo-modules.md#the-buildgradle-file) to include the generated code. 281 | - Update `cmakeListsPath` in `react-native.config.js` ([example](https://github.com/facebook/react-native/blob/3c17beafe387fb4966055d5dfcec62bef537e0f7/packages/react-native-popup-menu-android/react-native.config.js#L12)) so that Gradle doesn't look for `CMakeLists` file in the `build` directory but instead in your `outputDir`. 282 | 283 | --- 284 | 285 | > [!IMPORTANT] 286 | > This documentation is still experimental and details are subject to changes as we iterate. 287 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 288 | > 289 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 290 | -------------------------------------------------------------------------------- /docs/cxx-custom-types.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Supporting Custom C++ Types 4 | 5 | By default C++ Turbo Native Modules support [bridging functionality](https://github.com/facebook/react-native/tree/main/packages/react-native/ReactCommon/react/bridging) for most `std::` standard types. 6 | 7 | If you want to add support for new / custom types in your app / library, you only need to provide the necessary `bridging` header file. 8 | 9 | This guide continues the previous [C++ Turbo Native Modules](./turbo-modules-xplat.md) section. 10 | 11 | ## Example: Int64 12 | 13 | C++ Turbo Native Modules don't support `int64_t` numbers yet - because JavaScript doesn't support numbers greater `2^53`. 14 | 15 | We can't represent numbers > `2^53` as JavaScript `number`'s - but we can represent them as JavaScript `string`'s and automatically convert (aka. `bridge`) them to C++ `int64_t`'s by creating a custom Bridging header file called `Int64.h` in the `tm` folder: 16 | 17 | ```cpp Int64.h 18 | #pragma once 19 | 20 | #include 21 | 22 | namespace facebook::react { 23 | 24 | template <> 25 | struct Bridging { 26 | static int64_t fromJs(jsi::Runtime &rt, const jsi::String &value) { 27 | try { 28 | size_t pos; 29 | auto str = value.utf8(rt); 30 | auto num = std::stoll(str, &pos); 31 | if (pos != str.size()) { 32 | throw std::invalid_argument("Invalid number"); // don't support alphanumeric strings 33 | } 34 | return num; 35 | } catch (const std::logic_error &e) { 36 | throw jsi::JSError(rt, e.what()); 37 | } 38 | } 39 | 40 | static jsi::String toJs(jsi::Runtime &rt, int64_t value) { 41 | return bridging::toJs(rt, std::to_string(value)); 42 | } 43 | }; 44 | 45 | } // namespace facebook::react 46 | ``` 47 | 48 | The key components for your custom `bridging` header are: 49 | 50 | - Explicit specialization of the `Bridging` struct for your custom type, `int64_t` in this case 51 | - A `fromJs` function to convert from `jsi::` types to your custom type 52 | - A `toJS` function to convert from your custom type to `jsi:` types 53 | 54 | Omitting either `fromJS` or `toJS` would make you `bridging` header either _readonly_ or _writeonly_. 55 | 56 | Now you can add the following function to your JavaScript spec: 57 | 58 |
59 | TypeScript 60 | 61 | ```typescript title="NativeSampleModule.ts" 62 | // ... 63 | readonly cubicRoot: (input: string) => number; 64 | // .. 65 | ``` 66 | 67 |
68 |
69 | Flow 70 | 71 | ```js title="NativeSampleModule.js" 72 | // ... 73 | +cubicRoot: (input: string) => number; 74 | // .. 75 | ``` 76 | 77 |
78 | 79 | Declare it in your `NativeSampleModule.h` file and include the `Int64.h` header file: 80 | 81 | ```cpp 82 | //... 83 | #include "Int64.h" 84 | //... 85 | int32_t cubicRoot(jsi::Runtime& rt, int64_t input); 86 | ``` 87 | 88 | And implement it in `NativeSampleModule.cpp`: 89 | 90 | ```cpp 91 | //... 92 | #include 93 | //... 94 | int32_t NativeSampleModule::cubicRoot(jsi::Runtime& rt, int64_t input) { 95 | return std::cbrt(input); 96 | } 97 | ``` 98 | 99 | In your app you can call this new native function via: 100 | 101 | ```js 102 |
103 | NativeSampleModule.cubicRoot(...) ={" "} 104 | {JSON.stringify(NativeSampleModule.cubicRoot("9223372036854775807"))} 105 |
106 | ``` 107 | 108 | which should return `2097152`. 109 | 110 | ## Any custom type 111 | 112 | Similar to the example above you can now write custom `bridging` functionality for any custom C++ type you want to expose to react-native. E.g., you can add support for `folly::StringPiece`, `QString`, `boost::filesystem::path`, `absl::optional` or any other type you need to support in your C++ Turbo Native Modules. 113 | 114 | ```cpp title="Path.h" 115 | #pragma once 116 | 117 | #include 118 | #include 119 | 120 | namespace facebook::react { 121 | 122 | template<> 123 | struct Bridging { 124 | static boost::filesystem::path fromJs(jsi::Runtime& rt, const std::string& value) { // auto-bridge from jsi::String to std::string 125 | return boost::filesystem::path(value); 126 | } 127 | 128 | static jsi::String toJs(jsi::Runtime& rt, boost::filesystem::path value) { 129 | return bridging::toJs(rt, value.string()); 130 | } 131 | }; 132 | 133 | } // namespace facebook::react 134 | ``` 135 | 136 | ## Custom structs 137 | 138 | You can use the same approach for you custom types in JavaScript such as this one: 139 | 140 | ```js 141 | export type CustomType = { 142 | key: string, 143 | enabled: boolean, 144 | time?: number, 145 | }; 146 | ``` 147 | 148 | which can be exposed to your C++ Turbo Native Module via 149 | 150 |
151 | TypeScript 152 | 153 | ```typescript title="NativeSampleModule.ts" 154 | // ... 155 | readonly passCustomType: (input: CustomType) => CustomType; 156 | // .. 157 | ``` 158 | 159 |
160 |
161 | Flow 162 | 163 | ```js title="NativeSampleModule.js" 164 | // ... 165 | +passCustomType: (input: CustomType) => CustomType; 166 | // .. 167 | ``` 168 | 169 |
170 | 171 | ### Manually typed 172 | 173 | To use this custom type in C++, you need to define your custom Struct and `bridging` function e.g., directly in `NativeSampleModule.h`: 174 | 175 | ```cpp 176 | struct CustomType { 177 | std::string key; 178 | bool enabled; 179 | std::optional time; 180 | }; 181 | 182 | template <> 183 | struct Bridging { 184 | static CustomType fromJs( 185 | jsi::Runtime &rt, 186 | const jsi::Object &value, 187 | const std::shared_ptr &jsInvoker) { 188 | return CustomType{ 189 | bridging::fromJs( 190 | rt, value.getProperty(rt, "key"), jsInvoker), 191 | bridging::fromJs( 192 | rt, value.getProperty(rt, "enabled"), jsInvoker), 193 | bridging::fromJs>( 194 | rt, value.getProperty(rt, "time"), jsInvoker)}; 195 | } 196 | 197 | static jsi::Object toJs(jsi::Runtime &rt, const CustomType &value) { 198 | auto result = facebook::jsi::Object(rt); 199 | result.setProperty(rt, "key", bridging::toJs(rt, value.key)); 200 | result.setProperty(rt, "enabled", bridging::toJs(rt, value.enabled)); 201 | if (value.time) { 202 | result.setProperty(rt, "time", bridging::toJs(rt, value.time.value())); 203 | } 204 | return result; 205 | } 206 | }; 207 | ``` 208 | 209 | Declare it in your `NativeSampleModule.h` file: 210 | 211 | ```cpp 212 | CustomType passCustomType(jsi::Runtime& rt, CustomType input); 213 | ``` 214 | 215 | Implement it in `NativeSampleModule.cpp`: 216 | 217 | ```cpp 218 | CustomType NativeSampleModule::passCustomType(jsi::Runtime& rt, CustomType input) { 219 | input.key = "1909"; 220 | input.enabled = !input.enabled; 221 | input.time = 42; 222 | return input; 223 | } 224 | ``` 225 | 226 | In your app you can call this new native function via: 227 | 228 | ```js 229 |
230 | NativeSampleModule.passCustomType(...) ={" "} 231 | {JSON.stringify( 232 | NativeSampleModule.passCustomType({ 233 | key: "123", 234 | enabled: true, 235 | time: undefined, 236 | }) 237 | )} 238 |
239 | ``` 240 | 241 | which should return `{"key":"1909","enabled":false","time":42}`. 242 | 243 | This works - but is quite complex. 244 | 245 | ### Struct generator 246 | 247 | [**Codegen**](./codegen.md) for C++ Turbo Native Modules does support struct generators, so you can simplify the code above in `NativeSampleModule.h` to: 248 | 249 | ```cpp 250 | using CustomType = NativeSampleModuleCustomType>; 251 | template <> 252 | struct Bridging 253 | : NativeSampleModuleCustomTypeBridging {}; 254 | ``` 255 | 256 | With `using CustomType` you declare a name for your concrete struct. 257 | 258 | #### Member types 259 | 260 | With `std::string, bool, std::optional` you define the property types of the struct members in the order they were defined in your JavaScript spec. The **order matters**. The _1st_ template argument refers to the _1st_ data type of the struct, and so forth. 261 | 262 | Without any custom conversion functions: 263 | 264 | - you can only `bridge` a JS string to a [std::string](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/bridging/AString.h) and a JS boolean to a [bool](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/bridging/Bool.h). 265 | - but you can choose different JS `number` [representations in C++](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/bridging/Number.h). 266 | 267 | #### Base class 268 | 269 | `NativeSampleModuleCustomType` is an auto-generated template in your `AppSpecsJSI.h` which name is generated by: 270 | 271 | - `NativeSampleModule` (name of C++ Turbo Native Module in the JavaScript spec) + 272 | - `CustomType` (name of type in the JavaScript spec) 273 | 274 | The same naming schema applies to the necessary `Bridging` struct which is defined via `struct Bridging`. 275 | 276 | --- 277 | 278 | > [!IMPORTANT] 279 | > This documentation is still experimental and details are subject to changes as we iterate. 280 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 281 | > 282 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 283 | -------------------------------------------------------------------------------- /docs/enable-apps.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Enable the New Architecture for Apps 4 | 5 | This page will help you create or migrate a React Native app that uses the New Architecture. 6 | 7 | > [!WARNING] 8 | > If you're using Expo, refer to [docs.expo.dev/guides/new-architecture](https://docs.expo.dev/guides/new-architecture/). 9 | 10 | ### Prerequisites 11 | 12 | 1. Use or upgrade to the latest React Native version. This guide is written with the expectation that you’re using the [**latest** React Native release](https://github.com/facebook/react-native/releases/latest). 13 | 2. If you previously installed a global `react-native-cli` package, please remove it as it may cause unexpected issues: 14 | 15 | ```shell 16 | npm uninstall -g react-native-cli @react-native-community/cli 17 | ``` 18 | 19 | 3. Enable Hermes. If you are using React Native 0.70 or above, it is already the [default JS engine](https://reactnative.dev/blog/2022/07/08/hermes-as-the-default) so no action is needed. 20 | 21 | ### Create a React Native app 22 | 23 | If you are creating a new app to enable the New Architecture, follow all the steps in [Setting up the development environment](https://reactnative.dev/docs/environment-setup) section under the **React Native CLI Quickstart** tab. 24 | 25 | If following the setup guide, stop when you reach the section **Running your React Native Application**, and resume following this guide. 26 | 27 | Then, create a new React Native project from the template: 28 | 29 | ```shell 30 | npx react-native@latest init AwesomeProject --skip-install 31 | cd AwesomeProject 32 | yarn install 33 | ``` 34 | 35 | ### Enable New Architecture for iOS 36 | 37 | #### For new apps created from React Native CLI 38 | 39 | Navigate to the `ios` directory and run the following: 40 | 41 | ```shell 42 | # from `ios` directory 43 | bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install 44 | ``` 45 | 46 | Then build and run the app as usual: 47 | 48 | ```shell 49 | yarn ios 50 | ``` 51 | 52 | #### For existing apps 53 | 54 | You'll need to reinstall your pods by running `pod install` with the right flag: 55 | 56 | ```shell 57 | # Run pod install with the flag: 58 | RCT_NEW_ARCH_ENABLED=1 bundle exec pod install 59 | ``` 60 | 61 | #### Troubleshooting 62 | 63 | ##### Run `pod install` when a dependency with native code changes 64 | 65 | 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: 66 | 67 | ``` 68 | "scripts": { 69 | "pod-install": "cd ios && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install" 70 | } 71 | ``` 72 | 73 | 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. 74 | 75 | ##### Use Xcode to rename files in the `ios` folder 76 | 77 | Whenever you have to rename some files in the `ios` folder, please **use Xcode to rename them**. This ensure that the file references are updated in the Xcode project as well. You might need to clean the build folder (**Project** → **Clean Build Folder** or Cmd ⌘ + Shift ⇪ + K) before re-building the app. If the file is renamed outside of Xcode, you may need to click on the old `.m` file reference and Locate the new file. 78 | 79 | ##### `react-native run-ios` fails 80 | 81 | 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: 82 | 83 | 1. Open the project `ios/project.xcworkspace` in Xcode 84 | 2. In XCode, choose Product > Clean Build Folder 85 | 3. In the project directory, remove the `ios/Podfile.lock` file and `ios/Pods` directory: `rm -rf ios/Podfile.lock ios/Pods` 86 | 4. Re-run `yarn pod-install` and `yarn ios` 87 | 88 | ### Enable the New Architecture on Android 89 | 90 | > [!NOTE] 91 | > 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](https://reactnative.dev/docs/build-speed). 92 | 93 | #### For new apps created from React Native CLI 94 | 95 | Set the `newArchEnabled` property to `true` by **either**: 96 | 97 | - Changing the corresponding line in `android/gradle.properties` 98 | - Setting the environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` 99 | 100 | Then build and run the app as usual: 101 | 102 | ```shell 103 | yarn android 104 | ``` 105 | 106 | #### For existing apps 107 | 108 | You will only need to update your `android/gradle.properties` file as follows: 109 | 110 | ```diff 111 | # Use this property to enable support to the new architecture. 112 | # This will allow you to use TurboModules and the Fabric render in 113 | # your application. You should enable this flag either if you want 114 | # to write custom TurboModules/Fabric components OR use libraries that 115 | # are providing them. 116 | -newArchEnabled=false 117 | +newArchEnabled=true 118 | ``` 119 | 120 | ### Verify the New Architecture is in Use 121 | 122 | After you build and run the app when Metro serves the JavaScript bundle, you should see `"fabric": true` in the Metro logs: 123 | 124 | Metro shows fabric: true 125 | 126 | ### [Advanced] Handling custom Native Components 127 | 128 | If you followed the previous steps but your app uses some custom Native Components that have not been migrated to the New Architecture completely, you are going to see some reddish/pinkish boxes saying that the component is not compatible with Fabric. This is happening because custom Native Components written for the legacy architecture can't run as-is in the New Architecture. 129 | 130 | Starting from **React Native 0.72.0**, we worked on a interoperability layer to let you use legacy components in the New Architecture without having to wait for them to be migrated. 131 | 132 | You can read more about the interoperability layer and how to use it [here](https://github.com/reactwg/react-native-new-architecture/discussions/135). Follow the guide to register your components and then rerun your app with the commands with either `npm` or `yarn` 133 | 134 | ```shell 135 | # To run android 136 | npm/yarn run android 137 | 138 | # To run iOS 139 | npm/yarn run ios 140 | ``` 141 | 142 | ### Want to Know More? 143 | 144 | If you'd like to view the code changes relevant to 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. 145 | 146 | --- 147 | 148 | > [!IMPORTANT] 149 | > This documentation is still experimental and details are subject to changes as we iterate. 150 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 151 | > 152 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 153 | -------------------------------------------------------------------------------- /docs/enable-libraries-android.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Enable the New Architecture for Libraries: Android 4 | 5 | Once you have defined the JavaScript specs for your native modules as part of the [prerequisites](./enable-libraries-prerequisites.md), set up the configuration of the Codegen, and follow the Android/Gradle setup, you are now ready to migrate your library to the new architecture. Here are the steps you can follow to accomplish this. 6 | 7 | ## 1. Extend or Implement the Code-generated Native Interfaces 8 | 9 | The JavaScript spec for your native module or component will be used to generate native interface code for each supported platform (i.e., Android and iOS). These native interface files will be generated **when a React Native application that depends on your library is built**. 10 | 11 | While this generated native interface code **will not ship as part of your library**, you do need to make sure your Java/Kotlin code conforms to the protocols provided by these native interface files. 12 | 13 | You can invoke the `generateCodegenArtifactsFromSchema` Gradle task to generate your library’s native interface code in order to use them **as a reference:** 14 | 15 | ```bash 16 | ./gradlew generateCodegenArtifactsFromSchema 17 | ``` 18 | 19 | The files that are output can be found inside `build/generated/source/codegen` and **should not be committed**, but you’ll need to refer to them to determine what changes you need to make to your native modules in order for them to provide an implementation for each generated interface. 20 | 21 | The output of the Codegen for a module called `NativeAwesomeManager` will look like this: 22 | 23 | ``` 24 | app/build/generated/source/codegen 25 | ├── java 26 | │ └── com 27 | │ └── example 28 | │ └── samplelibrary 29 | │ └── NativeAwesomeManagerSpec.java 30 | ├── jni 31 | │ ├── Android.mk 32 | │ ├── CMakeLists.txt 33 | │ ├── react 34 | │ │ └── renderer 35 | │ │ └── components 36 | │ │ └── samplelibrary 37 | │ │ ├── ComponentDescriptors.h 38 | │ │ ├── EventEmitters.cpp 39 | │ │ ├── EventEmitters.h 40 | │ │ ├── Props.cpp 41 | │ │ ├── Props.h 42 | │ │ ├── ShadowNodes.cpp 43 | │ │ └── ShadowNodes.h 44 | │ ├── samplelibrary-generated.cpp 45 | │ └── samplelibrary.h 46 | └── schema.json 47 | ``` 48 | 49 | ### Extends the Abstract Class Provided by the Codegen 50 | 51 | Update your native module or component to ensure it **extends the abstract class** that has been code-generated from your JavaScript specs (i.e., the `NativeAwesomeManagerSpec.java` file from the previous example). 52 | 53 | Following the example set forth in the previous section, your library might import `NativeAwesomeManagerSpec`, implement the relevant native interface and the necessary methods for it: 54 | 55 |
56 | Java 57 | 58 | ```java 59 | import androidx.annotation.NonNull; 60 | 61 | import com.example.samplelibrary.NativeAwesomeManagerSpec; 62 | import com.facebook.react.bridge.Promise; 63 | import com.facebook.react.bridge.ReactApplicationContext; 64 | 65 | public class NativeAwesomeManager extends NativeAwesomeManagerSpec { 66 | 67 | public static final String NAME = "NativeAwesomeManager"; 68 | 69 | public NativeAwesomeManager(ReactApplicationContext reactContext) { 70 | super(reactContext); 71 | } 72 | 73 | @Override 74 | public void getString(String id, Promise promise) { 75 | // Implement this method 76 | } 77 | 78 | @NonNull 79 | @Override 80 | public String getName() { 81 | return NAME; 82 | } 83 | } 84 | ``` 85 | 86 |
87 |
88 | Kotlin 89 | 90 | ```kotlin 91 | import com.example.samplelibrary.NativeAwesomeManagerSpec 92 | import com.facebook.react.bridge.Promise 93 | import com.facebook.react.bridge.ReactApplicationContext 94 | 95 | class NativeAwesomeManager(reactContext: ReactApplicationContext) : 96 | NativeAwesomeManagerSpec(reactContext) { 97 | override fun getString(id: String, promise: Promise) { 98 | // Implement this method 99 | } 100 | 101 | override fun getName() = NAME 102 | 103 | companion object { 104 | val NAME = "NativeAwesomeManager" 105 | } 106 | } 107 | ``` 108 | 109 |
110 | 111 | Please note that the **generated abstract class** that you’re now extending (`MyAwesomeSpec` in this example) is itself extending `ReactContextBaseJavaModule`. Therefore you should not lose access to any of the method/fields you were previously using (e.g., the `ReactApplicationContext` and so on). Moreover, the generated class will now also implement the `TurboModule` interface for you. 112 | 113 | --- 114 | 115 | > [!IMPORTANT] 116 | > This documentation is still experimental and details are subject to changes as we iterate. 117 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 118 | > 119 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 120 | -------------------------------------------------------------------------------- /docs/enable-libraries-ios.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Enable the New Architecture for Libraries: iOS 4 | 5 | You have defined the JavaScript specs for your native modules as part of the [prerequisites](./enable-libraries-prerequisites.md), and you are now ready to migrate your library to the New Architecture. Here are the steps you can follow to accomplish this. 6 | 7 | ## 1. Updating your Podspec for the New Architecture 8 | 9 | The New Architecture makes use of CocoaPods. 10 | 11 | ### Add Folly and Other Dependencies 12 | 13 | The New Architecture requires some specific dependencies to work properly. You can set up your podspec to automatically install the required dependencies by modifying the `.podspec` file. In your `Pod::Spec.new` block, add the following line: 14 | 15 | ```diff 16 | Pod::Spec.new do |s| 17 | # ... 18 | + install_modules_dependencies(s) 19 | # ... 20 | end 21 | ``` 22 | 23 | At this [link](https://github.com/facebook/react-native/blob/v0.74.0/packages/react-native/scripts/react_native_pods.rb#L234-L239), you can find the documentation of the `install_modules_dependencies` function. 24 | 25 | If you need to explicitly know which `folly_flags` React Native is using, you can query them using the [`folly_flag`](https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L188) function. 26 | 27 | ## 2. Extend or Implement the Code-generated Native Interfaces 28 | 29 | The JavaScript spec for your native module or component will be used to generate native interface code for each supported platform (i.e., Android and iOS). These native interface files will be generated when a React Native application that depends on your library is built. 30 | 31 | While this generated native interface code **will not ship as part of your library**, you do need to make sure your Objective-C or Java code conforms to the protocols provided by these native interface files. You can use the Codegen script to generate your library’s native interface code in order to use it **as a reference**. 32 | 33 | ```sh 34 | cd 35 | node node_modules/react-native/scripts/generate-codegen-artifacts.js \ 36 | --path / \ 37 | --outputPath \ 38 | ``` 39 | 40 | This command will generate the boilerplate code required by iOS in the output path provided as a parameter. 41 | 42 | The files that are output by the script **should not be committed**, but you’ll need to refer to them to determine what changes you need to make to your native modules in order for them to provide an implementation for each generated `@protocol` / native interface. 43 | 44 | ### Conform to the protocols provided by the native interface code 45 | 46 | Update your native module or component to ensure it implements/extends the native interface that has been generated from your JavaScript specs. 47 | 48 | Following the example set forth in the previous section, your library might import `MyAwesomeSpecs.h`, extend the relevant native interface, and implement the necessary methods for this interface: 49 | 50 | ```objc 51 | #import 52 | 53 | @interface MyAwesomeModule () 54 | @end 55 | 56 | RCT_EXPORT_METHOD(getString:(NSString *)string 57 | callback:(RCTResponseSenderBlock)callback) 58 | { 59 | // Implement this method 60 | } 61 | 62 | - (std::shared_ptr)getTurboModule:(const ObjCTurboModule::InitParams &)params 63 | { 64 | return std::make_shared(params); 65 | } 66 | ``` 67 | 68 | For an existing native module, you will likely already have one or more instances of [`RCT_EXPORT_METHOD`](https://reactnative.dev/docs/next/native-modules-ios#export-a-native-method-to-javascript). To migrate to the New Architecture, you’ll need to make sure the method signature uses the structs provided by the Codegen output. 69 | 70 | --- 71 | 72 | > [!IMPORTANT] 73 | > This documentation is still experimental and details are subject to changes as we iterate. 74 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 75 | > 76 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 77 | -------------------------------------------------------------------------------- /docs/enable-libraries-prerequisites.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Enable the New Architecture for Libraries: Prerequisites 4 | 5 | > [!IMPORTANT] 6 | > Before proceeding to convert your library to natively use TurboModules/Fabric, you should [ensure that it is compatible with the Interop Layer](enable-libraries.md). There is no known performance cost imposed by the Interop Layer, but it will be removed at some point in the future. Today, it's safe and recommended to use the Interop Layer as a first solution to make your library compatible with the New Architecture quickly, and then you can consider migrating to the native APIs in the near future. 7 | 8 | This guide explains how to convert your library to use the TurboModules API and Fabric Component API natively, without the interop layer. The following steps are prerequisites to ensure your modules and components are ready for the New Architecture. 9 | 10 | 1. [Define Specs in JavaScript](#define-specs-in-javascript) 11 | 2. [Configure Codegen](#configure-codegen) 12 | 3. [Migrate off `UIManager` JavaScript APIs](#migrate-off-uimanager-javascript-apis) 13 | 14 | Once completed, you can follow these guides to enable New Architecture for 15 | 16 | - [your Android library](./enable-libraries-android.md) 17 | - [your iOS library](./enable-libraries-ios.md) 18 | 19 | ## Define Specs in JavaScript 20 | 21 | The JavaScript specs serve as the source of truth for the methods provided by each native module. They define all APIs provided by the native module, along with the types of those constants and functions. 22 | Using a **typed** spec file allows you to be intentional and declare all the input arguments and outputs of your native module’s methods. 23 | 24 | > [!NOTE] 25 | > We use [Flow](https://flow.org/) for JS typed specs. [TypeScript](https://www.typescriptlang.org/) support is in beta right now. 26 | 27 | To adopt the New Architecture, you start by creating these specs for your native modules and native components. You can do this before migrating to the New Architecture: the specs will be used later on to generate native interface code for all the supported platforms as a way to enforce uniform APIs across platforms. 28 | 29 | ### Turbo Native Modules 30 | 31 | JavaScript spec files **must** be named `Native.js`, and they export a `TurboModuleRegistry` `Spec` object. The name convention is important because the Codegen process looks for modules whose `js` (`jsx`, `ts`, or `tsx`) spec file starts with the keyword `Native`. 32 | 33 |
34 | Basic Flow Turbo Native Module Spec 35 | 36 | ```js 37 | // @flow strict 38 | 39 | import type { TurboModule } from "react-native/Libraries/TurboModule/RCTExport"; 40 | import { TurboModuleRegistry } from "react-native"; 41 | 42 | export interface Spec extends TurboModule { 43 | +getConstants: () => {||}; 44 | 45 | // your module methods go here, for example: 46 | getString(id: string): Promise; 47 | } 48 | 49 | export default (TurboModuleRegistry.get(""): ?Spec); 50 | ``` 51 | 52 |
53 | 54 |
55 | Basic TypeScript Turbo Native Module Spec 56 | 57 | ```tsx 58 | import { TurboModule, TurboModuleRegistry } from "react-native"; 59 | 60 | export interface Spec extends TurboModule { 61 | readonly getConstants: () => {}; 62 | 63 | // your module methods go here, for example: 64 | getString(id: string): Promise; 65 | } 66 | 67 | export default TurboModuleRegistry.get(""); 68 | ``` 69 | 70 |
71 | 72 | ### Fabric Native Components 73 | 74 | JavaScript spec files **must** be named `NativeComponent.js` (for TypeScript use extension `.ts` or `.tsx`) and they export a `HostComponent` object. The name convention is important: the Codegen process looks for components whose spec file (either JavaScript or TypeScript) ends with the suffix `NativeComponent`. 75 | 76 |
77 | Basic Flow Spec for Fabric Native Components 78 | 79 | ```js 80 | // @flow strict-local 81 | 82 | import type { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes"; 83 | import type { HostComponent } from "react-native"; 84 | import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent"; 85 | 86 | type NativeProps = $ReadOnly<{| 87 | ...ViewProps, 88 | // add other props here 89 | |}>; 90 | 91 | export default (codegenNativeComponent( 92 | "" 93 | ): HostComponent); 94 | ``` 95 | 96 |
97 |
98 | 99 | Basic TypeScript Spec for Fabric Native Components 100 | 101 | ```tsx 102 | import type { ViewProps } from "ViewPropTypes"; 103 | import type { HostComponent } from "react-native"; 104 | import codegenNativeComponent from "react-native/Libraries/Utilities/codegenNativeComponent"; 105 | 106 | export interface NativeProps extends ViewProps { 107 | // add other props here 108 | } 109 | 110 | export default codegenNativeComponent( 111 | "" 112 | ) as HostComponent; 113 | ``` 114 | 115 |
116 | 117 | ### Supported Types 118 | 119 | When using Flow or TypeScript, you will be using [type annotations](https://flow.org/en/docs/types/) to define your spec. Keeping in mind that the goal of defining a JavaScript spec is to ensure the generated native interface code is type-safe, the set of supported types will be those that can be mapped one-to-one to a corresponding type on the native platform. 120 | 121 | 122 | 123 | In general, this means you can use primitive types (strings, numbers, booleans), function types, object types, and array types. Union types, on the other hand, are not supported. All types must be read-only. For Flow: either `+` or `$ReadOnly<>` or `{||}` objects. For TypeScript: `readonly` for properties, `Readonly<>` for objects, and `ReadonlyArray<>` for arrays. 124 | 125 | > [!NOTE] 126 | > See Appendix [II. Flow Type to Native Type Mapping](./appendix.md#ii-flow-type-to-native-type-mapping). 127 | > See Appendix [III. TypeScript to Native Type Mapping](./appendix.md#iii-typescript-to-native-type-mapping). 128 | 129 | ### Codegen Helper Types 130 | 131 | You can use predefined types for your JavaScript spec, here is a list of them: 132 | 133 | - `Double` 134 | - `Float` 135 | - `Int32` 136 | - `UnsafeObject` 137 | - `WithDefault` - Sets default value for type 138 | - `BubblingEventHandler` - For events that are propagated (bubbled) up the component tree from child to parent up to the root (eg: `onStartShouldSetResponder`). 139 | - `DirectEventHandler` - For events that are called only on element recieving the event (eg: `onClick`) and don't bubble. 140 | 141 | Later on those types are compiled to coresponding equivalents on target platforms. 142 | 143 | ### Be Consistent Across Platforms and Eliminate Type Ambiguity 144 | 145 | Before adopting the New Architecture in your native module, you should ensure your methods are consistent across platforms. You will realize this as you set out to write the JavaScript spec for your native module - remember that JavaScript spec defines what the methods will look like on all supported platforms. 146 | 147 | If your existing native module has methods with the same name on multiple platforms, but with different numbers or types of arguments across platforms, you will need to find a way to make these consistent. If you have methods that can take two or more different types for the same argument, then you need to find a way to resolve this type of ambiguity as type unions are intentionally not supported. 148 | 149 | ## Configure Codegen 150 | 151 | [Codegen](./codegen.md) is a tool that runs when you build an Android app or install the dependencies of an iOS app. It creates some scaffolding code that you won't have to create manually. 152 | 153 | Codegen can be configured in the `package.json` file of your Library. Add the following JSON object at the end of it. 154 | 155 | ```diff 156 | }, 157 | + "codegenConfig": { 158 | + "name": "", 159 | + "type": "all", 160 | + "jsSrcsDir": ".", 161 | + "outputDir": { 162 | + "ios": "ios/generated", 163 | + "android": "android/generated" 164 | + }, 165 | + "includesGeneratedCode": true, 166 | + "android": { 167 | + "javaPackageName": "com.facebook.fbreact.specs" 168 | + } 169 | + } 170 | } 171 | ``` 172 | 173 | - The `codegenConfig` is the key used by the Codegen to verify that there is some code to generate. 174 | - The `name` field is the name of the library. 175 | - The `type` field is used to identify the type of module we want to create. We suggest keeping `all` to support libraries that contain both Turbo Native Module and Fabric Native Components. 176 | - The `jsSrcsDir` is the directory where the codegen will start looking for JavaScript specs. 177 | - The `outputDir` field is the output directory for the generated code. It can be defined in a platform-independent way, like `"outputDir": "universal/generated"`, or per-platform as in the example above. 178 | - The `includesGeneratedCode` field tells the Codegen whether this library include generated files. If `true`, the Codegen will not run for this library during the app build. 179 | - The `android.javaPackageName` is the name of the package where the generated code ends up. 180 | 181 | Android also requires to have the [React Gradle Plugin properly configured](new-architecture-app-intro#android-specifics) in your app. 182 | 183 | ## Migrate off `UIManager` JavaScript APIs 184 | 185 | In the New Architecture, most `UIManager` methods will become available as instance methods on native component instances obtained via `ref`: 186 | 187 | ```tsx 188 | function MyComponent(props: Props) { 189 | const viewRef = useRef(null); 190 | 191 | useEffect(() => { 192 | viewRef.current.measure(((left, top, width, height, pageX, pageY) => { 193 | // ... 194 | }); 195 | }, []); 196 | 197 | return ; 198 | } 199 | ``` 200 | 201 | This new API design provides several benefits: 202 | 203 | - Better developer ergonomics by removing the need for separately importing `UIManager` or calling `findNodeHandle`. 204 | - Better performance by avoiding the node handle lookup step. 205 | - Directionally aligned with [the analogous deprecation of `findDOMNode`](https://reactjs.org/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage). 206 | 207 | We will eventually deprecate `UIManager`. However, we recognize that migrations demand a high cost for many application and library authors. In order to minimize this cost, we plan to continue supporting as many of the methods on `UIManager` as possible in the New Architecture. 208 | 209 | **Support for `UIManager` methods in the New Architecture is actively being developed.** While we make progress here, early adopters can still experiment with the New Architecture by following these steps to migrate off common `UIManager` APIs: 210 | 211 | 1. Move the call to `requireNativeComponent` to a separate file 212 | 2. Migrating off `dispatchViewManagerCommand` 213 | 3. Creating NativeCommands with `codegenNativeCommands` 214 | 215 | ### Move the call to `requireNativeComponent` to a separate file 216 | 217 | This will prepare for the JS to be ready for the new codegen system for the New Architecture. The new file should be named `NativeComponent.js.` 218 | 219 | #### Old way 220 | 221 | ```js 222 | const RNTMyNativeView = requireNativeComponent('RNTMyNativeView'); 223 | 224 | [...] 225 | 226 | return ; 227 | ``` 228 | 229 | #### New way 230 | 231 | ```js title="RNTMyNativeNativeComponent.js" 232 | import RNTMyNativeViewNativeComponent from './RNTMyNativeViewNativeComponent'; 233 | 234 | [...] 235 | 236 | return ; 237 | ``` 238 | 239 | ```js title="RNTMyNativeViewNativeComponent.js" 240 | import { requireNativeComponent } from "react-native"; 241 | 242 | const RNTMyNativeViewNativeComponent = 243 | requireNativeComponent("RNTMyNativeView"); 244 | 245 | export default RNTMyNativeViewNativeComponent; 246 | ``` 247 | 248 | #### Flow support 249 | 250 | If `requireNativeComponent` is not typed, you can temporarily use the `mixed` type to fix the Flow warning, for example: 251 | 252 | ```js 253 | // @flow strict-local 254 | 255 | import type { HostComponent } from "react-native/Libraries/Renderer/shims/ReactNativeTypes"; 256 | // ... 257 | const RCTWebViewNativeComponent: HostComponent = 258 | requireNativeComponent("RNTMyNativeView"); 259 | ``` 260 | 261 | #### Later on you can replace `requireNativeComponent` 262 | 263 | When you are ready to migrate to Fabric you can replace `requireNativeComponent` with `codegenNativeComponent`: 264 | 265 | ```js title="RNTMyNativeViewNativeComponent.js" 266 | // @flow strict-local 267 | 268 | export default (codegenNativeComponent( 269 | "RNTMyNativeView" 270 | ): HostComponent); 271 | ``` 272 | 273 | And update the main file: 274 | 275 | ```js title="RNTMyNativeNativeComponent.js" 276 | // @flow strict-local 277 | 278 | export default require("./RNTMyNativeViewNativeComponent").default; 279 | ``` 280 | 281 | ### Migrate off `dispatchViewManagerCommand` 282 | 283 | Similar to the one above, in an effort to avoid calling methods on the UIManager, all view manager methods are now called through an instance of `NativeCommands`. `codegenNativeCommands` is a new API to code-generate `NativeCommands` given an interface of your view manager’s commands. 284 | 285 | **Before** 286 | 287 | ```tsx 288 | class MyComponent extends React.Component { 289 | _moveToRegion: (region: Region, duration: number) => { 290 | UIManager.dispatchViewManagerCommand( 291 | ReactNative.findNodeHandle(this), 292 | 'moveToRegion', 293 | [region, duration] 294 | ); 295 | } 296 | 297 | render() { 298 | return 299 | } 300 | } 301 | ``` 302 | 303 | **Creating NativeCommands with `codegenNativeCommands`** 304 | 305 | ```js title="MyCustomMapNativeComponent.js" 306 | // @flow strict-local 307 | 308 | import codegenNativeCommands from "react-native/Libraries/Utilities/codegenNativeCommands"; 309 | import type { HostComponent } from "react-native/Libraries/Renderer/shims/ReactNativeTypes"; 310 | 311 | type MyCustomMapNativeComponentType = HostComponent; 312 | 313 | interface NativeCommands { 314 | +moveToRegion: ( 315 | viewRef: React.ElementRef, 316 | region: MapRegion, 317 | duration: number 318 | ) => void; 319 | } 320 | 321 | export const Commands: NativeCommands = codegenNativeCommands({ 322 | supportedCommands: ["moveToRegion"], 323 | }); 324 | ``` 325 | 326 | Note: 327 | 328 | - The first argument in the `moveToRegion` command is a HostComponent ref of the native component 329 | - The arguments to the `moveToRegion` command are enumerated in the signature 330 | - The command definition is co-located with the native component. This is an encouraged pattern 331 | - Ensure you have included your command name in the `supportedCommands` array 332 | 333 | #### Using Your Command 334 | 335 | ```js 336 | // @flow strict-local 337 | 338 | import {Commands, ...} from './MyCustomMapNativeComponent'; 339 | 340 | class MyComponent extends React.Component { 341 | _ref: ?React.ElementRef; 342 | 343 | _captureRef: (ref: React.ElementRef) => { 344 | this._ref = ref; 345 | } 346 | 347 | _moveToRegion: (region: Region, duration: number) => { 348 | if (this._ref != null) { 349 | Commands.moveToRegion(this._ref, region, duration); 350 | } 351 | } 352 | 353 | render() { 354 | return 357 | } 358 | } 359 | ``` 360 | 361 | #### Updating Native Implementation 362 | 363 | In the example, the code-generated `Commands` will dispatch `moveToRegion` call to the native component’s view manager. In addition to writing the JS interface, you’ll need to update your native implementation signatures to match the dispatched method call. See the mapping for [Android argument types](https://facebook.github.io/react-native/docs/native-modules-android#argument-types) and[iOS argument types](https://facebook.github.io/react-native/docs/native-modules-ios#argument-types) for reference. 364 | 365 | **iOS** 366 | 367 | ```objc 368 | RCT_EXPORT_METHOD(moveToRegion:(nonnull NSNumber *)reactTag 369 | region:(NSDictionary *)region 370 | duration:(double)duration 371 | { 372 | ... 373 | } 374 | ``` 375 | 376 | **Android** 377 | 378 |
379 | Kotlin 380 | 381 | ```kotlin 382 | fun receiveCommand( 383 | view: ReactMapDrawerView?, commandId: String?, args: ReadableArray? 384 | ) { 385 | when (commandId) { 386 | "moveToRegion" -> { 387 | if (args != null) { 388 | val region: ReadableMap = args.getMap(0) 389 | val durationMs: Int = args.getInt(1) 390 | // ... act on the view... 391 | } 392 | } 393 | } 394 | } 395 | ``` 396 | 397 |
398 |
399 | Java 400 | 401 | ```java 402 | // receiveCommand signature has changed to receive String commandId 403 | @Override 404 | public void receiveCommand( 405 | ReactMapDrawerView view, String commandId, @Nullable ReadableArray args) { 406 | switch (commandId) { 407 | case "moveToRegion": 408 | if (args == null) { 409 | break; 410 | } 411 | 412 | ReadableMap region = args.getMap(0); 413 | int durationMs = args.getInt(1); 414 | // ... act on the view... 415 | break; 416 | } 417 | } 418 | ``` 419 | 420 |
421 | 422 | --- 423 | 424 | > [!IMPORTANT] 425 | > This documentation is still experimental and details are subject to changes as we iterate. 426 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 427 | > 428 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 429 | -------------------------------------------------------------------------------- /docs/enable-libraries.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Enable the New Architecture for Libraries 4 | 5 | **The first step for supporting the New Architecture in your library is to ensure that it is compatible with the [Interop Layer](https://github.com/reactwg/react-native-new-architecture/discussions/135)**. The Interop Layer makes it possible to use existing native modules written for the legacy architecture with the New Architecture. 6 | 7 | If your library is already fully converted to the TurboModules/Fabric APIs, then you can skip this guide. Otherwise, then **you should ensure compatibiltiy with the Interop Layer before proceeding to convert your library to natively use TurboModules/Fabric.** 8 | 9 | Starting with React Native 0.74, the Interop Layer is enabled by default. If your library works with it, then no changes will be needed on the user's side in order to use your library. 10 | 11 | > [!IMPORTANT] 12 | > If you have already added a Codegen spec to your library, but the library is not fully converted to TurboModules/Fabric, we recommend that you delete it before proceeding. You can add this back when you are ready to convert your library to natively use TurboModules/Fabric without the Interop Layer. 13 | 14 | ## Ensuring compatibility with the Interop Layer 15 | 16 | The Interop Layer will generally work out of the box with simple libraries, but the more complex your library is, the more likely it is that you will need to make some changes. The following sections will guide you through the process of verifying that your library works with the Interop Layer and fixing common issues that developers have encountered. 17 | 18 | > [!NOTE] 19 | > It is not necessary to test your library against the New Architecture with bridgeless _disabled_ — it is expected that from React Native 0.74, developers using the New Architecture will have bridgeless _enabled_. 20 | 21 | ## 1. Test your library with the New Architecture and the Interop Layer 22 | 23 | Follow [the guide for testing your library against the latest version of React Native](https://gist.github.com/cipolleschi/82b7a9561b8861330efabbd3eb08c6f5). Be sure to test all of the functionality of your library to ensure that it works as expected. 24 | 25 | ## 2. Fix any compatibility issues you encounter 26 | 27 | The following is a non-exhaustive list of common issues that developers have encountered when testing their libraries with the Interop Layer. If you encounter any of these issues, follow the provided guidance to fix them. 28 | 29 | ### [JavaScript] `XYZ is not a function (it is undefined)` 30 | 31 | When accessing a module directly from `NativeModules` in the new architecture some operators won't work due to the fact that TurboModule uses object prototypes now, in order to lazily load methods. So, if you used an operator like the spread operator or a function like `Object.keys` on `NativeModules.YourModule` then you will need to change to use `Object.create`. Learn more in [facebook/react-native#43221](https://github.com/facebook/react-native/issues/43221). 32 | 33 |
34 | Spread operator example 35 | 36 | ```js 37 | // Before: spread operator worked, but it will not with interop 38 | export default { 39 | ...NativeModules.RNCNetInfo, 40 | get eventEmitter(): NativeEventEmitter { 41 | ... 42 | } 43 | } 44 | ``` 45 | 46 | ```js 47 | // After: use Object.create instead 48 | Object.create(NativeModules.RNCNetInfo, { 49 | eventEmitter: { 50 | get: () => {...}, 51 | enumerable: true, 52 | }, 53 | }) 54 | ``` 55 | 56 |
57 | 58 |
59 | Object.keys example 60 | 61 | ```js 62 | // Before: Object.keys worked, but it will not with interop 63 | Object.keys(NativeModules.RNCNetInfo).forEach((key) => { 64 | ... 65 | }) 66 | ``` 67 | 68 | ```js 69 | // After: use for ... in instead 70 | for (const key in NativeModules.RNCNetInfo) { 71 | ... 72 | } 73 | ``` 74 | 75 |
76 | 77 | ### [JavaScript] `global.nativeCallSyncHook` can't be used to detect legacy "Remote Debugging in Chrome" with JSC 78 | 79 | `global.nativeCallSyncHook === 'undefined'` is a common way to check if you're running in "Remote Debugging in Chrome" (which is not supported with Hermes, only JSC). This is often used to provide some fallback behavior for sync native functions, because they do not work in the legacy remote debugging environment. Use `"RN$Bridgeless" in global && RN$Bridgeless === true` to determine if you are running in bridgeless. Learn more in [LinusU/react-native-get-random-values#57](https://github.com/LinusU/react-native-get-random-values/pull/57). 80 | 81 | It is generally not recommended to fork behavior based on whether bridgeless is enabled — this is an escape hatch that should be used sparingly. 82 | 83 | 84 |
85 | Example "isRemoteDebuggingInChrome()" function 86 | 87 | ```js 88 | function isRemoteDebuggingInChrome () { 89 | // Remote debugging in Chrome is not supported in bridgeless 90 | if ('RN$Bridgeless' in global && RN$Bridgeless === true) { 91 | return false 92 | } 93 | 94 | return __DEV__ && typeof global.nativeCallSyncHook === 'undefined' 95 | } 96 | ``` 97 | 98 |
99 | 100 | ### [iOS/Android] Interop Layer is not compatible with custom ShadowNodes 101 | 102 | The Interop Layer doesn't work on either Android or iOS if a legacy view is specifying a custom `ShadowNode`, i.e. in Android by overriding the method `getShadowNodeClass`, `createShadowNodeInstance` etc. Fabric won't call those methods and the widget will most likely be rendered incorrectly (i.e. wrong size, 0 height so unclickable, etc.). You can either work around this by not using custom `ShadowNode` or by converting your library to use TurboModules/Fabric without the Interop Layer. 103 | 104 | ### [Android] `ThemedContext.getNativeModule()` does not behave the same with Interop Layer 105 | 106 | A native view manager used to have a `ThemedContext`, which is a class derived from `ReactContext`. It was common to call `ThemedContext.getNativeModule()` or other methods expected to be inherited from `ReactContext`. However, with the Interop Layer this will call to `ReactContext`'s implementation (the `CatalystInstance` one) but not `BridgelessReactContext`. Accessing the internals to the `CatalystInstance` will cause an exception. 107 | 108 | You can solve this by using `ThemedContext.getReactApplicationContext().getNativeModule()`. 109 | 110 | ### [iOS] Cannot access CallInvoker from "RCTCxxBridge.callInvoker" 111 | 112 | In bridgeless mode, React Native does not support a fallback for `jsCallInvoker`, and the `bridge.jsCallInvoker` is `nil`. The app will crash when accessing null pointer. Instead, get `callInvoker` from `getTurboModule:`, e.g. [shopify/react-native-skia#2223](https://github.com/Shopify/react-native-skia/pull/2223). 113 | 114 | ## 3. Stuck? Get help 115 | 116 | Search the ["Libraries" discussion category](https://github.com/reactwg/react-native-new-architecture/discussions/categories/libraries) for similar issues, and if you can't find a solution, post a discussion with details about the issue you are facing. 117 | 118 | ## 4. Update library status 119 | 120 | Once you have verified that your library works with the Interop Layer, update the status of your library on [reactnative.directory](https://reactnative.directory/) to indicate that it is compatible with the New Architecture. If your library is already listed there, set `"newArchitecture": true` in [react-native-libraries.json](https://github.com/react-native-community/directory/blob/main/react-native-libraries.json), otherwise [add your library to the directory](https://github.com/react-native-community/directory?tab=readme-ov-file#how-do-i-add-a-library). 121 | 122 | ## Next steps 123 | 124 | Once your library is compatible with the Interop Layer, release a new version for your users. You can proceed to [converting your library to natively use TurboModules/Fabric](enable-libraries-prerequisites.md) when convenient for you. 125 | -------------------------------------------------------------------------------- /docs/react-18.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # React 18 & React Native 4 | 5 | This page describes how to use React 18 with React Native using the React Native's New Architecture. 6 | 7 | > **tl;dr:** The first version of React Native compatible with React 18 is **0.69.0**. In order to use the new features in React 18 including automatic batching, `startTransition`, and `useDeferredValue`, you must migrate your React Native app to the New Architecture. 8 | 9 | ## React 18 and the React Native New Architecture 10 | 11 | React 18 introduces [several new features](https://reactjs.org/blog/2022/03/29/react-v18.html) including: 12 | 13 | - Automatic batching 14 | - New Strict Mode behaviors 15 | - New hooks (`useId`, `useSyncExternalStore`) 16 | 17 | It also includes new concurrent features: 18 | 19 | - `startTransition` 20 | - `useTransition` 21 | - `useDeferredValue` 22 | - Full Suspense support 23 | 24 | The concurrent features in React 18 are built on top of the new concurrent rendering engine. Concurrent rendering is a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time. 25 | 26 | Previous versions of React Native built on the old architecture **cannot** support concurrent rendering or concurrent features. This is because the old architecture relied on mutating the native trees, which doesn’t allow for React to prepare multiple versions of the tree at the same time. 27 | 28 | Fortunately, the New Architecture was written bottom-up with concurrent rendering in mind, and is fully compatible with React 18. This means, in order to upgrade to React 18 in your React Native app, your application **needs to use the React Native's New Architecture** including Fabric Native Components and Turbo Native Modules. 29 | 30 | This means you’re able to use the new features in React 18 as soon as flip the New Architecture switch. Since the new concurrent features are opt-in by using features like `startTransition` or `Suspense`, we expect React 18 to work out-of-the-box with minimal changes for users who migrate to the New Architecture or create a new app with the New Architecture enabled. 31 | 32 | ### Note for the Old Architecture users 33 | 34 | Apps that are still on the Old Architecture will use React 17 mode even if React 18 is listed as a dependency in the `package.json` file. 35 | 36 | --- 37 | 38 | > [!IMPORTANT] 39 | > This documentation is still experimental and details are subject to changes as we iterate. 40 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 41 | > 42 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 43 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Troubleshooting 4 | 5 | This page contains resolutions to common problem you might face when migrating to the New Architecture. 6 | 7 | ## Xcode Build Issues 8 | 9 | Should the XCode Build fail with: 10 | 11 | **Command PhaseScriptExecution failed with a nonzero exit code** 12 | 13 | This error indicates that the codegen script that is injected into the Xcode build pipeline has exited early. You may get this for either your own library, or one of the core RN libraries (FBReactNativeSpec, rncore). 14 | 15 | Open `~/Library/Developer/Xcode/DerivedData`. and look for a folder named after your Xcode workspace (“RNTesterPods-AAAA” where “AAAA” is a string of characters). Within that folder, go to Build → Intermediates.noindex → Pods.build → Debug-iphonesimulator (or the equivalent for your iOS device, if applicable). Inside, look for the folder named after the codegen library has the script error. The logs for the script phase can be found within the DerivedSources folder, in a file named `codegen-LibraryName.log`. This log output should provide clarity on the source of the error. 16 | 17 | ## CocoaPods and Node Reset 18 | 19 | The CocoaPods integration will see frequent updates as we rollout the New Architecture, and it is possible to end up with your workspace in a broken state after one of these changes. You may clean up any changes related to the codegen by performing some of these steps: 20 | 21 | 1. Run `pod deintegrate` in your ios directory (or wherever your Podfile is located) and re-run `pod install` (or `arch -x86_64 pod install`, in case of a Mac M1). 22 | 2. Delete `Podfile.lock` and re-run `pod install` (or `arch -x86_64 pod install`, in case of a Mac M1). 23 | 3. Delete `node_modules` and re-run `yarn install`. 24 | 4. Delete your codegen artifacts and re-run `pod install` (or `arch -x86_64 pod install`, in case of a Mac M1), then clean and build your Xcode project. 25 | 26 | ## Android build is failing with `OutOfMemoryException` 27 | 28 | If your Android Gradle builds are failing with: `OutOfMemoryException: Out of memory: Java heap space.` or similar errors related to low memory, you might need to increase the memory allocated to the JVM. 29 | 30 | You can do that by editing the `gradle.properties` file in your `android/gradle.properties` folder: 31 | 32 | ```diff 33 | # Specifies the JVM arguments used for the daemon process. 34 | # The setting is particularly useful for tweaking memory settings. 35 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 36 | -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 37 | +org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 38 | ``` 39 | 40 | Make sure to uncomment the line and set the preferred memory size with the `-Xmx` parameter. 2Gb should be the minimum required and 4Gb is recommended. 41 | 42 | ## Android NDK and Mac with M1 Apple Silicon CPUs 43 | 44 | We're aware of a series of incompatibilities between the Android NDK and Macs on M1 CPUs ([here](https://github.com/android/ndk/issues/1299) and [here](https://github.com/android/ndk/issues/1410)). 45 | As you need to enable the NDK when building from source, you might face problems during your build. 46 | 47 | The workaround at this stage is [suggested here](https://github.com/android/ndk/issues/1299). 48 | As newer version of the Android SDK/NDK are released, we will update the documentation with the necessary steps. 49 | 50 | --- 51 | 52 | > [!IMPORTANT] 53 | > This documentation is still experimental and details are subject to changes as we iterate. 54 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 55 | > 56 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 57 | -------------------------------------------------------------------------------- /docs/turbo-modules-xplat.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Cross-Platform Turbo Native Modules with C++ 4 | 5 | This guide shows you how to implement a Turbo Native Module using C++ only, a way to share the same implementation with any supported platform (Android, iOS, macOS or Windows). 6 | 7 | Before continuing with this guide, please read the [Turbo Native Modules](./turbo-modules.md) section. As a further reference, we prepared an example for the RNTester app ([NativeCxxModuleExample](https://github.com/facebook/react-native/tree/main/packages/rn-tester/NativeCxxModuleExample)) and a sample run in our community repository ([run/pure-cxx-module](https://github.com/react-native-community/RNNewArchitectureApp/tree/run/pure-cxx-module)). 8 | 9 | ## How to Create a C++ Turbo Native Module 10 | 11 | To create a C++ Turbo Native Module, you need to: 12 | 13 | 1. Define the JavaScript specification. 14 | 2. Configure Codegen to generate the scaffolding. 15 | 3. Register the native module. 16 | 4. Write the native code to finish implementing the module. 17 | 18 | ### Setup a Test App for the New Architecture 19 | 20 | As first step, create a new application: 21 | 22 | ```sh 23 | npx react-native init CxxTurboModulesGuide 24 | cd CxxTurboModulesGuide 25 | yarn install 26 | ``` 27 | 28 | On Android enable the New Architecture by modifying the `android/gradle.properties` file: 29 | 30 | ```diff 31 | - newArchEnabled=false 32 | + newArchEnabled=true 33 | ``` 34 | 35 | On iOS enable the New Architecture when running `pod install` in the `ios` folder: 36 | 37 | ```sh 38 | RCT_NEW_ARCH_ENABLED=1 bundle exec pod install 39 | ``` 40 | 41 | ### Turbo Module Folder Setup 42 | 43 | Create a `tm` folder inside the project. It will contain all C++ TurboModules of your application. The final result should look like this: 44 | 45 | ```sh 46 | CxxTurboModulesGuide 47 | ├── android 48 | ├── ios 49 | ├── js 50 | └── tm 51 | ``` 52 | 53 | ## 1. JavaScript Specification 54 | 55 | Create the following spec inside the `tm` folder (`NativeSampleModule.ts|js`): 56 | 57 |
58 | TypeScript 59 | 60 | ```typescript title="NativeSampleModule.ts" 61 | import { TurboModule, TurboModuleRegistry } from "react-native"; 62 | 63 | export interface Spec extends TurboModule { 64 | readonly reverseString: (input: string) => string; 65 | } 66 | 67 | export default TurboModuleRegistry.getEnforcing("NativeSampleModule"); 68 | ``` 69 | 70 |
71 |
72 | Flow 73 | 74 | ```js title="NativeSampleModule.js" 75 | // @flow 76 | import type { TurboModule } from "react-native/Libraries/TurboModule/RCTExport"; 77 | // import type {TurboModule} from 'react-native'; in future versions 78 | import { TurboModuleRegistry } from "react-native"; 79 | 80 | export interface Spec extends TurboModule { 81 | +reverseString: (input: string) => string; 82 | } 83 | 84 | export default (TurboModuleRegistry.getEnforcing( 85 | "NativeSampleModule" 86 | ): Spec); 87 | ``` 88 | 89 |
90 | 91 | ## 2. Codegen Configuration 92 | 93 | Next, you need to add some configuration for [**Codegen**](./codegen.md). 94 | 95 | # Application 96 | 97 | Update your app's `package.json` file with the following entries: 98 | 99 | ```json title="package.json" 100 | { 101 | // ... 102 | "description": "React Native with Cxx Turbo Native Modules", 103 | "author": " (https://github.com/)", 104 | "license": "MIT", 105 | "homepage": "https://github.com//#readme", 106 | // ... 107 | "codegenConfig": { 108 | "name": "AppSpecs", 109 | "type": "all", 110 | "jsSrcsDir": "tm", 111 | "android": { 112 | "javaPackageName": "com.facebook.fbreact.specs" 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | It adds necessary properties which we will later re-use in the iOS `podspec` file and configures **Codegen** to search for specs inside the `tm` folder. 119 | 120 | > [!Warning] 121 | > C++ Turbo Native Modules don't autolink and need to be manually included into the app with the described steps below. 122 | 123 | ### iOS: Create the `podspec` file 124 | 125 | For iOS, you'll need to create a `AppTurboModules.podspec` file in the `tm` folder - which will look like: 126 | 127 | ```ruby title="AppTurboModules.podspec" 128 | require "json" 129 | 130 | package = JSON.parse(File.read(File.join(__dir__, "../package.json"))) 131 | 132 | Pod::Spec.new do |s| 133 | s.name = "AppTurboModules" 134 | s.version = package["version"] 135 | s.summary = package["description"] 136 | s.description = package["description"] 137 | s.homepage = package["homepage"] 138 | s.license = package["license"] 139 | s.platforms = { :ios => "12.4" } 140 | s.author = package["author"] 141 | s.source = { :git => package["repository"], :tag => "#{s.version}" } 142 | s.source_files = "**/*.{h,cpp}" 143 | s.pod_target_xcconfig = { 144 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 145 | } 146 | install_modules_dependencies(s) 147 | end 148 | ``` 149 | 150 | You need to add it as a dependency to your application in `ios/Podfile`, e.g., after the `use_react_native!(...)` section: 151 | 152 | ```ruby 153 | if ENV['RCT_NEW_ARCH_ENABLED'] == '1' 154 | pod 'AppTurboModules', :path => "./../tm" 155 | end 156 | ``` 157 | 158 | ### Android: `build.gradle`, `CMakeLists.txt`, `Onload.cpp` 159 | 160 | For Android, you'll need to create a `CMakeLists.txt` file in the `tm` folder - which will look like: 161 | 162 | ```cmake 163 | cmake_minimum_required(VERSION 3.13) 164 | set(CMAKE_VERBOSE_MAKEFILE on) 165 | 166 | add_compile_options( 167 | -fexceptions 168 | -frtti 169 | -std=c++17) 170 | 171 | file(GLOB tm_SRC CONFIGURE_DEPENDS *.cpp) 172 | add_library(tm STATIC ${tm_SRC}) 173 | 174 | target_include_directories(tm PUBLIC .) 175 | target_include_directories(react_codegen_AppSpecs PUBLIC .) 176 | 177 | target_link_libraries(tm 178 | jsi 179 | react_nativemodule_core 180 | react_codegen_AppSpecs) 181 | ``` 182 | 183 | It defines the `tm` folder as a source for native code and sets up necessary dependencies. 184 | 185 | You need to add it as a dependency to your application in `android/app/build.gradle`, e.g., at the very end of that file: 186 | 187 | ```kotlin title="build.gradle" 188 | android { 189 | externalNativeBuild { 190 | cmake { 191 | path "src/main/jni/CMakeLists.txt" 192 | } 193 | } 194 | } 195 | ``` 196 | 197 | > [!note] 198 | > Ensure to pick the correct **android/app/build.gradle** file and not android/build.gradle. 199 | 200 | ## 3. Module Registration 201 | 202 | ### iOS 203 | 204 | To register a C++ Turbo Native Module in your app you will need to update `ios/CxxTurboModulesGuide/AppDelegate.mm` with the following entries: 205 | 206 | ```diff 207 | #import "AppDelegate.h" 208 | 209 | #import 210 | + #import 211 | + #import 212 | + #import 213 | 214 | + @interface AppDelegate () {} 215 | + @end 216 | 217 | // ... 218 | 219 | ᠆ (Class)getModuleClassFromName:(const char *)name 220 | { 221 | return RCTCoreModulesClassProvider(name); 222 | } 223 | 224 | + #pragma mark RCTTurboModuleManagerDelegate 225 | 226 | + - (std::shared_ptr)getTurboModule:(const std::string &)name 227 | + jsInvoker:(std::shared_ptr)jsInvoker 228 | + { 229 | + if (name == "NativeSampleModule") { 230 | + return std::make_shared(jsInvoker); 231 | + } 232 | + return nullptr; 233 | + } 234 | ``` 235 | 236 | This will instantiante a `NativeSampleModule` associated with the name `NativeSampleModule` as defined in our JavaScript spec file earlier. 237 | 238 | ### Android 239 | 240 | Android apps aren't setup for native code compilation by default. 241 | 242 | 1.) Create the folder `android/app/src/main/jni` 243 | 244 | 2.) Copy `CMakeLists.txt` and `Onload.cpp` from [node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup](https://github.com/facebook/react-native/tree/main/packages/react-native/ReactAndroid/cmake-utils/default-app-setup) into the `android/app/src/main/jni` folder. 245 | 246 | Update `Onload.cpp` with the following entries: 247 | 248 | ```diff 249 | // ... 250 | 251 | #include 252 | #include 253 | + #include 254 | 255 | // ... 256 | 257 | std::shared_ptr cxxModuleProvider( 258 | const std::string &name, 259 | const std::shared_ptr &jsInvoker) { 260 | + if (name == "NativeSampleModule") { 261 | + return std::make_shared(jsInvoker); 262 | + } 263 | return nullptr; 264 | } 265 | 266 | // ... 267 | ``` 268 | 269 | Update `CMakeLists.txt` with the following entries, e.g., at the very end of that file: 270 | 271 | ```diff 272 | // ... 273 | 274 | # This file includes all the necessary to let you build your application with the New Architecture. 275 | include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake) 276 | 277 | + # App needs to add and link against tm (TurboModules) folder 278 | + add_subdirectory(${REACT_ANDROID_DIR}/../../../tm/ tm_build) 279 | + target_link_libraries(${CMAKE_PROJECT_NAME} tm) 280 | ``` 281 | 282 | This will instantiante a `NativeSampleModule` associated with the name `NativeSampleModule` as defined in our JavaScript spec file earlier. 283 | 284 | ## 4. C++ Native Code 285 | 286 | For the final step, you'll need to write some native code to connect the JavaScript side to the native platforms. This process requires two main steps: 287 | 288 | - Run **Codegen** to see what it generates. 289 | - Write your native code, implementing the generated interfaces. 290 | 291 | ### Run Codegen 292 | 293 | > [!Note] 294 | > Follow the [Codegen](./codegen.md) guide for general information. 295 | 296 | On iOS Codegen is run each time you execute in the `ios` folder: 297 | 298 | ```sh 299 | RCT_NEW_ARCH_ENABLED=1 bundle exec pod install 300 | ``` 301 | 302 | You can inspect the generated `AppSpecsJSI.h` and `AppSpecsJSI-generated.cpp` files inside the `CxxTurboModulesGuide/ios/build/generated/ios` folder. 303 | 304 | Those files are prefixed with `AppSpecs` as this matches the `codegenConfig.name` parameter added earlier to `package.json`. 305 | 306 | On Android Codegen is run each time you execute: 307 | 308 | ```sh 309 | yarn android 310 | ``` 311 | 312 | You can inspect the generated `AppSpecsJSI.h` and `AppSpecsJSI-generated.cpp` files inside the `CxxTurboModulesGuide/android/app/build/generated/source/codegen/jni` folder. 313 | 314 | You only need to re-run codegen if you have changed your JavaScript spec. 315 | 316 | The C++ function generated for our JavaScript spec file looks like: 317 | 318 | ```cpp 319 | virtual jsi::String reverseString(jsi::Runtime &rt, jsi::String input) = 0; 320 | ``` 321 | 322 | You can directly work with the lower level `jsi::` types - but for convience C++ Turbo Native Modules automatically `bridge` into `std::` types for you. 323 | 324 | ### Implementation 325 | 326 | Now create a `NativeSampleModule.h` file with the following content: 327 | 328 | > [!Note] 329 | > Due to current differences in the CMake and CocoaPod setup we need some creativity to include the correct Codegen header on each platform. 330 | 331 | ```cpp 332 | #pragma once 333 | 334 | #if __has_include() // CocoaPod headers on Apple 335 | #include 336 | #elif __has_include("AppSpecsJSI.h") // CMake headers on Android 337 | #include "AppSpecsJSI.h" 338 | #endif 339 | #include 340 | #include 341 | 342 | namespace facebook::react { 343 | 344 | class NativeSampleModule : public NativeSampleModuleCxxSpec { 345 | public: 346 | NativeSampleModule(std::shared_ptr jsInvoker); 347 | 348 | std::string reverseString(jsi::Runtime& rt, std::string input); 349 | }; 350 | 351 | } // namespace facebook::react 352 | ``` 353 | 354 | In this case you can use any C++ type which `bridges` to a `jsi::String` - default or [custom one](./cxx-custom-types.md). You can't specify an incompatible type such as `bool`, `float` or `std::vector<>` as it does not `bridge` to `jsi::String` and hence results in a compilation error. 355 | 356 | Now add a `NativeSampleModule.cpp` file with an implementation for it: 357 | 358 | ```cpp 359 | #include "NativeSampleModule.h" 360 | 361 | namespace facebook::react { 362 | 363 | NativeSampleModule::NativeSampleModule(std::shared_ptr jsInvoker) 364 | : NativeSampleModuleCxxSpec(std::move(jsInvoker)) {} 365 | 366 | std::string NativeSampleModule::reverseString(jsi::Runtime& rt, std::string input) { 367 | return std::string(input.rbegin(), input.rend()); 368 | } 369 | 370 | } // namespace facebook::react 371 | ``` 372 | 373 | As we have added new C++ files run in the `ios` folder: 374 | 375 | ```sh 376 | RCT_NEW_ARCH_ENABLED=1 bundle exec pod install 377 | ``` 378 | 379 | for iOS. In Xcode they appear under the `Pods` target in the `Development Pods \ TurboModules` subfolder. 380 | 381 | You should now be able to compile your app both on Android and iOS. 382 | 383 | ```sh 384 | CxxTurboModulesGuide 385 | ├── android 386 | │ └── app 387 | │ │── src 388 | │ │ └── main 389 | │ │ └── jni 390 | │ │ ├── CMakeLists.txt 391 | │ │ └── OnLoad.cpp 392 | │ └── build.gradle (updated) 393 | ├── ios 394 | │ └── CxxTurboModulesGuide 395 | │ └── AppDelegate.mm (updated) 396 | ├── js 397 | │ └── App.tsx|jsx (updated) 398 | └── tm 399 | ├── CMakeLists.txt 400 | ├── NativeSampleModule.h 401 | ├── NativeSampleModule.cpp 402 | ├── NativeSampleModule.ts|js 403 | └── TurboModules.podspec 404 | ``` 405 | 406 | ## 5. Adding the C++ Turbo Native Module to your App 407 | 408 | For demo purposes we can update our app's `App.tsx|jsx` with the following entries: 409 | 410 | ```diff 411 | //... 412 | import { 413 | Colors, 414 | DebugInstructions, 415 | Header, 416 | LearnMoreLinks, 417 | ReloadInstructions, 418 | } from 'react-native/Libraries/NewAppScreen'; 419 | + import NativeSampleModule from './tm/NativeSampleModule'; 420 | //... 421 | 425 | +
426 | + NativeSampleModule.reverseString(...) ={' '} 427 | + {NativeSampleModule.reverseString( 428 | + 'the quick brown fox jumps over the lazy dog' 429 | + )} 430 | +
; 431 |
432 | Edit App.tsx to change this 433 | screen and then come back to see your edits. 434 |
435 | //... 436 | ``` 437 | 438 | Run the app to see your C++ Turbo Native Module in action! 439 | 440 | ## App TurboModuleProvider [Optional] 441 | 442 | You can avoid some code duplication once you added multiple C++ Turbo Native Modules by declaring an AppTurboModuleProvider: 443 | 444 | ```cpp title="AppTurboModuleProvider.h" 445 | #pragma once 446 | 447 | #include 448 | #include 449 | #include 450 | 451 | namespace facebook::react { 452 | 453 | class AppTurboModuleProvider { 454 | public: 455 | std::shared_ptr getTurboModule( 456 | const std::string& name, 457 | std::shared_ptr jsInvoker) const; 458 | }; 459 | 460 | } // namespace facebook::react 461 | ``` 462 | 463 | And implementing it: 464 | 465 | ```cpp title="AppTurboModuleProvider.cpp" 466 | #include "AppTurboModuleProvider.h" 467 | #include "NativeSampleModule.h" 468 | 469 | namespace facebook::react { 470 | 471 | std::shared_ptr AppTurboModuleProvider::getTurboModule( 472 | const std::string& name, 473 | std::shared_ptr jsInvoker) const { 474 | if (name == "NativeSampleModule") { 475 | return std::make_shared(jsInvoker); 476 | } 477 | // Other C++ Turbo Native Modules for you app 478 | return nullptr; 479 | } 480 | 481 | } // namespace facebook::react 482 | ``` 483 | 484 | And then re-using it in `OnLoad.cpp` for Android and `AppDelegate.mm` for iOS, e.g., via: 485 | 486 | ```cpp 487 | static facebook::react::AppTurboModuleProvider appTurboModuleProvider; 488 | return appTurboModuleProvider.getTurboModule(name, jsInvoker); 489 | ``` 490 | 491 | in the corresponding functions. 492 | 493 | ## Calling OS specific APIs 494 | 495 | You can still call OS specific functions in the compilation unit (e.g., `NS/CF` APIs on Apple or `Win32/WinRT` APIs on Windows) as long as the method signatures only use `std::` or `jsi::` types. 496 | 497 | For Apple specific APIs you need to change the extension of your implementation file from `.cpp` to `.mm` to be able to consume `NS/CF` APIs. 498 | 499 | ## Extending C++ Turbo Native Modules 500 | 501 | If you need to support some types that are not supported yet, have a look at [this other guide](./cxx-custom-types.md). 502 | 503 | --- 504 | 505 | > [!IMPORTANT] 506 | > This documentation is still experimental and details are subject to changes as we iterate. 507 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 508 | > 509 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 510 | -------------------------------------------------------------------------------- /docs/turbo-modules.md: -------------------------------------------------------------------------------- 1 | [../README.md#Guides](../README.md#guides) 2 | 3 | # Turbo Native Modules 4 | 5 | If you've worked with React Native, you may be familiar with the concept of [Native Modules](https://reactnative.dev/docs/native-modules-intro), which allow JavaScript and platform-native code to communicate over the React Native "bridge", which handles cross-platform serialization via JSON. 6 | 7 | Turbo Native Modules are the next iteration on Native Modules that provide a few extra [benefits](https://reactnative.dev/docs/the-new-architecture/landing-page): 8 | 9 | - Strongly typed interfaces that are consistent across platforms 10 | - 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 11 | - Lazy loading of modules, allowing for faster app startup 12 | - The use of JSI, a JavaScript interface for native code, allows for more efficient communication between native and JavaScript code than the bridge 13 | 14 | At a high-level the steps for writing a Turbo Module are: 15 | 16 | 1. Define a JavaScript specification using Flow or TypeScript. 17 | 2. Configure the dependencies management system to generate code from the provided spec. 18 | 3. Implement the Native code. 19 | 4. Integrate the code in the app. 20 | 21 | This guide will show you how to create a basic Turbo Native Module compatible with the latest version of React Native. 22 | 23 | > [!Note] 24 | > You can also setup local library containing Turbo Native Module with one command. Read the guide to [Local libraries setup](https://reactnative.dev/docs/next/local-library-setup) for more details. 25 | 26 | ## How to Create a Turbo Native Module 27 | 28 | To create a Turbo Native Module, we need to: 29 | 30 | 1. Define the JavaScript specification. 31 | 2. Configure the module so that Codegen can generate the scaffolding. 32 | 3. Write the native code to finish implementing the module. 33 | 34 | ### 1. Folder Setup 35 | 36 | 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 Turbo Native Modules that can be released as open-source libraries later. 37 | 38 | 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. 39 | 40 | Within `RTNCalculator`, create three subfolders: `js`, `ios`, and `android`. 41 | 42 | The final result should look like this: 43 | 44 | ```sh 45 | TurboModulesGuide 46 | ├── MyApp 47 | └── RTNCalculator 48 | ├── android 49 | ├── ios 50 | └── js 51 | ``` 52 | 53 | ### 2. JavaScript Specification 54 | 55 | 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. 56 | 57 | There are two requirements the file containing this specification must meet: 58 | 59 | 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. 60 | 2. The file must export a `TurboModuleRegistrySpec` object. 61 | 62 |
63 | Flow 64 | 65 | ```typescript title="NativeCalculator.js" 66 | // @flow 67 | import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; 68 | import {TurboModuleRegistry} from 'react-native'; 69 | 70 | export interface Spec extends TurboModule { 71 | add(a: number, b: number): Promise; 72 | } 73 | export default (TurboModuleRegistry.get( 74 | 'RTNCalculator' 75 | ): ?Spec); 76 | ``` 77 | 78 |
79 | 80 |
81 | TypeScript 82 | 83 | ```typescript title="NativeCalculator.ts" 84 | import { TurboModule, TurboModuleRegistry } from "react-native"; 85 | 86 | export interface Spec extends TurboModule { 87 | add(a: number, b: number): Promise; 88 | } 89 | 90 | export default TurboModuleRegistry.get("RTNCalculator") as Spec | null; 91 | ``` 92 | 93 |
94 | 95 | Place the file in the `js` directory that we have just created, eg: `js/NativeRTNCalculator.ts`. 96 | 97 | At the beginning of the spec files are the imports: 98 | 99 | - The `TurboModule` type, which defines the base interface for all Turbo Native Modules 100 | - The `TurboModuleRegistry` JavaScript module, which contains functions for loading Turbo Native Modules 101 | 102 | The second section of the file contains the interface specification for the Turbo Native Module. 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 Turbo Native Module. 103 | 104 | Finally, we invoke `TurboModuleRegistry.get`, passing the module's name, which will load the Turbo Native Module if it's available. 105 | 106 | > [!Warning] 107 | > 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. 108 | 109 | ### 3. Module Configuration 110 | 111 | Next, you need to add some configuration for [**Codegen**](./codegen.md) and auto-linking. 112 | 113 | Some configuration files are shared between iOS and Android, while the others are platform-specific. 114 | 115 | ##### Shared 116 | 117 | The shared configuration is a `package.json` file used by yarn when installing your module. Create the `package.json` file in the root of the `RTNCalculator` directory. 118 | 119 | ```json title="package.json" 120 | { 121 | "name": "rtn-calculator", 122 | "version": "0.0.1", 123 | "description": "Add numbers with Turbo Native Modules", 124 | "react-native": "js/index", 125 | "source": "js/index", 126 | "files": [ 127 | "js", 128 | "android", 129 | "ios", 130 | "rtn-calculator.podspec", 131 | "!android/build", 132 | "!ios/build", 133 | "!**/__tests__", 134 | "!**/__fixtures__", 135 | "!**/__mocks__" 136 | ], 137 | "keywords": ["react-native", "ios", "android"], 138 | "repository": "https://github.com//rtn-calculator", 139 | "author": " (https://github.com/)", 140 | "license": "MIT", 141 | "bugs": { 142 | "url": "https://github.com//rtn-calculator/issues" 143 | }, 144 | "homepage": "https://github.com//rtn-calculator#readme", 145 | "devDependencies": {}, 146 | "peerDependencies": { 147 | "react": "*", 148 | "react-native": "*" 149 | }, 150 | "codegenConfig": { 151 | "name": "RTNCalculatorSpec", 152 | "type": "modules", 153 | "jsSrcsDir": "js", 154 | "android": { 155 | "javaPackageName": "com.rtncalculator" 156 | } 157 | } 158 | } 159 | ``` 160 | 161 | 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. 162 | 163 | Then there are the dependencies for this package. For this guide, you need `react` and `react-native`. 164 | 165 | Finally, the **Codegen** configuration is specified by the `codegenConfig` field. It contains an object that defines the module through four fields: 166 | 167 | - `name`: The name of the library. By convention, you should add the `Spec` suffix. 168 | - `type`: The type of module contained by this package. In this case, it is a Turbo Native Module; thus, the value to use is `modules`. 169 | - `jsSrcsDir`: the relative path to access the `js` specification that is parsed by **Codegen**. 170 | - `android.javaPackageName`: the package to use in the Java files generated by **Codegen**. 171 | 172 | #### iOS: Create the `podspec` file 173 | 174 | 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. 175 | 176 | The file will look like this: 177 | 178 | ```ruby title="rtn-calculator.podspec" 179 | require "json" 180 | 181 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 182 | 183 | Pod::Spec.new do |s| 184 | s.name = "rtn-calculator" 185 | s.version = package["version"] 186 | s.summary = package["description"] 187 | s.description = package["description"] 188 | s.homepage = package["homepage"] 189 | s.license = package["license"] 190 | s.platforms = { :ios => "11.0" } 191 | s.author = package["author"] 192 | s.source = { :git => package["repository"], :tag => "#{s.version}" } 193 | 194 | s.source_files = "ios/**/*.{h,m,mm,swift}" 195 | 196 | install_modules_dependencies(s) 197 | end 198 | ``` 199 | 200 | 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`. 201 | 202 | The first part of the file prepares some variables that we use throughout the file. Then, there is a section that contains some information used to configure the pod, like its name, version, and description. 203 | 204 | All the requirements for the New Architecture have been encapsulated in the [`install_modules_dependencies`](https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L198). It takes care of installing the proper dependencies based on which architecture is currently enabled. It also automatically installs the `React-Core` dependency in the old architecture. 205 | 206 | #### Android: `build.gradle` and `ReactPackage` class 207 | 208 | To prepare Android to run **Codegen** you have to: 209 | 210 | 1. Update the `build.gradle` file. 211 | 2. A Java/Kotlin class that implements the `ReactPackage` interface 212 | 213 | At the end of these steps, the `android` folder should look like this: 214 | 215 | ```title="Android Folder Structure" 216 | android 217 | ├── build.gradle 218 | └── src 219 | └── main 220 | └── java 221 | └── com 222 | └── rtncalculator 223 | └── CalculatorPackage.java 224 | ``` 225 | 226 | ##### The `build.gradle` file 227 | 228 | First, create a `build.gradle` file in the `android` folder, with the following contents: 229 | 230 |
231 | Java 232 | 233 | ```java title="build.gradle" 234 | buildscript { 235 | ext.safeExtGet = {prop, fallback -> 236 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 237 | } 238 | repositories { 239 | google() 240 | gradlePluginPortal() 241 | } 242 | dependencies { 243 | classpath("com.android.tools.build:gradle:7.3.1") 244 | } 245 | } 246 | 247 | apply plugin: 'com.android.library' 248 | apply plugin: 'com.facebook.react' 249 | 250 | android { 251 | compileSdkVersion safeExtGet('compileSdkVersion', 33) 252 | namespace "com.rtncalculator" 253 | } 254 | 255 | repositories { 256 | mavenCentral() 257 | google() 258 | } 259 | 260 | dependencies { 261 | implementation 'com.facebook.react:react-native' 262 | } 263 | ``` 264 | 265 |
266 | 267 |
268 | Kotlin 269 | 270 | ```kotlin title="build.gradle" 271 | buildscript { 272 | ext.safeExtGet = {prop, fallback -> 273 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 274 | } 275 | repositories { 276 | google() 277 | gradlePluginPortal() 278 | } 279 | dependencies { 280 | classpath("com.android.tools.build:gradle:7.3.1") 281 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.22") 282 | } 283 | } 284 | 285 | apply plugin: 'com.android.library' 286 | apply plugin: 'com.facebook.react' 287 | apply plugin: 'org.jetbrains.kotlin.android' 288 | 289 | android { 290 | compileSdkVersion safeExtGet('compileSdkVersion', 33) 291 | namespace "com.rtncalculator" 292 | } 293 | 294 | repositories { 295 | mavenCentral() 296 | google() 297 | } 298 | 299 | dependencies { 300 | implementation 'com.facebook.react:react-native' 301 | } 302 | ``` 303 | 304 |
305 | 306 | ##### The `ReactPackage` class 307 | 308 | Then, you need a class that extends the `BaseReactPackage` 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. 309 | 310 | Create an `android/src/main/java/com/rtncalculator` folder and, inside that folder, create a `CalculatorPackage.java` file. 311 | 312 |
313 | Java 314 | 315 | ```java title="CalculatorPackage.java" 316 | package com.rtncalculator; 317 | 318 | import androidx.annotation.Nullable; 319 | import com.facebook.react.bridge.NativeModule; 320 | import com.facebook.react.bridge.ReactApplicationContext; 321 | import com.facebook.react.module.model.ReactModuleInfoProvider; 322 | import com.facebook.react.BaseReactPackage; 323 | 324 | import java.util.Collections; 325 | import java.util.List; 326 | 327 | public class CalculatorPackage extends BaseReactPackage { 328 | 329 | @Nullable 330 | @Override 331 | public NativeModule getModule(String name, ReactApplicationContext reactContext) { 332 | return null; 333 | } 334 | 335 | @Override 336 | public ReactModuleInfoProvider getReactModuleInfoProvider() { 337 | return null; 338 | } 339 | } 340 | ``` 341 | 342 |
343 |
344 | Kotlin 345 | 346 | ```kotlin title="CalculatorPackage.kt" 347 | package com.rtncalculator; 348 | 349 | import com.facebook.react.BaseReactPackage 350 | import com.facebook.react.bridge.NativeModule 351 | import com.facebook.react.bridge.ReactApplicationContext 352 | import com.facebook.react.module.model.ReactModuleInfoProvider 353 | 354 | class CalculatorPackage : BaseReactPackage() { 355 | override fun getModule(name: String?, reactContext: ReactApplicationContext): NativeModule? = null 356 | 357 | override fun getReactModuleInfoProvider(): ReactModuleInfoProvider? = null 358 | } 359 | ``` 360 | 361 |
362 | 363 | React Native uses the `ReactPackage` interface to understand what native classes the app has to use for the `ViewManager` and `Native Modules` exported by the library. 364 | 365 | ### 4. Native Code 366 | 367 | For the final step in getting your Turbo Native Module 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: 368 | 369 | - Run **Codegen** to see what it generates. 370 | - Write your native code, implementing the generated interfaces. 371 | 372 | When developing a React Native app that uses a Turbo Native Module, it is the 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. 373 | 374 | As the 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](./codegen.md). 375 | 376 | > [!Warning] 377 | > 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. It is also possible to [include the codegen output into your library](./codegen.md#including-generated-code-into-libraries). 378 | 379 | 380 | #### iOS 381 | 382 | ##### Generate the code - iOS 383 | 384 | To run Codegen for the iOS platform, we need to open a terminal and run the following command: 385 | 386 | ```sh title="Running Codegen for iOS" 387 | cd MyApp 388 | yarn add ../RTNCalculator 389 | cd .. 390 | node MyApp/node_modules/react-native/scripts/generate-codegen-artifacts.js \ 391 | --targetPlatform ios \ 392 | --path MyApp/ \ 393 | --outputPath RTNCalculator/generated/ 394 | ``` 395 | 396 | This script first adds the `RTNCalculator` module to the app with `yarn add`. Then, it invokes Codegen via the `generate-codegen-artifacts.js` script. 397 | 398 | The `--path` option specifies the path to the app, while the `--outputPath` option tells Codegen where to output the generated code. 399 | 400 | The output of this process is the following folder structure: 401 | 402 | ```sh 403 | generated 404 | └── build 405 | └── generated 406 | └── ios 407 | ├── FBReactNativeSpec 408 | │ ├── FBReactNativeSpec-generated.mm 409 | │ └── FBReactNativeSpec.h 410 | ├── RCTThirdPartyFabricComponentsProvider.h 411 | ├── RCTThirdPartyFabricComponentsProvider.mm 412 | ├── RTNCalculatorSpec 413 | │ ├── RTNCalculatorSpec-generated.mm 414 | │ └── RTNCalculatorSpec.h 415 | └── react 416 | └── renderer 417 | └── components 418 | └── rncore 419 | ├── ComponentDescriptors.h 420 | ├── EventEmitters.cpp 421 | ├── EventEmitters.h 422 | ├── Props.cpp 423 | ├── Props.h 424 | ├── RCTComponentViewHelpers.h 425 | ├── ShadowNodes.cpp 426 | └── ShadowNodes.h 427 | ``` 428 | 429 | The relevant path for the Turbo Native Module interface is `generated/build/generated/ios/RTNCalculatorSpec`. 430 | 431 | See the [Codegen](./codegen.md) section for further details on the generated files. 432 | 433 | > [!Note] 434 | > When generating the scaffolding code using **Codegen**, iOS does not clean the `build` folder automatically. If you changed the Spec name, for example, and then run **Codegen** again, the old files would be retained. 435 | > If that happens, remember to remove the `build` folder before running the **Codegen** again. 436 | > 437 | > ``` 438 | > cd MyApp/ios 439 | > rm -rf build 440 | > ``` 441 | 442 | ##### Write the Native iOS Code 443 | 444 | Now add the Native code for your Turbo Native Module. Create two files in the `RTNCalculator/ios` folder: 445 | 446 | 1. The `RTNCalculator.h`, a header file for the module. 447 | 2. The `RTNCalculator.mm`, the implementation of the module. 448 | 449 | ###### RTNCalculator.h 450 | 451 | ```objc title="RTNCalculator.h" 452 | #import 453 | 454 | NS_ASSUME_NONNULL_BEGIN 455 | 456 | @interface RTNCalculator : NSObject 457 | 458 | @end 459 | 460 | NS_ASSUME_NONNULL_END 461 | ``` 462 | 463 | 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. 464 | 465 | ###### RTNCalculator.mm 466 | 467 | ```objc title="RTNCalculator.mm" 468 | #import "RTNCalculator.h" 469 | 470 | @implementation RTNCalculator 471 | 472 | RCT_EXPORT_MODULE() 473 | 474 | - (void)add:(double)a b:(double)b resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { 475 | NSNumber *result = [[NSNumber alloc] initWithInteger:a+b]; 476 | resolve(result); 477 | } 478 | 479 | - (std::shared_ptr)getTurboModule: 480 | (const facebook::react::ObjCTurboModule::InitParams &)params 481 | { 482 | return std::make_shared(params); 483 | } 484 | 485 | @end 486 | ``` 487 | 488 | The most important call is to the `RCT_EXPORT_MODULE`, which is required to export the module so that React Native can load the Turbo Native Module. 489 | 490 | Then the `add` method, whose signature must match the one specified by the Codegen in the `RTNCalculatorSpec.h`. 491 | 492 | Finally, the `getTurboModule` method gets an instance of the Turbo Native Module 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. 493 | 494 | > [!Tip] 495 | > 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/packages/react-native/React/Base/RCTBridgeModule.h). 496 | 497 | #### Android 498 | 499 | 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. 500 | 501 | ##### Generate the Code - Android 502 | 503 | 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. 504 | 505 | ```sh title="Running Codegen for Android" 506 | cd MyApp 507 | yarn add ../RTNCalculator 508 | cd android 509 | ./gradlew generateCodegenArtifactsFromSchema 510 | ``` 511 | 512 | 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. 513 | 514 | > [!Tip] 515 | > 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`. 516 | 517 | The generated code is stored in the `MyApp/node_modules/rtn-calculator/android/build/generated/source/codegen` folder and it has this structure: 518 | 519 | ```title="Android generated code" 520 | codegen 521 | ├── java 522 | │ └── com 523 | │ └── rtncalculator 524 | │ └── NativeRTNCalculatorSpec.java 525 | ├── jni 526 | │ ├── Android.mk 527 | │ ├── CMakeLists.txt 528 | │ ├── RTNCalculator-generated.cpp 529 | │ ├── RTNCalculator.h 530 | │ └── react 531 | │ └── renderer 532 | │ └── components 533 | │ └── RTNCalculator 534 | │ ├── ComponentDescriptors.h 535 | │ ├── EventEmitters.cpp 536 | │ ├── EventEmitters.h 537 | │ ├── Props.cpp 538 | │ ├── Props.h 539 | │ ├── ShadowNodes.cpp 540 | │ └── ShadowNodes.h 541 | └── schema.json 542 | ``` 543 | 544 | ##### Write the Native Android Code 545 | 546 | The native code for the Android side of a Turbo Native Module requires: 547 | 548 | 1. to create a `CalculatorModule.java` that implements the module. 549 | 2. to update the `CalculatorPackage.java` created in the previous step. 550 | 551 | The final structure within the Android library should look like this: 552 | 553 | ```title="Android Folder Structure" 554 | android 555 | ├── build.gradle 556 | └── src 557 | └── main 558 | └── java 559 | └── com 560 | └── rtncalculator 561 | ├── CalculatorModule.java 562 | └── CalculatorPackage.java 563 | ``` 564 | 565 | ###### Creating the `CalculatorModule.java` 566 | 567 |
568 | Java 569 | 570 | ```java title="CalculatorModule.java" 571 | package com.rtncalculator; 572 | 573 | import androidx.annotation.NonNull; 574 | import com.facebook.react.bridge.NativeModule; 575 | import com.facebook.react.bridge.Promise; 576 | import com.facebook.react.bridge.ReactApplicationContext; 577 | import com.facebook.react.bridge.ReactContext; 578 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 579 | import com.facebook.react.bridge.ReactMethod; 580 | import java.util.Map; 581 | import java.util.HashMap; 582 | import com.rtncalculator.NativeRTNCalculatorSpec; 583 | 584 | public class CalculatorModule extends NativeRTNCalculatorSpec { 585 | 586 | public static String NAME = "RTNCalculator"; 587 | 588 | CalculatorModule(ReactApplicationContext context) { 589 | super(context); 590 | } 591 | 592 | @Override 593 | @NonNull 594 | public String getName() { 595 | return NAME; 596 | } 597 | 598 | @Override 599 | public void add(double a, double b, Promise promise) { 600 | promise.resolve(a + b); 601 | } 602 | } 603 | ``` 604 | 605 |
606 |
607 | Kotlin 608 | 609 | ```kotlin title="CalculatorModule.kt" 610 | package com.rtncalculator 611 | 612 | import com.facebook.react.bridge.Promise 613 | import com.facebook.react.bridge.ReactApplicationContext 614 | import com.rtncalculator.NativeRTNCalculatorSpec 615 | 616 | class CalculatorModule(reactContext: ReactApplicationContext) : NativeRTNCalculatorSpec(reactContext) { 617 | 618 | override fun getName() = NAME 619 | 620 | override fun add(a: Double, b: Double, promise: Promise) { 621 | promise.resolve(a + b) 622 | } 623 | 624 | companion object { 625 | const val NAME = "RTNCalculator" 626 | } 627 | } 628 | ``` 629 | 630 |
631 | 632 | This class implements the module itself, which extends the `NativeRTNCalculatorSpec` that was generated from the `NativeCalculator` JavaScript specification file. 633 | 634 | ###### Updating the `CalculatorPackage.java` 635 | 636 |
637 | Java 638 | 639 | ```diff title="CalculatorPackage.java" 640 | package com.rtncalculator; 641 | 642 | import androidx.annotation.Nullable; 643 | import com.facebook.react.bridge.NativeModule; 644 | import com.facebook.react.bridge.ReactApplicationContext; 645 | + import com.facebook.react.module.model.ReactModuleInfo; 646 | import com.facebook.react.module.model.ReactModuleInfoProvider; 647 | import com.facebook.react.BaseReactPackage; 648 | 649 | import java.util.Collections; 650 | import java.util.List; 651 | + import java.util.HashMap; 652 | + import java.util.Map; 653 | 654 | public class CalculatorPackage extends BaseReactPackage { 655 | 656 | @Nullable 657 | @Override 658 | public NativeModule getModule(String name, ReactApplicationContext reactContext) { 659 | + if (name.equals(CalculatorModule.NAME)) { 660 | + return new CalculatorModule(reactContext); 661 | + } else { 662 | return null; 663 | + } 664 | } 665 | 666 | 667 | @Override 668 | public ReactModuleInfoProvider getReactModuleInfoProvider() { 669 | - return null; 670 | + return () -> { 671 | + final Map moduleInfos = new HashMap<>(); 672 | + moduleInfos.put( 673 | + CalculatorModule.NAME, 674 | + new ReactModuleInfo( 675 | + CalculatorModule.NAME, 676 | + CalculatorModule.NAME, 677 | + false, // canOverrideExistingModule 678 | + false, // needsEagerInit 679 | + false, // isCxxModule 680 | + true // isTurboModule 681 | + )); 682 | + return moduleInfos; 683 | + }; 684 | } 685 | } 686 | ``` 687 | 688 |
689 | 690 |
691 | Kotlin 692 | 693 | ```diff title="CalculatorPackage.kt" 694 | package com.rtncalculator; 695 | 696 | import com.facebook.react.BaseReactPackage 697 | import com.facebook.react.bridge.NativeModule 698 | import com.facebook.react.bridge.ReactApplicationContext 699 | +import com.facebook.react.module.model.ReactModuleInfo 700 | import com.facebook.react.module.model.ReactModuleInfoProvider 701 | 702 | class CalculatorPackage : BaseReactPackage() { 703 | - override fun getModule(name: String?, reactContext: ReactApplicationContext): NativeModule? = null 704 | + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = 705 | + if (name == CalculatorModule.NAME) { 706 | + CalculatorModule(reactContext) 707 | + } else { 708 | + null 709 | + } 710 | 711 | - override fun getReactModuleInfoProvider() = ReactModuleInfoProvider? = null 712 | + override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { 713 | + mapOf( 714 | + CalculatorModule.NAME to ReactModuleInfo( 715 | + CalculatorModule.NAME, 716 | + CalculatorModule.NAME, 717 | + false, // canOverrideExistingModule 718 | + false, // needsEagerInit 719 | + false, // isCxxModule 720 | + true // isTurboModule 721 | + ) 722 | + ) 723 | + } 724 | } 725 | ``` 726 | 727 |
728 | 729 | 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. 730 | 731 | #### Final structure 732 | 733 | The final structure should look like this: 734 | 735 | ```sh 736 | TurboModulesGuide 737 | ├── MyApp 738 | └── RTNCalculator 739 | ├── android 740 | │ ├── build.gradle 741 | │ └── src 742 | │ └── main 743 | │ └── java 744 | │ └── com 745 | │ └── rtncalculator 746 | │ ├── CalculatorPackage.java 747 | │ └── CalculatorModule.java 748 | ├── generated 749 | ├── ios 750 | │ ├── RTNCalculator.h 751 | │ └── RTNCalculator.mm 752 | ├── js 753 | │ └── NativeCalculator.ts 754 | ├── package.json 755 | └── rtn-calculator.podspec 756 | ``` 757 | 758 | ### 5. Adding the Turbo Native Module to your App 759 | 760 | Now you can install and use the Turbo Native Module in your app. 761 | 762 | #### Shared 763 | 764 | 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: 765 | 766 | ```sh 767 | cd MyApp 768 | yarn add ../RTNCalculator 769 | ``` 770 | 771 | This command will add the `RTNCalculator` module to the `node_modules` of your app. 772 | 773 | #### iOS 774 | 775 | Then, you need to install the new dependencies in your iOS project. To do so, run these commands: 776 | 777 | ```sh 778 | cd ios 779 | RCT_NEW_ARCH_ENABLED=1 bundle exec pod install 780 | ``` 781 | 782 | 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**. 783 | 784 | > [!Note] 785 | > 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. 786 | 787 | #### Android 788 | 789 | Android configuration requires to enable the **New Architecture**: 790 | 791 | 1. Open the `android/gradle.properties` file 792 | 2. Scroll down to the end of the file and switch the `newArchEnabled` property from `false` to `true`. 793 | 794 | #### JavaScript 795 | 796 | Now you can use your Turbo Native Module calculator in your app! 797 | 798 | Here's an example App.js file using the `add` method: 799 | 800 |
801 | Flow 802 | 803 | ```typescript title="App.js" 804 | /** 805 | * Sample React Native App 806 | * https://github.com/facebook/react-native 807 | * 808 | * @format 809 | * @flow strict-local 810 | */ 811 | import React from "react"; 812 | import { useState } from "react"; 813 | import type { Node } from "react"; 814 | import { SafeAreaView, StatusBar, Text, Button } from "react-native"; 815 | import RTNCalculator from "rtn-calculator/js/NativeCalculator"; 816 | 817 | const App: () => Node = () => { 818 | const [result, setResult] = useState(null); 819 | return ( 820 | 821 | 822 | 823 | 3+7={result ?? "??"} 824 | 825 |
839 | 840 |
841 | TypeScript 842 | 843 | ```typescript title="App.tsx" 844 | /** 845 | * Sample React Native App 846 | * https://github.com/facebook/react-native 847 | * 848 | * @format 849 | */ 850 | import React from "react"; 851 | import { useState } from "react"; 852 | import { SafeAreaView, StatusBar, Text, Button } from "react-native"; 853 | import RTNCalculator from "rtn-calculator/js/NativeRTNCalculator"; 854 | 855 | const App: () => JSX.Element = () => { 856 | const [result, setResult] = useState(null); 857 | return ( 858 | 859 | 860 | 861 | 3+7={result ?? "??"} 862 | 863 |
877 | 878 | Try this out to see your Turbo Native Module in action! 879 | 880 | --- 881 | 882 | > [!IMPORTANT] 883 | > This documentation is still experimental and details are subject to changes as we iterate. 884 | > Feel free to share your feedback on this [discussion](https://github.com/reactwg/react-native-new-architecture/discussions/8). 885 | > 886 | > Moreover, it contains **several manual steps**. Please note that this won't be representative of the final developer experience once the New Architecture is stable. We're working on tools, templates and libraries to help you get started fast on the New Architecture, without having to go through the whole setup. 887 | 888 | ## Add Event Emitting Capabilities 889 | 890 | Starting from React Native 0.76, Turbo Native Modules have a new, type-safe API to emit events from the Native layer to the JS layer. 891 | 892 | This capability can be helpful to notify the JS layer about changes in the platforms (for example a change in appearence) or for tasks that ended asynchronously. 893 | It is also a great way to send events from your modules to your components. 894 | 895 | To add this feature, we have to apply some changes at the various layers. 896 | 897 | ### 1. Add the Event Emitter to the JS Spec. 898 | 899 | First of all, our specs needs to define an EventEmitter function. 900 | 901 | Modify the specs as it follows: 902 | 903 |
904 | Flow 905 | 906 | ```diff title="NativeCalculator.js with Event Emitter" 907 | // @flow 908 | import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; 909 | import {TurboModuleRegistry} from 'react-native'; 910 | + import type {EventEmitter} from 'react-native/Libraries/Types/CodegenTypes'; 911 | 912 | export interface Spec extends TurboModule { 913 | add(a: number, b: number): Promise; 914 | + onValueChanged: EventEmitter 915 | } 916 | export default (TurboModuleRegistry.get( 917 | 'RTNCalculator' 918 | ): ?Spec); 919 | ``` 920 | 921 |
922 | 923 |
924 | TypeScript 925 | 926 | ```diff title="NativeCalculator.ts with Event Emitter" 927 | import { TurboModule, TurboModuleRegistry } from "react-native"; 928 | + import type {EventEmitter} from 'react-native/Libraries/Types/CodegenTypes'; 929 | 930 | export interface Spec extends TurboModule { 931 | add(a: number, b: number): Promise; 932 | + readonly onValueChanged: EventEmitter 933 | } 934 | 935 | export default TurboModuleRegistry.get("RTNCalculator") as Spec | null; 936 | ``` 937 | 938 |
939 | 940 | This new property will tell codegen to generate all the new properties and methods to wire the Event Emitter in native code 941 | 942 | ### 2. Run Codegen 943 | 944 | This step is the same step of the previous guide. 945 | 946 | #### iOS 947 | 948 | Just run again: 949 | 950 | ```sh title="Running Codegen for iOS" 951 | cd MyApp 952 | yarn add ../RTNCalculator 953 | cd .. 954 | node MyApp/node_modules/react-native/scripts/generate-codegen-artifacts.js \ 955 | --targetPlatform ios \ 956 | --path MyApp/ \ 957 | --outputPath RTNCalculator/generated/ 958 | ``` 959 | 960 | To see the generated output. 961 | 962 | In this case, the Codegen will create a new base class called `NativeRTNCalculatorSpecBase` that contains the glue code that allows the Event Emitter to work properly. 963 | 964 | #### Android 965 | 966 | Just run again: 967 | 968 | ```sh title="Running Codegen for Android" 969 | cd MyApp 970 | yarn add ../RTNCalculator 971 | cd android 972 | ./gradlew generateCodegenArtifactsFromSchema 973 | ``` 974 | 975 | For Android, the output is not much different. The classes that were generated before will now contains the new code to make the Event Emitter work. 976 | 977 | ### 3. Update the Native Code 978 | 979 | Now that Codegen generated some new code, we need to update our existing native code to use the newly generated code. 980 | 981 | #### iOS 982 | 983 | For iOS, we need two steps: 984 | 1. inherit from the newly created class 985 | 2. emit the event 986 | 987 | ##### Inherith from the Newly created class 988 | 989 | 1. Open the `RTNCalculator.h` file 990 | 2. Modify it as it follows: 991 | 992 | ```diff title="RTNCalculator.h with Event Emitter" 993 | #import 994 | 995 | NS_ASSUME_NONNULL_BEGIN 996 | 997 | - @interface RTNCalculator : NSObject 998 | + @interface RTNCalculator : NativeRTNCalculatorSpecBase 999 | 1000 | @end 1001 | 1002 | NS_ASSUME_NONNULL_END 1003 | ``` 1004 | 1005 | ##### Emit the event 1006 | 1007 | 1. Open the `RTNCalculator.mm` file 1008 | 2. Modify it as it follows: 1009 | 1010 | ```diff title="RTNCalculator.mm with Event Emitter" 1011 | #import "RTNCalculator.h" 1012 | 1013 | @implementation RTNCalculator 1014 | 1015 | RCT_EXPORT_MODULE() 1016 | 1017 | - (void)add:(double)a b:(double)b resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { 1018 | NSNumber *result = [[NSNumber alloc] initWithInteger:a+b]; 1019 | resolve(result); 1020 | + [self emitOnValueChanged:@(result)]; 1021 | } 1022 | 1023 | - (std::shared_ptr)getTurboModule: 1024 | (const facebook::react::ObjCTurboModule::InitParams &)params 1025 | { 1026 | return std::make_shared(params); 1027 | } 1028 | 1029 | @end 1030 | ``` 1031 | 1032 | #### Android 1033 | 1034 | On Android, the CalculatorModule already inherits from the right class. So, the only step required is to emit the event. 1035 | 1036 | 1. Open the `CalculatorModule.java` file 1037 | 2. Modify it as it follows: 1038 | 1039 | ```diff title="CalculatorModule.java" 1040 | 1041 | public class CalculatorModule extends NativeCalculatorSpec { 1042 | 1043 | public static String NAME = "RTNCalculator"; 1044 | 1045 | CalculatorModule(ReactApplicationContext context) { 1046 | super(context); 1047 | } 1048 | 1049 | @Override 1050 | @NonNull 1051 | public String getName() { 1052 | return NAME; 1053 | } 1054 | 1055 | @Override 1056 | public void add(double a, double b, Promise promise) { 1057 | promise.resolve(a + b); 1058 | + emitOnValueChanged(a + b); 1059 | } 1060 | } 1061 | ``` 1062 | 1063 | ### 4. Listen for the Event in JS. 1064 | 1065 | Now that all the Native Code is properly wired, we only need to listen for the event on JS side. 1066 | 1067 | To do so, we can modify our app code as it follows: 1068 | 1069 | ```diff 1070 | /** 1071 | * Sample React Native App 1072 | * https://github.com/facebook/react-native 1073 | * 1074 | * @format 1075 | */ 1076 | import React from "react"; 1077 | import { useState } from "react"; 1078 | - import { SafeAreaView, StatusBar, Text, Button } from "react-native"; 1079 | + import { Alert, EventSubscription, SafeAreaView, StatusBar, Text, Button } from "react-native"; 1080 | import RTNCalculator from "rtn-calculator/js/NativeRTNCalculator"; 1081 | 1082 | const App: () => JSX.Element = () => { 1083 | const [result, setResult] = useState(null); 1084 | + const listenerSubscription = React.useRef(null); 1085 | 1086 | + React.useEffect(() => { 1087 | + listenerSubscription.current = RTNCalculator.onValueChanged((data) => { Alert.alert(`Result: ${data}`) }); 1088 | 1089 | + return () => { 1090 | + listenerSubscription.current?.remove(); 1091 | + listenerSubscription.current = null; 1092 | + } 1093 | + }, []) 1094 | 1095 | 1096 | return ( 1097 | 1098 | 1099 | 1100 | 3+7={result ?? "??"} 1101 | 1102 |