diff --git a/README.md b/README.md
index 3e3825695..b9fdf1f75 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ If you want to create your own React Native module, scaffolding the project can
- [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) and [Husky](https://github.com/typicode/husky) pre-configured
- Bob pre-configured to compile your files
- CircleCI pre-configured to run tests on the CI
+- Use C++ code to boost performance of your modules
@@ -26,6 +27,7 @@ Bob can build code for following targets:
- Flow definitions (copies .js files to .flow files)
- TypeScript definitions (uses `tsc` to generate declaration files)
- Android AAR files
+- C++ modules
## Why
diff --git a/src/create.ts b/src/create.ts
index de48fb5a5..5aaa72039 100644
--- a/src/create.ts
+++ b/src/create.ts
@@ -12,6 +12,7 @@ import pack from '../package.json';
const TEMPLATE = path.resolve(__dirname, '../templates/library');
const BINARIES = /(gradlew|\.(jar|keystore|png|jpg|gif))$/;
+const CPP_FILES = path.resolve(__dirname, '../templates/cppLibrary');
export default async function create(argv: yargs.Arguments) {
const folder = path.join(process.cwd(), argv.name);
@@ -51,6 +52,7 @@ export default async function create(argv: yargs.Arguments) {
authorEmail,
authorUrl,
githubUrl: repo,
+ useCpp,
} = (await inquirer.prompt([
{
type: 'input',
@@ -118,6 +120,12 @@ export default async function create(argv: yargs.Arguments) {
},
validate: input => /^https?:\/\//.test(input) || 'Must be a valid URL',
},
+ {
+ type: 'confirm',
+ name: 'useCpp',
+ message: 'Does your library use C++ code?',
+ default: false,
+ },
])) as {
slug: string;
description: string;
@@ -125,6 +133,7 @@ export default async function create(argv: yargs.Arguments) {
authorEmail: string;
authorUrl: string;
githubUrl: string;
+ useCpp: boolean;
};
const project = slug.replace(/^(react-native-|@[^/]+\/)/, '');
@@ -143,6 +152,7 @@ export default async function create(argv: yargs.Arguments) {
.slice(1)}`,
package: slug.replace(/[^a-z0-9]/g, '').toLowerCase(),
podspec: slug.replace(/[^a-z0-9]+/g, '-').replace(/^-/, ''),
+ useCpp,
},
author: {
name: authorName,
@@ -176,6 +186,9 @@ export default async function create(argv: yargs.Arguments) {
};
await copyDir(TEMPLATE, folder);
+ if (options.project.useCpp) {
+ await copyDir(CPP_FILES, folder);
+ }
try {
await spawn.sync(
diff --git a/templates/cppLibrary/android/CMakeLists.txt b/templates/cppLibrary/android/CMakeLists.txt
new file mode 100644
index 000000000..b5d27b7f0
--- /dev/null
+++ b/templates/cppLibrary/android/CMakeLists.txt
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+set (CMAKE_VERBOSE_MAKEFILE ON)
+set (CMAKE_CXX_STANDARD 11)
+
+add_library(cpp
+ SHARED
+ ../cpp/example.cpp
+ cpp-adapter.cpp
+)
+
+# Specifies a path to native header files.
+include_directories(
+ ../cpp
+)
diff --git a/templates/cppLibrary/android/cpp-adapter.cpp b/templates/cppLibrary/android/cpp-adapter.cpp
new file mode 100644
index 000000000..96adc6b33
--- /dev/null
+++ b/templates/cppLibrary/android/cpp-adapter.cpp
@@ -0,0 +1,8 @@
+#include
+#include "example.h"
+
+extern "C"
+JNIEXPORT jint JNICALL
+Java_com_<%= project.package %>_<%= project.name %>Module_nativeMultiply(JNIEnv *env, jclass type, jint a, jint b) {
+ return example::multiply(a, b);
+}
diff --git a/templates/cppLibrary/cpp/example.cpp b/templates/cppLibrary/cpp/example.cpp
new file mode 100644
index 000000000..4a7550b13
--- /dev/null
+++ b/templates/cppLibrary/cpp/example.cpp
@@ -0,0 +1,7 @@
+#include "example.h"
+
+namespace example {
+ int multiply(int a, int b) {
+ return a * b;
+ }
+}
diff --git a/templates/cppLibrary/cpp/example.h b/templates/cppLibrary/cpp/example.h
new file mode 100644
index 000000000..6b81e1074
--- /dev/null
+++ b/templates/cppLibrary/cpp/example.h
@@ -0,0 +1,8 @@
+#ifndef EXAMPLE_H
+#define EXAMPLE_H
+
+namespace example {
+ int multiply(int a, int b);
+}
+
+#endif /* EXAMPLE_H */
diff --git a/templates/library/<%= project.podspec %>.podspec b/templates/library/<%= project.podspec %>.podspec
index f1a474ba8..e27ca3f3b 100644
--- a/templates/library/<%= project.podspec %>.podspec
+++ b/templates/library/<%= project.podspec %>.podspec
@@ -13,7 +13,12 @@ Pod::Spec.new do |s|
s.platforms = { :ios => "9.0" }
s.source = { :git => "<%= repo %>.git", :tag => "#{s.version}" }
- s.source_files = "ios/**/*.{h,m}"
+ s.source_files = "ios/**/*.{h,m,mm}"
+ <% if(project.useCpp==true){ %>
+ s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}"
+ <% } else{ %>
+ s.source_files = "ios/**/*.{h,m,mm}"
+ <% } %>
s.dependency "React"
end
diff --git a/templates/library/android/build.gradle b/templates/library/android/build.gradle
index 2dfe7f4d0..e0f9f1b8b 100644
--- a/templates/library/android/build.gradle
+++ b/templates/library/android/build.gradle
@@ -33,7 +33,22 @@ android {
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
versionCode 1
versionName "1.0"
+ <%if (project.useCpp==true) {%>
+ externalNativeBuild {
+ cmake {
+ cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
+ abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
+ }
+ }
+ <%}%>
+ }
+ <%if (project.useCpp==true) {%>
+ externalNativeBuild {
+ cmake {
+ path "CMakeLists.txt"
+ }
}
+ <%}%>
buildTypes {
release {
minifyEnabled false
diff --git a/templates/library/android/src/main/java/com/<%= project.package %>/<%= project.name %>Module.kt b/templates/library/android/src/main/java/com/<%= project.package %>/<%= project.name %>Module.kt
index e2d583d9b..c8d8e4ca9 100644
--- a/templates/library/android/src/main/java/com/<%= project.package %>/<%= project.name %>Module.kt
+++ b/templates/library/android/src/main/java/com/<%= project.package %>/<%= project.name %>Module.kt
@@ -17,4 +17,23 @@ class <%= project.name %>Module(reactContext: ReactApplicationContext) : ReactCo
fun getDeviceName(promise: Promise) {
promise.resolve(android.os.Build.MODEL)
}
+ <%if (project.useCpp==true) {%>
+
+ @ReactMethod
+ fun multiply(a: Int, b: Int, promise: Promise) {
+ promise.resolve(nativeMultiply(a, b));
+ }
+
+ external fun nativeMultiply(a: Int, b: Int): Int;
+
+ companion object
+ {
+
+ // Used to load the 'native-lib' library on application startup.
+ init
+ {
+ System.loadLibrary("cpp")
+ }
+ }
+ <%}%>
}
diff --git a/templates/library/example/src/App.tsx b/templates/library/example/src/App.tsx
index 823ed601f..d787e374c 100644
--- a/templates/library/example/src/App.tsx
+++ b/templates/library/example/src/App.tsx
@@ -4,14 +4,24 @@ import <%= project.name %> from '<%= project.slug %>';
export default function App() {
const [deviceName, setDeviceName] = React.useState('');
+ <%if (project.useCpp==true) {%>
+ const [value, setValue] = React.useState();
+ <%}%>
+
React.useEffect(() => {
<%= project.name %>.getDeviceName().then(setDeviceName);
+ <%if (project.useCpp==true) {%>
+ <%= project.name %>.multiply(2, 3).then(setValue);
+ <%}%>
}, []);
return (
Device name: {deviceName}
+ <%if (project.useCpp==true) {%>
+ C++ mulitply value: {value}
+ <%}%>
);
}
diff --git a/templates/library/ios/<%= project.name %>.h b/templates/library/ios/<%= project.name %>.h
index aeac1a032..88fc80896 100644
--- a/templates/library/ios/<%= project.name %>.h
+++ b/templates/library/ios/<%= project.name %>.h
@@ -1,5 +1,13 @@
#import
+<%if (project.useCpp==true) {%>
+#ifdef __cplusplus
+
+#import "example.h"
+
+#endif
+<%}%>
+
@interface <%= project.name %> : NSObject
@end
diff --git a/templates/library/ios/<%= project.name %>.m b/templates/library/ios/<%= project.name %>.mm
similarity index 59%
rename from templates/library/ios/<%= project.name %>.m
rename to templates/library/ios/<%= project.name %>.mm
index af2d6bf4a..761a17972 100644
--- a/templates/library/ios/<%= project.name %>.m
+++ b/templates/library/ios/<%= project.name %>.mm
@@ -15,4 +15,14 @@ @implementation <%= project.name %>
resolve(deviceInfo.name);
}
+<%if (project.useCpp==true) {%>
+RCT_EXPORT_METHOD(multiply:(nonnull NSNumber*)a withB:(nonnull NSNumber*)b resolver:(RCTPromiseResolveBlock)resolve
+withReject:(RCTPromiseRejectBlock)reject)
+{
+ long result = example::multiply([a longValue], [b longValue]);
+
+ resolve(@(result));
+}
+<%}%>
+
@end
diff --git a/templates/library/ios/<%= project.name %>.xcodeproj/project.pbxproj b/templates/library/ios/<%= project.name %>.xcodeproj/project.pbxproj
index 01fe74036..b3e0b6a58 100644
--- a/templates/library/ios/<%= project.name %>.xcodeproj/project.pbxproj
+++ b/templates/library/ios/<%= project.name %>.xcodeproj/project.pbxproj
@@ -7,7 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
- B3E7B58A1CC2AC0600A0062D /* <%= project.name %>.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* <%= project.name %>.m */; };
+<%if (project.useCpp==true) {%>
+ 5E46D8CD2428F78900513E24 /* example.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E46D8CB2428F78900513E24 /* example.cpp */; };
+<%}%>
+ 5E555C0D2413F4C50049A1A2 /* <%= project.name %>.mm in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* <%= project.name %>.mm */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -24,8 +27,12 @@
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* lib<%= project.name %>.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = lib<%= project.name %>.a; sourceTree = BUILT_PRODUCTS_DIR; };
+<%if (project.useCpp==true) {%>
+ 5E46D8CB2428F78900513E24 /* example.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = example.cpp; path = ../cpp/example.cpp; sourceTree = ""; };
+ 5E46D8CC2428F78900513E24 /* example.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = example.h; path = ../cpp/example.h; sourceTree = ""; };
+<%}%>
B3E7B5881CC2AC0600A0062D /* <%= project.name %>.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = <%= project.name %>.h; sourceTree = ""; };
- B3E7B5891CC2AC0600A0062D /* <%= project.name %>.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = <%= project.name %>.m; sourceTree = ""; };
+ B3E7B5891CC2AC0600A0062D /* <%= project.name %>.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = <%= project.name %>.mm; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -50,8 +57,12 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
+<%if (project.useCpp==true) {%>
+ 5E46D8CB2428F78900513E24 /* example.cpp */,
+ 5E46D8CC2428F78900513E24 /* example.h */,
+<%}%>
B3E7B5881CC2AC0600A0062D /* <%= project.name %>.h */,
- B3E7B5891CC2AC0600A0062D /* <%= project.name %>.m */,
+ B3E7B5891CC2AC0600A0062D /* <%= project.name %>.mm */,
134814211AA4EA7D00B7C361 /* Products */,
);
sourceTree = "";
@@ -95,6 +106,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
+ English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
@@ -112,7 +124,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- B3E7B58A1CC2AC0600A0062D /* <%= project.name %>.m in Sources */,
+<%if (project.useCpp==true) {%>
+ 5E46D8CD2428F78900513E24 /* example.cpp in Sources */,
+<%}%>
+ 5E555C0D2413F4C50049A1A2 /* <%= project.name %>.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -216,7 +231,7 @@
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
- "$(inherited)",
+ "$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
diff --git a/templates/library/src/index.tsx b/templates/library/src/index.tsx
index 13dc979d3..ea788ba8b 100644
--- a/templates/library/src/index.tsx
+++ b/templates/library/src/index.tsx
@@ -2,6 +2,9 @@ import { NativeModules } from 'react-native';
type <%= project.name %>Type = {
getDeviceName(): Promise;
+ <%if (project.useCpp==true) {%>
+ multiply(a: number, b: number): Promise;
+ <%}%>
};
const { <%= project.name %> } = NativeModules;
diff --git a/yarn.lock b/yarn.lock
index 77f764ac1..cfab0ae2f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2283,6 +2283,13 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
+debug@^3.2.6:
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+ integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+ dependencies:
+ ms "^2.1.1"
+
decamelize-keys@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
@@ -5338,11 +5345,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-safe-buffer@~5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
- integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
-
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"