Skip to content

Commit ad43843

Browse files
committed
feat: add kotlin support to turbo module template
1 parent 722afae commit ad43843

File tree

16 files changed

+228
-42
lines changed

16 files changed

+228
-42
lines changed

.github/workflows/build-templates.yml

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ jobs:
1010
matrix:
1111
type:
1212
- module
13+
- module-legacy
14+
- module-mixed
15+
- module-turbo
1316
- view
1417
language:
1518
- java-objc
@@ -19,22 +22,26 @@ jobs:
1922
- cpp
2023
example:
2124
- native
25+
exclude:
26+
- type: module-turbo
27+
language: java-swift
28+
- type: module-turbo
29+
language: kotlin-swift
30+
- type: module-mixed
31+
language: java-swift
32+
- type: module-mixed
33+
language: kotlin-swift
34+
- type: module-legacy
35+
language: java-swift
36+
- type: module-legacy
37+
language: kotlin-swift
2238
include:
2339
- type: module
2440
language: js
2541
example: expo
2642
- type: module
2743
language: js
2844
example: native
29-
- type: module-legacy
30-
language: java-objc
31-
example: native
32-
- type: module-turbo
33-
language: java-objc
34-
example: native
35-
- type: module-mixed
36-
language: java-objc
37-
example: native
3845

3946
steps:
4047
- name: Checkout

packages/create-react-native-library/src/index.ts

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ const OBJC_FILES = {
4949
};
5050

5151
const KOTLIN_FILES = {
52-
module: path.resolve(__dirname, '../templates/kotlin-library'),
52+
module_legacy: path.resolve(__dirname, '../templates/kotlin-library-legacy'),
53+
module_turbo: path.resolve(__dirname, '../templates/kotlin-library-turbo'),
54+
module_mixed: path.resolve(__dirname, '../templates/kotlin-library-mixed'),
5355
view: path.resolve(__dirname, '../templates/kotlin-view-library'),
5456
};
5557

@@ -287,21 +289,25 @@ async function create(argv: yargs.Arguments<any>) {
287289
],
288290
},
289291
'languages': {
290-
type: (_, values) =>
291-
values.type === 'library' ||
292-
values.type === 'module-turbo' ||
293-
values.type === 'module-mixed'
294-
? null
295-
: 'select',
292+
type: (_, values) => (values.type !== 'library' ? 'select' : null),
296293
name: 'languages',
297294
message: 'Which languages do you want to use?',
298-
choices: [
299-
{ title: 'Java & Objective-C', value: 'java-objc' },
300-
{ title: 'Java & Swift', value: 'java-swift' },
301-
{ title: 'Kotlin & Objective-C', value: 'kotlin-objc' },
302-
{ title: 'Kotlin & Swift', value: 'kotlin-swift' },
303-
{ title: 'C++ for both iOS & Android', value: 'cpp' },
304-
],
295+
choices: (_, values) => {
296+
const languages = [
297+
{ title: 'Java & Objective-C', value: 'java-objc' },
298+
{ title: 'Kotlin & Objective-C', value: 'kotlin-objc' },
299+
];
300+
301+
if (values.type !== 'module-turbo' && values.type !== 'module-mixed') {
302+
languages.push(
303+
{ title: 'Java & Swift', value: 'java-swift' },
304+
{ title: 'Kotlin & Swift', value: 'kotlin-swift' },
305+
{ title: 'C++ for both iOS & Android', value: 'cpp' }
306+
);
307+
}
308+
309+
return languages;
310+
},
305311
},
306312
'example': {
307313
type: (_, values) => (values.type === 'library' ? 'select' : null),
@@ -440,7 +446,7 @@ async function create(argv: yargs.Arguments<any>) {
440446

441447
await fs.mkdirp(folder);
442448

443-
const spinner = ora('Generating example app').start();
449+
const spinner = ora('Generating example').start();
444450

445451
await generateExampleApp({
446452
type: example,
@@ -449,9 +455,7 @@ async function create(argv: yargs.Arguments<any>) {
449455
isNewArch: options.project.turbomodule,
450456
});
451457

452-
spinner.succeed(
453-
`Project created successfully at ${kleur.yellow(argv.name)}!\n`
454-
);
458+
spinner.text = 'Copying files';
455459

456460
await copyDir(COMMON_FILES, folder);
457461

@@ -493,14 +497,12 @@ async function create(argv: yargs.Arguments<any>) {
493497
await copyDir(OBJC_FILES[moduleType], folder);
494498
}
495499

496-
if (options.project.kotlin) {
497-
await copyDir(KOTLIN_FILES[moduleType], folder);
500+
const android_files = options.project.kotlin ? KOTLIN_FILES : JAVA_FILES;
501+
502+
if (moduleType === 'module') {
503+
await copyDir(android_files[`${moduleType}_${architecture}`], folder);
498504
} else {
499-
if (moduleType === 'module') {
500-
await copyDir(JAVA_FILES[`${moduleType}_${architecture}`], folder);
501-
} else {
502-
await copyDir(JAVA_FILES[moduleType], folder);
503-
}
505+
await copyDir(android_files[moduleType], folder);
504506
}
505507

506508
if (options.project.cpp) {
@@ -532,6 +534,10 @@ async function create(argv: yargs.Arguments<any>) {
532534
// Ignore error
533535
}
534536

537+
spinner.succeed(
538+
`Project created successfully at ${kleur.yellow(argv.name)}!\n`
539+
);
540+
535541
const platforms = {
536542
ios: { name: 'iOS', color: 'cyan' },
537543
android: { name: 'Android', color: 'green' },

packages/create-react-native-library/src/utils/generateExampleApp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export default async function generateExampleApp({
131131

132132
await fs.writeFile(
133133
path.join(directory, 'ios', 'Podfile'),
134-
"ENV['RCT_NEW_ARCH_ENABLED'] = '1'\n" + podfile
134+
"ENV['RCT_NEW_ARCH_ENABLED'] = '1'\n\n" + podfile
135135
);
136136
}
137137
}

packages/create-react-native-library/templates/common/CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ To run the example app on iOS:
3232
yarn example ios
3333
```
3434

35+
<% if (project.architecture == 'mixed') { -%>
36+
By default, the example is configured to build with the new architecture. To build with the old architecture, you can change the following:
37+
38+
1. For Android, change `newArchEnabled=true` to `newArchEnabled=false` in `example/android/gradle.properties`.
39+
2. For iOS, change `ENV['RCT_NEW_ARCH_ENABLED'] = '1'` to `ENV['RCT_NEW_ARCH_ENABLED'] = '0'` in `example/ios/Podfile`.
40+
<% } -%>
3541
<% if (!project.native) { -%>
3642
To run the example app on Web:
3743

packages/create-react-native-library/templates/java-library-mixed/android/src/legacy/{%- project.name %}Module.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class <%- project.name -%>Module extends ReactContextBaseJavaModule {
1616
@Override
1717
@NonNull
1818
public String getName() {
19-
return <%- project.name -%>ModuleImpl.NAME;
19+
return NAME;
2020
}
2121

2222
// Example method

packages/create-react-native-library/templates/java-library-mixed/android/src/turbo/{%- project.name %}Module.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class <%- project.name -%>Module extends Native<%- project.name -%>Spec {
1515
@Override
1616
@NonNull
1717
public String getName() {
18-
return <%- project.name -%>ModuleImpl.NAME;
18+
return NAME;
1919
}
2020

2121
// Example method

packages/create-react-native-library/templates/java-library-turbo/android/src/main/java/com/{%- project.package %}/{%- project.name %}Module.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
import androidx.annotation.NonNull;
44

5-
import com.facebook.react.bridge.Promise;
65
import com.facebook.react.bridge.ReactApplicationContext;
7-
import com.facebook.react.bridge.ReactMethod;
86
import com.facebook.react.module.annotations.ReactModule;
97

108
@ReactModule(name = <%- project.name -%>Module.NAME)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.<%- project.package -%>;
2+
3+
import com.facebook.react.bridge.ReactApplicationContext
4+
import com.facebook.react.bridge.ReactContextBaseJavaModule
5+
import com.facebook.react.bridge.ReactMethod
6+
import com.facebook.react.bridge.Promise
7+
8+
class <%- project.name -%>Module internal constructor(context: ReactApplicationContext?) :
9+
ReactContextBaseJavaModule(context) {
10+
11+
override fun getName(): String {
12+
return NAME
13+
}
14+
15+
// Example method
16+
// See https://reactnative.dev/docs/native-modules-android
17+
@ReactMethod
18+
fun multiply(a: Double, b: Double, promise: Promise) {
19+
<%- project.name -%>ModuleImpl.multiply(a, b, promise)
20+
}
21+
22+
companion object {
23+
const val NAME = <%- project.name -%>ModuleImpl.NAME
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.<%- project.package -%>;
2+
3+
import com.facebook.react.bridge.Promise
4+
5+
/**
6+
* This is where the module implementation lives
7+
* The exposed methods can be defined in the `turbo` and `legacy` folders
8+
*/
9+
object <%- project.name -%>ModuleImpl {
10+
const val NAME = "<%- project.name -%>"
11+
12+
fun multiply(a: Double, b: Double, promise: Promise) {
13+
promise.resolve(a * b)
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.<%- project.package -%>;
2+
3+
import com.facebook.react.TurboReactPackage
4+
import com.facebook.react.bridge.ReactApplicationContext
5+
import com.facebook.react.bridge.NativeModule
6+
import com.facebook.react.module.model.ReactModuleInfoProvider
7+
import com.facebook.react.module.model.ReactModuleInfo
8+
import java.util.HashMap
9+
10+
class <%- project.name -%>Package : TurboReactPackage() {
11+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12+
return if (name == <%- project.name -%>Module.NAME) {
13+
<%- project.name -%>Module(reactContext)
14+
} else {
15+
null
16+
}
17+
}
18+
19+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20+
return ReactModuleInfoProvider {
21+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22+
val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
23+
moduleInfos[<%- project.name -%>Module.NAME] = ReactModuleInfo(
24+
<%- project.name -%>Module.NAME,
25+
<%- project.name -%>Module.NAME,
26+
false, // canOverrideExistingModule
27+
false, // needsEagerInit
28+
true, // hasConstants
29+
false, // isCxxModule
30+
isTurboModule // isTurboModule
31+
)
32+
moduleInfos
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.<%- project.package -%>;
2+
3+
import com.facebook.react.bridge.ReactApplicationContext
4+
import com.facebook.react.bridge.ReactMethod
5+
import com.facebook.react.bridge.Promise
6+
7+
class <%- project.name -%>Module internal constructor(context: ReactApplicationContext?) :
8+
Native<%- project.name -%>Spec(context) {
9+
override fun getName(): String {
10+
return NAME
11+
}
12+
13+
// Example method
14+
// See https://reactnative.dev/docs/native-modules-android
15+
@ReactMethod
16+
override fun multiply(a: Double, b: Double, promise: Promise) {
17+
<%- project.name -%>ModuleImpl.multiply(a, b, promise)
18+
}
19+
20+
companion object {
21+
val NAME: String = <%- project.name -%>ModuleImpl.NAME
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.<%- project.package -%>
2+
3+
import com.facebook.react.bridge.ReactApplicationContext
4+
import com.facebook.react.module.annotations.ReactModule
5+
6+
@ReactModule(name = <%- project.name -%>Module.NAME)
7+
class <%- project.name -%>Module(reactContext: ReactApplicationContext?) : Native<%- project.name -%>Spec(reactContext) {
8+
override fun getName(): String {
9+
return NAME
10+
}
11+
12+
// Example method
13+
// See https://reactnative.dev/docs/native-modules-android
14+
override fun multiply(a: Double, b: Double): Double {
15+
return a * b
16+
}
17+
18+
companion object {
19+
const val NAME = "<%- project.name -%>"
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.<%- project.package -%>
2+
3+
import com.facebook.react.TurboReactPackage
4+
import com.facebook.react.bridge.NativeModule
5+
import com.facebook.react.bridge.ReactApplicationContext
6+
import com.facebook.react.module.model.ReactModuleInfo
7+
import com.facebook.react.module.model.ReactModuleInfoProvider
8+
import java.util.HashMap
9+
10+
class <%- project.name -%>Package : TurboReactPackage() {
11+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12+
return if (name == <%- project.name -%>Module.NAME) {
13+
<%- project.name -%>Module(reactContext)
14+
} else {
15+
null
16+
}
17+
}
18+
19+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20+
return ReactModuleInfoProvider {
21+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22+
moduleInfos[<%- project.name -%>Module.NAME] = ReactModuleInfo(
23+
<%- project.name -%>Module.NAME,
24+
<%- project.name -%>Module.NAME,
25+
false, // canOverrideExistingModule
26+
false, // needsEagerInit
27+
true, // hasConstants
28+
false, // isCxxModule
29+
true // isTurboModule
30+
)
31+
moduleInfos
32+
}
33+
}
34+
}

packages/create-react-native-library/templates/native-common/android/build.gradle

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,29 @@ android {
7979
sourceCompatibility JavaVersion.VERSION_1_8
8080
targetCompatibility JavaVersion.VERSION_1_8
8181
}
82-
<% if (project.architecture == 'mixed') { -%>
82+
83+
<% if (project.architecture == 'turbo') { -%>
84+
sourceSets {
85+
main {
86+
if (isNewArchitectureEnabled()) {
87+
java.srcDirs += [
88+
// This is needed to build Kotlin project with TurboModules enabled
89+
"${project.buildDir}/generated/source/codegen/java"
90+
]
91+
}
92+
}
93+
}
94+
<% } else if (project.architecture == 'mixed') { -%>
8395
sourceSets {
8496
main {
8597
if (isNewArchitectureEnabled()) {
86-
java.srcDirs += ['src/turbo']
98+
java.srcDirs += [
99+
"src/turbo",
100+
// This is needed to build Kotlin project with TurboModules enabled
101+
"${project.buildDir}/generated/source/codegen/java"
102+
]
87103
} else {
88-
java.srcDirs += ['src/legacy']
104+
java.srcDirs += ["src/legacy"]
89105
}
90106
}
91107
}

0 commit comments

Comments
 (0)