diff --git a/.github/workflows/build-templates.yml b/.github/workflows/build-templates.yml index 4c5de8686..73d5df0b9 100644 --- a/.github/workflows/build-templates.yml +++ b/.github/workflows/build-templates.yml @@ -34,6 +34,7 @@ jobs: - fabric-view - legacy-module - legacy-view + - nitro-module language: - kotlin-objc - kotlin-swift @@ -47,6 +48,10 @@ jobs: language: cpp - type: legacy-view language: cpp + - type: nitro-module + language: kotlin-objc + - type: nitro-module + language: cpp include: - os: ubuntu type: library diff --git a/packages/create-react-native-library/src/exampleApp/dependencies.ts b/packages/create-react-native-library/src/exampleApp/dependencies.ts index dccb19a98..64552e278 100644 --- a/packages/create-react-native-library/src/exampleApp/dependencies.ts +++ b/packages/create-react-native-library/src/exampleApp/dependencies.ts @@ -1,11 +1,11 @@ import assert from 'node:assert'; import path from 'path'; import fs from 'fs-extra'; -import type { ExampleApp } from '../input'; +import type { TemplateConfiguration } from '../template'; export async function getDependencyVersionsFromExampleApp( folder: string, - exampleAppType: ExampleApp + config: TemplateConfiguration ) { const examplePackageJson = await fs.readJSON( path.join(folder, 'example', 'package.json') @@ -27,7 +27,11 @@ export async function getDependencyVersionsFromExampleApp( 'react-native': reactNative, }; - if (exampleAppType === 'vanilla') { + if ( + config.example === 'vanilla' && + (config.project.moduleConfig === 'turbo-modules' || + config.project.viewConfig === 'fabric-view') + ) { // React Native doesn't provide the community CLI as a dependency. // We have to get read the version from the example app and put to the root package json const exampleCommunityCLIVersion = diff --git a/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts b/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts index 1fb4d76a1..e998f2dfc 100644 --- a/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts +++ b/packages/create-react-native-library/src/exampleApp/generateExampleApp.ts @@ -3,7 +3,7 @@ import path from 'path'; import https from 'https'; import { spawn } from '../utils/spawn'; import sortObjectKeys from '../utils/sortObjectKeys'; -import type { ExampleApp } from '../input'; +import type { TemplateConfiguration } from '../template'; const FILES_TO_DELETE = [ '__tests__', @@ -42,25 +42,15 @@ const PACKAGES_TO_ADD_WEB = { }; export default async function generateExampleApp({ - type, - dest, - arch, - project, - bobVersion, + config, + destination, reactNativeVersion = 'latest', }: { - type: ExampleApp; - dest: string; - arch: 'new' | 'legacy'; - project: { - slug: string; - name: string; - package: string; - }; - bobVersion: string; + config: TemplateConfiguration; + destination: string; reactNativeVersion?: string; }) { - const directory = path.join(dest, 'example'); + const directory = path.join(destination, 'example'); // `npx --package react-native-test-app@latest init --name ${projectName}Example --destination example --version ${reactNativeVersion}` const testAppArgs = [ @@ -68,7 +58,7 @@ export default async function generateExampleApp({ `react-native-test-app@latest`, 'init', '--name', - `${project.name}Example`, + `${config.project.name}Example`, `--destination`, directory, ...(reactNativeVersion !== 'latest' @@ -84,9 +74,9 @@ export default async function generateExampleApp({ const vanillaArgs = [ `@react-native-community/cli`, 'init', - `${project.name}Example`, + `${config.project.name}Example`, '--package-name', - `${project.package}.example`, + `${config.project.package}.example`, '--directory', directory, '--version', @@ -107,7 +97,7 @@ export default async function generateExampleApp({ let args: string[] = []; - switch (type) { + switch (config.example) { case 'vanilla': args = vanillaArgs; break; @@ -131,7 +121,7 @@ export default async function generateExampleApp({ // Patch the example app's package.json const pkg = await fs.readJSON(path.join(directory, 'package.json')); - pkg.name = `${project.slug}-example`; + pkg.name = `${config.project.slug}-example`; // Remove Jest config for now delete pkg.jest; @@ -144,12 +134,12 @@ export default async function generateExampleApp({ const SCRIPTS_TO_ADD = { 'build:android': 'react-native build-android --extra-params "--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a"', - 'build:ios': `react-native build-ios --scheme ${project.name}Example --mode Debug --extra-params "-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"`, + 'build:ios': `react-native build-ios --scheme ${config.project.name}Example --mode Debug --extra-params "-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"`, }; - if (type === 'vanilla') { + if (config.example === 'vanilla') { Object.assign(scripts, SCRIPTS_TO_ADD); - } else if (type === 'test-app') { + } else if (config.example === 'test-app') { // `react-native-test-app` doesn't bundle application by default in 'Release' mode and also `bundle` command doesn't create a directory. // `mkdist` script should be removed after stable React Native major contains this fix: https://github.com/facebook/react-native/pull/45182. @@ -173,9 +163,9 @@ export default async function generateExampleApp({ const app = await fs.readJSON(path.join(directory, 'app.json')); app.android = app.android || {}; - app.android.package = `${project.package}.example`; + app.android.package = `${config.project.package}.example`; app.ios = app.ios || {}; - app.ios.bundleIdentifier = `${project.package}.example`; + app.ios.bundleIdentifier = `${config.project.package}.example`; await fs.writeJSON(path.join(directory, 'app.json'), app, { spaces: 2, @@ -188,12 +178,19 @@ export default async function generateExampleApp({ }); const PACKAGES_TO_ADD_DEV = { - 'react-native-builder-bob': `^${bobVersion}`, + 'react-native-builder-bob': `^${config.versions.bob}`, }; + if (config.project.moduleConfig === 'nitro-modules') { + const packagesToAddNitro = { + 'react-native-nitro-modules': `^${config.versions.nitroModules}`, + }; + Object.assign(dependencies, packagesToAddNitro); + } + Object.assign(devDependencies, PACKAGES_TO_ADD_DEV); - if (type === 'expo') { + if (config.example === 'expo') { const sdkVersion = dependencies.expo.split('.')[0].replace(/[^\d]/, ''); let bundledNativeModules: Record; @@ -231,15 +228,17 @@ export default async function generateExampleApp({ const app = await fs.readJSON(path.join(directory, 'app.json')); app.expo.android = app.expo.android || {}; - app.expo.android.package = `${project.package}.example`; + app.expo.android.package = `${config.project.package}.example`; app.expo.ios = app.expo.ios || {}; - app.expo.ios.bundleIdentifier = `${project.package}.example`; + app.expo.ios.bundleIdentifier = `${config.project.package}.example`; await fs.writeJSON(path.join(directory, 'app.json'), app, { spaces: 2, }); } + // Sort the deps by name to match behavior of package managers + // This way the package.json doesn't get updated when installing deps for (const field of ['dependencies', 'devDependencies']) { if (pkg[field]) { pkg[field] = sortObjectKeys(pkg[field]); @@ -250,7 +249,7 @@ export default async function generateExampleApp({ spaces: 2, }); - if (type !== 'expo') { + if (config.example !== 'expo') { let gradleProperties = await fs.readFile( path.join(directory, 'android', 'gradle.properties'), 'utf8' @@ -264,7 +263,7 @@ export default async function generateExampleApp({ ); // If the library is on new architecture, enable new arch for iOS and Android - if (arch === 'new') { + if (config.project.arch === 'new') { // iOS // Add ENV['RCT_NEW_ARCH_ENABLED'] = 1 on top of example/ios/Podfile const podfile = await fs.readFile( diff --git a/packages/create-react-native-library/src/index.ts b/packages/create-react-native-library/src/index.ts index f01eed082..100c349f4 100644 --- a/packages/create-react-native-library/src/index.ts +++ b/packages/create-react-native-library/src/index.ts @@ -21,6 +21,7 @@ import { getDependencyVersionsFromExampleApp } from './exampleApp/dependencies'; import { printErrorHelp, printNextSteps, printUsedRNVersion } from './inform'; const FALLBACK_BOB_VERSION = '0.32.0'; +const FALLBACK_NITRO_MODULES_VERSION = '0.18.0'; yargs .command( @@ -48,6 +49,10 @@ async function create(_argv: yargs.Arguments) { 'react-native-builder-bob', FALLBACK_BOB_VERSION ); + const nitroModulesVersionPromise = resolveNpmPackageVersion( + 'react-native-nitro-modules', + FALLBACK_NITRO_MODULES_VERSION + ); const local = await promptLocalLibrary(argv); const folder = await promptPath(argv, local); @@ -70,8 +75,18 @@ async function create(_argv: yargs.Arguments) { const bobVersion = await bobVersionPromise; + const nitroModulesVersion = + answers.type === 'nitro-module' + ? await nitroModulesVersionPromise + : undefined; + const config = generateTemplateConfiguration({ - bobVersion, + versions: { + bob: bobVersion, + nitroModules: nitroModulesVersion, + // Nitro codegen's version is always the same as nitro modules version. + nitroCodegen: nitroModulesVersion, + }, basename, answers, }); @@ -88,12 +103,9 @@ async function create(_argv: yargs.Arguments) { spinner.text = 'Generating example app'; await generateExampleApp({ - type: config.example, - dest: folder, - arch: config.project.arch, - project: config.project, - bobVersion, + destination: folder, reactNativeVersion: answers.reactNativeVersion, + config, }); } @@ -106,7 +118,7 @@ async function create(_argv: yargs.Arguments) { if (config.example !== 'none') { const { devDependencies } = await getDependencyVersionsFromExampleApp( folder, - config.example + config ); rootPackageJson.devDependencies = rootPackageJson.devDependencies @@ -117,7 +129,11 @@ async function create(_argv: yargs.Arguments) { : devDependencies; } - if (config.example === 'vanilla' && config.project.arch === 'new') { + if ( + config.example === 'vanilla' && + (config.project.moduleConfig === 'turbo-modules' || + config.project.viewConfig === 'fabric-view') + ) { addCodegenBuildScript(folder); } diff --git a/packages/create-react-native-library/src/input.ts b/packages/create-react-native-library/src/input.ts index 3e61e5426..3d9c8f885 100644 --- a/packages/create-react-native-library/src/input.ts +++ b/packages/create-react-native-library/src/input.ts @@ -25,6 +25,7 @@ export type ProjectType = | 'fabric-view' | 'legacy-module' | 'legacy-view' + | 'nitro-module' | 'library'; const LANGUAGE_CHOICES: { @@ -32,16 +33,16 @@ const LANGUAGE_CHOICES: { value: ProjectLanguages; types: ProjectType[]; }[] = [ + { + title: 'Kotlin & Swift', + value: 'kotlin-swift', + types: ['nitro-module', 'legacy-module', 'legacy-view'], + }, { title: 'Kotlin & Objective-C', value: 'kotlin-objc', types: ['turbo-module', 'fabric-view', 'legacy-module', 'legacy-view'], }, - { - title: 'Kotlin & Swift', - value: 'kotlin-swift', - types: ['legacy-module', 'legacy-view'], - }, { title: 'C++ for Android & iOS', value: 'cpp', @@ -89,6 +90,12 @@ const TYPE_CHOICES: { value: 'turbo-module', description: 'integration for native APIs to JS', }, + { + title: 'Nitro module', + value: 'nitro-module', + description: + 'type-safe, fast integration for native APIs to JS (experimental)', + }, { title: 'Fabric view', value: 'fabric-view', diff --git a/packages/create-react-native-library/src/template.ts b/packages/create-react-native-library/src/template.ts index 946784e94..c003891c1 100644 --- a/packages/create-react-native-library/src/template.ts +++ b/packages/create-react-native-library/src/template.ts @@ -1,14 +1,31 @@ import path from 'path'; import fs from 'fs-extra'; import ejs from 'ejs'; -import type { Answers, ExampleApp, SupportedArchitecture } from './input'; +import type { + Answers, + ExampleApp, + ProjectType, + SupportedArchitecture, +} from './input'; + +export type TemplateVersions = { + bob: string; + nitroModules?: string; + nitroCodegen?: string; +}; + +export type ModuleConfig = + | 'native-modules' + | 'turbo-modules' + | 'nitro-modules' + | null; + +export type ViewConfig = 'paper-view' | 'fabric-view' | null; // Please think at least 5 times before introducing a new config key // You can just reuse the existing ones most of the time export type TemplateConfiguration = { - bob: { - version: string; - }; + versions: TemplateVersions; project: { slug: string; description: string; @@ -21,8 +38,8 @@ export type TemplateConfiguration = { arch: SupportedArchitecture; cpp: boolean; swift: boolean; - view: boolean; - module: boolean; + viewConfig: ViewConfig; + moduleConfig: ModuleConfig; }; author: { name: string; @@ -72,6 +89,7 @@ const NATIVE_FILES = { module_new: path.resolve(__dirname, '../templates/native-library-new'), view_legacy: path.resolve(__dirname, '../templates/native-view-legacy'), view_new: path.resolve(__dirname, '../templates/native-view-new'), + module_nitro: path.resolve(__dirname, '../templates/nitro-module'), } as const; const OBJC_FILES = { @@ -93,11 +111,11 @@ const SWIFT_FILES = { } as const; export function generateTemplateConfiguration({ - bobVersion, + versions, basename, answers, }: { - bobVersion: string; + versions: TemplateVersions; basename: string; answers: Answers; }): TemplateConfiguration { @@ -124,9 +142,7 @@ export function generateTemplateConfiguration({ .toLowerCase()}`; return { - bob: { - version: bobVersion, - }, + versions, project: { slug, description: answers.description, @@ -146,8 +162,8 @@ export function generateTemplateConfiguration({ arch, cpp: languages === 'cpp', swift: languages === 'kotlin-swift', - view: answers.type.endsWith('-view'), - module: answers.type.endsWith('-module'), + viewConfig: getViewConfig(type), + moduleConfig: getModuleConfig(type), }, author: { name: answers.authorName, @@ -160,6 +176,30 @@ export function generateTemplateConfiguration({ }; } +function getModuleConfig(projectType: ProjectType): ModuleConfig { + switch (projectType) { + case 'nitro-module': + return 'nitro-modules'; + case 'turbo-module': + return 'turbo-modules'; + case 'legacy-module': + return 'native-modules'; + default: + return null; + } +} + +function getViewConfig(projectType: ProjectType): ViewConfig { + switch (projectType) { + case 'legacy-view': + return 'paper-view'; + case 'fabric-view': + return 'fabric-view'; + default: + return null; + } +} + export async function applyTemplates( answers: Answers, config: TemplateConfiguration, @@ -174,7 +214,7 @@ export async function applyTemplates( if (config.example !== 'none') { await applyTemplate(config, EXAMPLE_COMMON_FILES, folder); - if (config.project.view) { + if (config.project.viewConfig !== null) { await applyTemplate(config, EXAMPLE_VIEW_FILES, folder); } else { if (config.project.arch === 'legacy') { @@ -196,7 +236,12 @@ export async function applyTemplates( await applyTemplate(config, NATIVE_COMMON_EXAMPLE_FILES, folder); } - if (config.project.module) { + if (config.project.moduleConfig === 'nitro-modules') { + await applyTemplate(config, NATIVE_FILES['module_nitro'], folder); + return; + } + + if (config.project.moduleConfig !== null) { await applyTemplate( config, NATIVE_FILES[`module_${config.project.arch}`], @@ -213,7 +258,7 @@ export async function applyTemplates( if (config.project.swift) { await applyTemplate(config, SWIFT_FILES[`module_legacy`], folder); } else { - if (config.project.module) { + if (config.project.moduleConfig !== null) { await applyTemplate(config, OBJC_FILES[`module_common`], folder); } else { await applyTemplate( @@ -224,9 +269,9 @@ export async function applyTemplates( } } - const templateType = `${config.project.module ? 'module' : 'view'}_${ - config.project.arch - }` as const; + const templateType = `${ + config.project.moduleConfig !== null ? 'module' : 'view' + }_${config.project.arch}` as const; await applyTemplate(config, KOTLIN_FILES[templateType], folder); diff --git a/packages/create-react-native-library/templates/common-local/$package.json b/packages/create-react-native-library/templates/common-local/$package.json index 5faf3f41c..71b873e99 100644 --- a/packages/create-react-native-library/templates/common-local/$package.json +++ b/packages/create-react-native-library/templates/common-local/$package.json @@ -5,8 +5,8 @@ "main": "src/index", <% if (project.arch === 'new') { -%> "codegenConfig": { - "name": "RN<%- project.name -%><%- project.view ? 'View': '' -%>Spec", - "type": <%- project.view ? '"all"': '"modules"' %>, + "name": "RN<%- project.name -%><%- project.viewConfig !== null ? 'View': '' -%>Spec", + "type": <%- project.viewConfig !== null ? '"components"': '"modules"' %>, "jsSrcsDir": "src" }, <% } -%> diff --git a/packages/create-react-native-library/templates/common/$.github/workflows/ci.yml b/packages/create-react-native-library/templates/common/$.github/workflows/ci.yml index ab1606734..3cba52f1d 100644 --- a/packages/create-react-native-library/templates/common/$.github/workflows/ci.yml +++ b/packages/create-react-native-library/templates/common/$.github/workflows/ci.yml @@ -61,6 +61,11 @@ jobs: - name: Setup uses: ./.github/actions/setup +<% if (project.moduleConfig === 'nitro-modules') { -%> + + - name: Generate nitrogen code + run: yarn nitrogen +<% } -%> - name: Cache turborepo for Android uses: actions/cache@v4 @@ -117,6 +122,11 @@ jobs: - name: Setup uses: ./.github/actions/setup +<% if (project.moduleConfig === 'nitro-modules') { -%> + + - name: Generate nitrogen code + run: yarn nitrogen +<% } -%> - name: Cache turborepo for iOS uses: actions/cache@v4 diff --git a/packages/create-react-native-library/templates/common/$.gitignore b/packages/create-react-native-library/templates/common/$.gitignore index cfe2bb4fd..b50ff3d36 100644 --- a/packages/create-react-native-library/templates/common/$.gitignore +++ b/packages/create-react-native-library/templates/common/$.gitignore @@ -80,3 +80,6 @@ lib/ # React Native Codegen ios/generated android/generated + +# React Native Nitro Modules +nitrogen/ diff --git a/packages/create-react-native-library/templates/common/$package.json b/packages/create-react-native-library/templates/common/$package.json index 5ae1e4432..aa8d4c342 100644 --- a/packages/create-react-native-library/templates/common/$package.json +++ b/packages/create-react-native-library/templates/common/$package.json @@ -23,6 +23,9 @@ "android", "ios", "cpp", +<% if (project.moduleConfig === "nitro-modules") { -%> + "nitrogen", +<% } -%> "*.podspec", "react-native.config.js", "!ios/build", @@ -49,6 +52,9 @@ "clean": "del-cli lib", <% } -%> "prepare": "bob build", +<% if (project.moduleConfig === 'nitro-modules') { -%> + "nitrogen": "nitro-codegen", +<% } -%> "release": "release-it" }, "keywords": [ @@ -72,7 +78,7 @@ "devDependencies": { "@commitlint/config-conventional": "^17.0.2", "@evilmartians/lefthook": "^1.5.0", -<% if (example === 'vanilla') { -%> +<% if (example === 'vanilla' && (project.moduleConfig === 'turbo-modules' || project.viewConfig === 'fabric-view')) { -%> "@react-native-community/cli": "15.0.0-alpha.2", <% } -%> "@react-native/eslint-config": "^0.73.1", @@ -85,10 +91,16 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.1", "jest": "^29.7.0", +<% if (project.moduleConfig === 'nitro-modules') { -%> + "nitro-codegen": "^<%- versions.nitroCodegen %>", +<% } -%> "prettier": "^3.0.3", "react": "17.0.2", "react-native": "0.73.0", - "react-native-builder-bob": "^<%- bob.version %>", + "react-native-builder-bob": "^<%- versions.bob %>", +<% if (project.moduleConfig === 'nitro-modules') { -%> + "react-native-nitro-modules": "^<%- versions.nitroModules %>", +<% } -%> "release-it": "^17.10.0", <% if (example !== 'expo') { -%> "turbo": "^1.10.7", @@ -100,7 +112,12 @@ }, "peerDependencies": { "react": "*", +<% if (project.moduleConfig === 'nitro-modules') { -%> + "react-native": "*", + "react-native-nitro-modules": "^<%- versions.nitroModules %>" +<% } else { -%> "react-native": "*" +<% } -%> }, <% if (example !== 'none') { -%> "workspaces": [ @@ -172,7 +189,16 @@ "source": "src", "output": "lib", "targets": [ -<% if (project.arch === 'new') { -%> +<% if (project.moduleConfig === "nitro-modules") { -%> + [ + "custom", + { + "script": "nitrogen", + "clean": "nitrogen/" + } + ], +<% } -%> +<% if (project.moduleConfig === 'turbo-modules' || project.viewConfig === 'fabric-view') { -%> "codegen", <% } -%> [ @@ -194,11 +220,11 @@ } ] ] -<% if (project.arch === 'new') { -%> +<% if (project.moduleConfig === 'turbo-modules' || project.viewConfig === 'fabric-view') { -%> }, "codegenConfig": { - "name": "RN<%- project.name -%><%- project.view ? 'View': '' -%>Spec", - "type": "<%- project.view ? 'all': 'modules' -%>", + "name": "RN<%- project.name -%><%- project.viewConfig !== null ? 'View': '' -%>Spec", + "type": "<%- project.viewConfig !== null ? 'components': 'modules' -%>", "jsSrcsDir": "src", "outputDir": { "ios": "ios/generated", diff --git a/packages/create-react-native-library/templates/common/CONTRIBUTING.md b/packages/create-react-native-library/templates/common/CONTRIBUTING.md index 656304dba..1bf70511e 100644 --- a/packages/create-react-native-library/templates/common/CONTRIBUTING.md +++ b/packages/create-react-native-library/templates/common/CONTRIBUTING.md @@ -18,6 +18,22 @@ yarn ``` > Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development. +<% if (project.moduleConfig === 'nitro-modules') { -%> + +This project uses Nitro Modules. If you're not familiar with how Nitro works, make sure to check the [Nitro Modules Docs](https://nitro.margelo.com/). + +You need to run the code generation process using [Nitrogen](https://nitro.margelo.com/docs/nitrogen) to generate the boilerplate-heavy code required for this project. Without this step, the example apps will not build. + +Run **Nitrogen** in the following cases: +- When you make changes to any `*.nitro.ts` files. +- When running the project for the first time (since the generated files are not committed to the repository). + +To invoke **Nitrogen**, use the following command: + +```sh +yarn nitrogen +``` +<% } -%> The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make. diff --git a/packages/create-react-native-library/templates/common/README.md b/packages/create-react-native-library/templates/common/README.md index 4fe83e1b3..5b34c58f1 100644 --- a/packages/create-react-native-library/templates/common/README.md +++ b/packages/create-react-native-library/templates/common/README.md @@ -4,13 +4,21 @@ ## Installation +<% if (project.moduleConfig === 'nitro-modules') { -%> +```sh +npm install <%- project.slug %> react-native-nitro-modules + +> `react-native-nitro-modules` is required as this library relies on [Nitro Modules](https://nitro.margelo.com/). +``` +<% } else { -%> ```sh npm install <%- project.slug %> ``` +<% } -%> ## Usage -<% if (project.view) { -%> +<% if (project.viewConfig !== null) { -%> ```js import { <%- project.name -%>View } from "<%- project.slug -%>"; @@ -20,7 +28,7 @@ import { <%- project.name -%>View } from "<%- project.slug -%>"; <<%- project.name -%>View color="tomato" /> ``` -<% } else if (project.arch === 'new' && project.module) { -%> +<% } else if (project.moduleConfig === 'nitro-modules' || project.moduleConfig === 'turbo-modules') { -%> ```js import { multiply } from '<%- project.slug -%>'; diff --git a/packages/create-react-native-library/templates/native-common/android/build.gradle b/packages/create-react-native-library/templates/native-common/android/build.gradle index ac8e1841c..6e7b4d135 100644 --- a/packages/create-react-native-library/templates/native-common/android/build.gradle +++ b/packages/create-react-native-library/templates/native-common/android/build.gradle @@ -28,6 +28,9 @@ def isNewArchitectureEnabled() { apply plugin: "com.android.library" apply plugin: "kotlin-android" +<% if (project.moduleConfig === 'nitro-modules') { -%> +apply from: '../nitrogen/generated/android/<%- project.package_cpp -%>+autolinking.gradle' +<% } -%> if (isNewArchitectureEnabled()) { apply plugin: "com.facebook.react" @@ -48,7 +51,11 @@ def supportsNamespace() { android { if (supportsNamespace()) { +<% if (project.moduleConfig === 'nitro-modules') { -%> + namespace "com.margelo.nitro.<%- project.package -%>" +<% } else { -%> namespace "com.<%- project.package -%>" +<% } -%> sourceSets { main { @@ -68,17 +75,27 @@ android { <% if (project.arch === "new") { -%> buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() <% } -%> +<% if (project.cpp || project.moduleConfig === 'nitro-modules') { -%> -<% if (project.cpp) { -%> externalNativeBuild { cmake { - cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all" + cppFlags "-frtti -fexceptions -Wall -fstack-protector-all" + arguments "-DANDROID_STL=c++_shared" abiFilters (*reactNativeArchitectures()) + + buildTypes { + debug { + cppFlags "-O1 -g" + } + release { + cppFlags "-O2" + } + } } } <% } -%> } -<% if (project.cpp) { -%> +<% if (project.cpp || project.moduleConfig === 'nitro-modules') { -%> externalNativeBuild { cmake { @@ -86,10 +103,36 @@ android { } } <% } -%> +<% if (project.moduleConfig === 'nitro-modules') { -%> + + packagingOptions { + excludes = [ + "META-INF", + "META-INF/**", + "**/libc++_shared.so", + "**/libfbjni.so", + "**/libjsi.so", + "**/libfolly_json.so", + "**/libfolly_runtime.so", + "**/libglog.so", + "**/libhermes.so", + "**/libhermes-executor-debug.so", + "**/libhermes_executor.so", + "**/libreactnative.so", + "**/libreactnativejni.so", + "**/libturbomodulejsijni.so", + "**/libreact_nativemodule_core.so", + "**/libjscexecutor.so" + ] + } +<% } -%> <% if (project.arch === "new") { -%> buildFeatures { buildConfig true +<% if (project.moduleConfig === 'nitro-modules') { -%> + prefab true +<% } -%> } <% } -%> @@ -132,13 +175,16 @@ def kotlin_version = getExtOrDefault("kotlinVersion") dependencies { implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" +<% if (project.moduleConfig === 'nitro-modules') { -%> + implementation project(":react-native-nitro-modules") +<% } -%> } -<% if (project.arch === "new") { -%> +<% if (project.moduleConfig === "turbo-modules" || project.viewConfig === "fabric-view") { -%> if (isNewArchitectureEnabled()) { react { jsRootDir = file("../src/") - libraryName = "<%- project.view ? project.name + 'View' : project.name -%>" + libraryName = "<%- project.viewConfig !== null ? project.name + 'View' : project.name -%>" codegenJavaPackageName = "com.<%- project.package -%>" } } diff --git a/packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec b/packages/create-react-native-library/templates/native-common/{%- project.name %}.podspec similarity index 89% rename from packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec rename to packages/create-react-native-library/templates/native-common/{%- project.name %}.podspec index 5ee275d4f..9d8cf0c07 100644 --- a/packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec +++ b/packages/create-react-native-library/templates/native-common/{%- project.name %}.podspec @@ -4,7 +4,7 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json"))) folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' Pod::Spec.new do |s| - s.name = "<%- project.identifier -%>" + s.name = "<%- project.name -%>" s.version = package["version"] s.summary = package["description"] s.homepage = package["homepage"] @@ -24,6 +24,11 @@ Pod::Spec.new do |s| <% } else { -%> s.source_files = "ios/**/*.{h,m,mm}" <% } -%> +<% if (project.moduleConfig === "nitro-modules") { -%> + + load 'nitrogen/generated/ios/<%- project.name -%>+autolinking.rb' + add_nitrogen_files(s) +<% } -%> # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. @@ -40,7 +45,7 @@ Pod::Spec.new do |s| "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" } -<% if (project.view) { -%> +<% if (project.viewConfig !== null) { -%> s.dependency "React-RCTFabric" <% } -%> s.dependency "React-Codegen" diff --git a/packages/create-react-native-library/templates/nitro-module/android/CMakeLists.txt b/packages/create-react-native-library/templates/nitro-module/android/CMakeLists.txt new file mode 100644 index 000000000..1fa9ed39d --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/android/CMakeLists.txt @@ -0,0 +1,24 @@ +project(<%- project.package_cpp -%>) +cmake_minimum_required(VERSION 3.9.0) + +set(PACKAGE_NAME <%- project.package_cpp -%>) +set(CMAKE_VERBOSE_MAKEFILE ON) +set(CMAKE_CXX_STANDARD 20) + +# Define C++ library and add all sources +add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp) + +# Add Nitrogen specs :) +include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/<%- project.package_cpp -%>+autolinking.cmake) + +# Set up local includes +include_directories("src/main/cpp" "../cpp") + +find_library(LOG_LIB log) + +# Link all libraries together +target_link_libraries( + ${PACKAGE_NAME} + ${LOG_LIB} + android # <-- Android core +) diff --git a/packages/create-react-native-library/templates/nitro-module/android/src/main/cpp/cpp-adapter.cpp b/packages/create-react-native-library/templates/nitro-module/android/src/main/cpp/cpp-adapter.cpp new file mode 100644 index 000000000..8e1f6123b --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/android/src/main/cpp/cpp-adapter.cpp @@ -0,0 +1,6 @@ +#include +#include "<%- project.package_cpp -%>OnLoad.hpp" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + return margelo::nitro::<%- project.package_cpp -%>::initialize(vm); +} diff --git a/packages/create-react-native-library/templates/nitro-module/android/src/main/java/com/margelo/nitro/{%- project.package_dir %}/{%- project.name %}.kt b/packages/create-react-native-library/templates/nitro-module/android/src/main/java/com/margelo/nitro/{%- project.package_dir %}/{%- project.name %}.kt new file mode 100644 index 000000000..67f48eb3b --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/android/src/main/java/com/margelo/nitro/{%- project.package_dir %}/{%- project.name %}.kt @@ -0,0 +1,10 @@ +package com.margelo.nitro.<%- project.package %> + +class CrnlNitro : Hybrid<%- project.name %>Spec() { + override val memorySize: Long + get() = 0L + + override fun multiply(a: Double, b: Double): Double { + return a * b + } +} diff --git a/packages/create-react-native-library/templates/nitro-module/android/src/main/java/com/{%- project.package_dir %}/{%- project.name %}Package.kt b/packages/create-react-native-library/templates/nitro-module/android/src/main/java/com/{%- project.package_dir %}/{%- project.name %}Package.kt new file mode 100644 index 000000000..f832910bc --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/android/src/main/java/com/{%- project.package_dir %}/{%- project.name %}Package.kt @@ -0,0 +1,22 @@ +package com.<%- project.package %> + +import com.facebook.react.TurboReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfoProvider + +class <%- project.name -%>Package : TurboReactPackage() { + override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { + return null + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { HashMap() } + } + + companion object { + init { + System.loadLibrary("<%- project.package_cpp -%>") + } + } +} diff --git a/packages/create-react-native-library/templates/nitro-module/ios/{%- project.name %}.swift b/packages/create-react-native-library/templates/nitro-module/ios/{%- project.name %}.swift new file mode 100644 index 000000000..1f09ee1a4 --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/ios/{%- project.name %}.swift @@ -0,0 +1,9 @@ +class <%- project.name -%>: Hybrid<%- project.name -%>Spec { + public override var memorySize: Int { + getSizeOf(self) + } + + public func multiply(a: Double, b: Double) throws -> Double { + return a * b + } +} diff --git a/packages/create-react-native-library/templates/nitro-module/nitro.json b/packages/create-react-native-library/templates/nitro-module/nitro.json new file mode 100644 index 000000000..8a1674a36 --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/nitro.json @@ -0,0 +1,17 @@ +{ + "cxxNamespace": ["<%- project.package_cpp -%>"], + "ios": { + "iosModuleName": "<%- project.name -%>" + }, + "android": { + "androidNamespace": <%- JSON.stringify(project.package.split('.')) -%>, + "androidCxxLibName": "<%- project.package_cpp -%>" + }, + "autolinking": { + "<%- project.name -%>": { + "swift": "<%- project.name -%>", + "kotlin": "<%- project.name -%>" + } + }, + "ignorePaths": ["node_modules"] +} diff --git a/packages/create-react-native-library/templates/nitro-module/src/index.tsx b/packages/create-react-native-library/templates/nitro-module/src/index.tsx new file mode 100644 index 000000000..fb7633d49 --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/src/index.tsx @@ -0,0 +1,9 @@ +import { NitroModules } from 'react-native-nitro-modules'; +import type { <%- project.name -%> } from './<%- project.name -%>.nitro'; + +const <%- project.name -%>HybridObject = + NitroModules.createHybridObject<<%- project.name -%>>('<%- project.name -%>'); + +export function multiply(a: number, b: number): number { + return <%- project.name -%>HybridObject.multiply(a, b); +} diff --git a/packages/create-react-native-library/templates/nitro-module/src/{%- project.name %}.nitro.ts b/packages/create-react-native-library/templates/nitro-module/src/{%- project.name %}.nitro.ts new file mode 100644 index 000000000..439d9a4e9 --- /dev/null +++ b/packages/create-react-native-library/templates/nitro-module/src/{%- project.name %}.nitro.ts @@ -0,0 +1,6 @@ +import type { HybridObject } from 'react-native-nitro-modules'; + +export interface <%- project.name %> + extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> { + multiply(a: number, b: number): number; +}