C++ Turbo Native Modules
这个文档仍然是实验性的,随着我们的迭代,细节会有变化。欢迎在工作小组内的讨论中分享你的反馈。
此外,它还包含几个手动步骤。请注意新架构尚未稳定下来,最终的开发者体验会继续迭代改善。我们正在努力开发工具、模板和库,以帮助你在新架构上快速入门,而不需要经历整个设置过程。
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).
Before continuing with this guide, please read the Turbo Native Modules section. As a further reference, we prepared an example for the RNTester app (NativeCxxModuleExample) and a sample run in our community repository (run/pure-cxx-module).
C++ Turbo Native Modules work with the New Architecture enabled. To migrate to the New Architecture, follow the Migration guide
How to Create a C++ Turbo Native Module
To create a C++ Turbo Native Module, you need to:
- Define the JavaScript specification.
- Configure Codegen to generate the scaffolding.
- Register the native module.
- Write the native code to finish implementing the module.
Setup a Test App for the New Architecture
As first step, create a new application:
npx react-native@v0.71.0-rc.3 init CxxTurboModulesGuide --version v0.71.0-rc.3
cd CxxTurboModulesGuide
yarn install
On Android enable the New Architecture by modifying the android/gradle.properties
file:
- newArchEnabled=false
+ newArchEnabled=true
On iOS enable the New Architecture when running pod install
in the ios
folder:
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
Turbo Module Folder Setup
Create a tm
folder inside the project. It will contain all C++ TurboModules of your application. The final result should look like this:
CxxTurboModulesGuide
├── android
├── ios
├── js
└── tm
1. JavaScript Specification
Create the following spec inside the tm
folder:
- TypeScript
- Flow
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
// import type {TurboModule} from 'react-native'; in future versions
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
readonly reverseString: (input: string) => string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
);
// @flow
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
// import type {TurboModule} from 'react-native'; in future versions
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
+reverseString: (input: string) => string;
}
export default (TurboModuleRegistry.getEnforcing<Spec>(
'NativeSampleModule',
): Spec);
2. Codegen Configuration
Next, you need to add some configuration for Codegen.
Application
Update your app's package.json
file with the following entries:
{
// ...
"description": "React Native with Cxx Turbo Native Modules",
"author": "<Your Name> <your_email@your_provider.com> (https://github.com/<your_github_handle>)",
"license": "MIT",
"homepage": "https://github.com/<your_github_handle>/#readme",
// ...
"codegenConfig": {
"name": "AppSpecs",
"type": "all",
"jsSrcsDir": "tm",
"android": {
"javaPackageName": "com.facebook.fbreact.specs"
}
}
}
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.
C++ Turbo Native Modules don't autolink and need to be manually included into the app with the described steps below.
iOS: Create the podspec
file
For iOS, you'll need to create a AppTurboModules.podspec
file in the tm
folder - which will look like:
require "json"
package = JSON.parse(File.read(File.join(__dir__, "../package.json")))
Pod::Spec.new do |s|
s.name = "AppTurboModules"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "12.4" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }
s.source_files = "**/*.{h,cpp}"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
install_modules_dependencies(s)
end
You need to add it as a dependency to your application in ios/Podfile
, e.g., after the use_react_native!(...)
section:
if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
pod 'AppTurboModules', :path => "./../tm"
end
Android: build.gradle
, CMakeLists.txt
, Onload.cpp
For Android, you'll need to create a CMakeLists.txt
file in the tm
folder - which will look like:
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
add_compile_options(
-fexceptions
-frtti
-std=c++17)
file(GLOB tm_SRC CONFIGURE_DEPENDS *.cpp)
add_library(tm STATIC ${tm_SRC})
target_include_directories(tm PUBLIC .)
target_include_directories(react_codegen_AppSpecs PUBLIC .)
target_link_libraries(tm
jsi
react_nativemodule_core
react_codegen_AppSpecs)
It defines the tm
folder as a source for native code and sets up necessary dependencies.
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:
android {
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt"
}
}
}
Ensure to pick the correct android/app/build.gradle file and not android/build.gradle.
3. Module Registration
iOS
To register a C++ Turbo Native Module in your app you will need to update ios/CxxTurboModulesGuide/AppDelegate.mm
with the following entries:
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
+ #import <React/CoreModulesPlugins.h>
+ #import <ReactCommon/RCTTurboModuleManager.h>
+ #import <TurboModules/NativeSampleModule.h>
+ @interface AppDelegate () <RCTTurboModuleManagerDelegate> {}
+ @end
// ...
᠆ (Class)getModuleClassFromName:(const char *)name
{
return RCTCoreModulesClassProvider(name);
}
+ #pragma mark RCTTurboModuleManagerDelegate
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
+ jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
+ {
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
+ return nullptr;
+ }
This will instantiante a NativeSampleModule
associated with the name NativeSampleModule
as defined in our JavaScript spec file earlier.
Android
Android apps aren't setup for native code compilation by default.
1.) Create the folder android/app/src/main/jni
2.) Copy CMakeLists.txt
and Onload.cpp
from node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup into the android/app/src/main/jni
folder.
Update Onload.cpp
with the following entries:
// ...
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <rncli.h>
+ #include <NativeSampleModule.h>
// ...
std::shared_ptr<TurboModule> cxxModuleProvider(
const std::string &name,
const std::shared_ptr<CallInvoker> &jsInvoker) {
+ if (name == "NativeSampleModule") {
+ return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
+ }
return nullptr;
}
// ...
Update CMakeLists.txt
with the following entries, e.g., at the very end of that file:
// ...
# This file includes all the necessary to let you build your application with the New Architecture.
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
+ # App needs to add and link against tm (TurboModules) folder
+ add_subdirectory(${REACT_ANDROID_DIR}/../../../tm/ tm_build)
+ target_link_libraries(${CMAKE_PROJECT_NAME} tm)
This will instantiante a NativeSampleModule
associated with the name NativeSampleModule
as defined in our JavaScript spec file earlier.
4. C++ Native Code
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:
- Run Codegen to see what it generates.
- Write your native code, implementing the generated interfaces.
Run Codegen
Follow the Codegen guide for general information.
On iOS Codegen is run each time you execute in the ios
folder:
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
You can inspect the generated AppSpecsJSI.h
and AppSpecsJSI-generated.cpp
files inside the CxxTurboModulesGuide/ios/build/generated/ios
folder.
Those files are prefixed with AppSpecs
as this matches the codegenConfig.name
parameter added earlier to package.json
.
On Android Codegen is run each time you execute:
yarn android
You can inspect the generated AppSpecsJSI.h
and AppSpecsJSI-generated.cpp
files inside the CxxTurboModulesGuide/android/app/build/generated/source/codegen/jni
folder.
You only need to re-run codegen if you have changed your JavaScript spec.
The C++ function generated for our JavaScript spec file looks like:
virtual jsi::String reverseString(jsi::Runtime &rt, jsi::String input) = 0;
You can directly work with the lower level jsi::
types - but for convience C++ Turbo Native Modules automatically bridge
into std::
types for you.
Implementation
Now create a NativeSampleModule.h
file with the following content:
Due to current differences in the CMake and CocoaPod setup we need some creativity to include the correct Codegen header on each platform.
#pragma once
#if __has_include(<React-Codegen/AppSpecsJSI.h>) // CocoaPod headers on Apple
#include <React-Codegen/AppSpecsJSI.h>
#elif __has_include("AppSpecsJSI.h") // CMake headers on Android
#include "AppSpecsJSI.h"
#endif
#include <memory>
#include <string>
namespace facebook::react {
class NativeSampleModule : public NativeSampleModuleCxxSpec<NativeSampleModule> {
public:
NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker);
std::string reverseString(jsi::Runtime& rt, std::string input);
};
} // namespace facebook::react
In this case you can use any C++ type which bridges
to a jsi::String
- default or custom one. 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.
Now add a NativeSampleModule.cpp
file with an implementation for it:
#include "NativeSampleModule.h"
namespace facebook::react {
NativeSampleModule::NativeSampleModule(std::shared_ptr<CallInvoker> jsInvoker)
: NativeSampleModuleCxxSpec(std::move(jsInvoker)) {}
std::string NativeSampleModule::reverseString(jsi::Runtime& rt, std::string input) {
return std::string(input.rbegin(), input.rend());
}
} // namespace facebook::react
As we have added new C++ files run in the ios
folder:
RCT_NEW_ARCH_ENABLED=1 bundle exec pod install
for iOS. In Xcode they appear under the Pods
target in the Development Pods \ TurboModules
subfolder.
You should now be able to compile your app both on Android and iOS.
CxxTurboModulesGuide
├── android
│ └── app
│ │── src
│ │ └── main
│ │ └── jni
│ │ ├── CMakeLists.txt
│ │ └── OnLoad.cpp
│ └── build.gradle (updated)
├── ios
│ └── CxxTurboModulesGuide
│ └── AppDelegate.mm (updated)
├── js
└── App.tsx|jsx (updated)
└── tm
├── CMakeLists.txt
├── NativeSampleModule.h
├── NativeSampleModule.cpp
├── NativeSampleModule.ts|js
└── TurboModules.podspec
5. Adding the C++ Turbo Native Module to your App
For demo purposes we can update our app's App.tsx|jsx
with the following entries:
//...
import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
+ import NativeSampleModule from './tm/NativeSampleModule';
//...
<View
style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
+ <Section title="Cxx TurboModule">
+ NativeSampleModule.reverseString(...) ={' '}
+ {NativeSampleModule.reverseString(
+ 'the quick brown fox jumps over the lazy dog'
+ )}
+ </Section>;
<Section title="Step One">
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
screen and then come back to see your edits.
</Section>
//...
Run the app to see your C++ Turbo Native Module in action!
App TurboModuleProvider [Optional]
You can avoid some code duplication once you added multiple C++ Turbo Native Modules by declaring an AppTurboModuleProvider:
#pragma once
#include <ReactCommon/TurboModuleBinding.h>
#include <memory>
#include <string>
namespace facebook::react {
class AppTurboModuleProvider {
public:
std::shared_ptr<TurboModule> getTurboModule(
const std::string& name,
std::shared_ptr<CallInvoker> jsInvoker) const;
};
} // namespace facebook::react
And implementing it:
#include "AppTurboModuleProvider.h"
#include "NativeSampleModule.h"
namespace facebook::react {
std::shared_ptr<TurboModule> AppTurboModuleProvider::getTurboModule(
const std::string& name,
std::shared_ptr<CallInvoker> jsInvoker) const {
if (name == "NativeSampleModule") {
return std::make_shared<facebook::react::NativeSampleModule>(jsInvoker);
}
// Other C++ Turbo Native Modules for you app
return nullptr;
}
} // namespace facebook::react
And then re-using it in OnLoad.cpp
for Android and AppDelegate.mm
for iOS, e.g., via:
static facebook::react::AppTurboModuleProvider appTurboModuleProvider;
return appTurboModuleProvider.getTurboModule(name, jsInvoker);
in the corresponding functions.
Calling OS specific APIs
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.
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.
Extending C++ Turbo Native Modules
If you need to support some types that are not supported yet, have a look at this other guide.