From 436240964306b79d58286384c171fce026067ca9 Mon Sep 17 00:00:00 2001 From: JimmyDaddy Date: Tue, 31 Mar 2026 15:12:15 +0800 Subject: [PATCH] feat(rn): add new architecture support --- README.md | 2 + android/build.gradle | 18 ++- android/cpp-adapter.cpp | 30 +++-- .../bsdiffpatch/BsDiffPatchModule.kt | 43 +++++++ .../bsdiffpatch/BsDiffPatchPackage.kt | 36 ++++++ .../bsdiffpatch/BsDiffPatchModule.kt | 52 +++++++++ .../bsdiffpatch/BsDiffPatchPackage.kt | 16 +++ .../bsdiffpatch/BsDiffPatchModule.kt | 107 ------------------ .../bsdiffpatch/BsDiffPatchNative.kt | 95 ++++++++++++++++ .../bsdiffpatch/BsDiffPatchPackage.kt | 18 --- ios/BsDiffPatch.mm | 13 +++ package.json | 8 ++ src/NativeBsDiffPatch.ts | 9 ++ src/index.ts | 19 +--- 14 files changed, 310 insertions(+), 156 deletions(-) create mode 100644 android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt create mode 100644 android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt create mode 100644 android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt create mode 100644 android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt delete mode 100644 android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt create mode 100644 android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchNative.kt delete mode 100644 android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt create mode 100644 src/NativeBsDiffPatch.ts diff --git a/README.md b/README.md index 7b50269..654b078 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ rn bs diff patch file +Supports both the legacy bridge and React Native New Architecture (TurboModule). + ## Installation ```sh diff --git a/android/build.gradle b/android/build.gradle index 475489d..5982770 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -20,6 +20,12 @@ apply plugin: "kotlin-android" if (isNewArchitectureEnabled()) { apply plugin: "com.facebook.react" + + react { + jsRootDir = file("../src") + libraryName = "RNBsDiffPatchSpec" + codegenJavaPackageName = "com.jimmydaddy.bsdiffpatch" + } } def getExtOrDefault(name) { @@ -42,11 +48,18 @@ def supportsNamespace() { android { if (supportsNamespace()) { namespace "com.jimmydaddy.bsdiffpatch" + } - sourceSets { - main { + sourceSets { + main { + if (supportsNamespace()) { manifest.srcFile "src/main/AndroidManifestNew.xml" } + java.setSrcDirs( + isNewArchitectureEnabled() + ? ["src/main/java", "newarch/java"] + : ["src/main/java", "oldarch/java"] + ) } } @@ -100,4 +113,3 @@ dependencies { implementation "com.facebook.react:react-native:+" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } - diff --git a/android/cpp-adapter.cpp b/android/cpp-adapter.cpp index 3055cd6..40bff07 100644 --- a/android/cpp-adapter.cpp +++ b/android/cpp-adapter.cpp @@ -1,12 +1,7 @@ #include #include "react-native-bs-diff-patch.h" -extern "C" - -JNIEXPORT jint JNICALL -Java_com_jimmydaddy_bsdiffpatch_BsDiffPatchModule_00024Companion_bsDiffFile(JNIEnv *env, - jobject type, jstring oldFile_, - jstring newFile_, jstring patchFile_) { +static jint bsDiffFileJNI(JNIEnv *env, jstring oldFile_, jstring newFile_, jstring patchFile_) { const char *oldFile = env->GetStringUTFChars(oldFile_, 0); const char *newFile = env->GetStringUTFChars(newFile_, 0); const char *patchFile = env->GetStringUTFChars(patchFile_, 0); @@ -20,10 +15,7 @@ Java_com_jimmydaddy_bsdiffpatch_BsDiffPatchModule_00024Companion_bsDiffFile(JNIE return result; } -extern "C" JNIEXPORT jint JNICALL -Java_com_jimmydaddy_bsdiffpatch_BsDiffPatchModule_00024Companion_bsPatchFile(JNIEnv *env, - jobject type, jstring oldFile_, - jstring newFile_, jstring patchFile_) { +static jint bsPatchFileJNI(JNIEnv *env, jstring oldFile_, jstring newFile_, jstring patchFile_) { const char *oldFile = env->GetStringUTFChars(oldFile_, 0); const char *newFile = env->GetStringUTFChars(newFile_, 0); const char *patchFile = env->GetStringUTFChars(patchFile_, 0); @@ -36,3 +28,21 @@ Java_com_jimmydaddy_bsdiffpatch_BsDiffPatchModule_00024Companion_bsPatchFile(JNI return result; } + +extern "C" JNIEXPORT jint JNICALL +Java_com_jimmydaddy_bsdiffpatch_BsDiffPatchNative_bsDiffFile(JNIEnv *env, + jobject type, + jstring oldFile_, + jstring newFile_, + jstring patchFile_) { + return bsDiffFileJNI(env, oldFile_, newFile_, patchFile_); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_jimmydaddy_bsdiffpatch_BsDiffPatchNative_bsPatchFile(JNIEnv *env, + jobject type, + jstring oldFile_, + jstring newFile_, + jstring patchFile_) { + return bsPatchFileJNI(env, oldFile_, newFile_, patchFile_); +} diff --git a/android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt b/android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt new file mode 100644 index 0000000..49c80e6 --- /dev/null +++ b/android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt @@ -0,0 +1,43 @@ +package com.jimmydaddy.bsdiffpatch + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule + +@ReactModule(name = BsDiffPatchNative.NAME) +class BsDiffPatchModule(reactContext: ReactApplicationContext) : + NativeBsDiffPatchSpec(reactContext) { + override fun getName(): String = BsDiffPatchNative.NAME + + override fun patch( + oldFile: String, + newFile: String, + patchFile: String, + promise: Promise + ) { + execute(promise) { + BsDiffPatchNative.patch(oldFile, newFile, patchFile) + } + } + + override fun diff( + oldFile: String, + newFile: String, + patchFile: String, + promise: Promise + ) { + execute(promise) { + BsDiffPatchNative.diff(oldFile, newFile, patchFile) + } + } + + private fun execute(promise: Promise, block: () -> Int) { + try { + promise.resolve(block()) + } catch (error: BsDiffPatchException) { + promise.reject(error.code, error.message, error) + } catch (error: Exception) { + promise.reject("EUNSPECIFIED", error.message, error) + } + } +} diff --git a/android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt b/android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt new file mode 100644 index 0000000..b3dc767 --- /dev/null +++ b/android/newarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt @@ -0,0 +1,36 @@ +package com.jimmydaddy.bsdiffpatch + +import com.facebook.react.TurboReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider + +class BsDiffPatchPackage : TurboReactPackage() { + override fun getModule( + name: String, + reactContext: ReactApplicationContext + ): NativeModule? { + return if (name == BsDiffPatchNative.NAME) { + BsDiffPatchModule(reactContext) + } else { + null + } + } + + override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { + return ReactModuleInfoProvider { + mapOf( + BsDiffPatchNative.NAME to ReactModuleInfo( + BsDiffPatchNative.NAME, + BsDiffPatchModule::class.java.name, + false, + false, + false, + false, + true + ) + ) + } + } +} diff --git a/android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt b/android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt new file mode 100644 index 0000000..e1fed04 --- /dev/null +++ b/android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt @@ -0,0 +1,52 @@ +package com.jimmydaddy.bsdiffpatch + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.module.annotations.ReactModule + +@ReactModule(name = BsDiffPatchNative.NAME) +class BsDiffPatchModule(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext) { + override fun getName(): String = BsDiffPatchNative.NAME + + @ReactMethod + fun patch(oldFile: String?, newFile: String?, patchFile: String?, promise: Promise) { + execute(promise) { + BsDiffPatchNative.patch( + requireArgument(oldFile, "oldFile"), + requireArgument(newFile, "newFile"), + requireArgument(patchFile, "patchFile") + ) + } + } + + @ReactMethod + fun diff(oldFile: String?, newFile: String?, patchFile: String?, promise: Promise) { + execute(promise) { + BsDiffPatchNative.diff( + requireArgument(oldFile, "oldFile"), + requireArgument(newFile, "newFile"), + requireArgument(patchFile, "patchFile") + ) + } + } + + private fun requireArgument(value: String?, fieldName: String): String { + if (value.isNullOrEmpty()) { + throw BsDiffPatchException("EINVAL", "$fieldName can not be null or empty") + } + return value + } + + private fun execute(promise: Promise, block: () -> Int) { + try { + promise.resolve(block()) + } catch (error: BsDiffPatchException) { + promise.reject(error.code, error.message, error) + } catch (error: Exception) { + promise.reject("EUNSPECIFIED", error.message, error) + } + } +} diff --git a/android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt b/android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt new file mode 100644 index 0000000..be2877d --- /dev/null +++ b/android/oldarch/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt @@ -0,0 +1,16 @@ +package com.jimmydaddy.bsdiffpatch + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + +class BsDiffPatchPackage : ReactPackage { + override fun createNativeModules( + reactContext: ReactApplicationContext + ): List = listOf(BsDiffPatchModule(reactContext)) + + override fun createViewManagers( + reactContext: ReactApplicationContext + ): List> = emptyList() +} diff --git a/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt b/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt deleted file mode 100644 index e397957..0000000 --- a/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchModule.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.jimmydaddy.bsdiffpatch - -import com.facebook.react.bridge.Promise -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContextBaseJavaModule -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.module.annotations.ReactModule -import com.jimmydaddy.bsdiffpatch.BsDiffPatchModule -import java.io.File - -@ReactModule(name = BsDiffPatchModule.NAME) -class BsDiffPatchModule(reactContext: ReactApplicationContext?) : - ReactContextBaseJavaModule(reactContext) { - override fun getName(): String { - return NAME - } - - private fun getFileDir(dir: String): String { - if (dir.startsWith("file://")) { - return dir.substring(7) - } - return dir - } - - // Example method - // See https://reactnative.dev/docs/native-modules-android - @ReactMethod - fun patch(oldFile: String?, newFile: String?, patchFile: String?, promise: Promise) { - if (oldFile == null || newFile == null || patchFile == null || oldFile.isEmpty() || newFile.isEmpty() || patchFile.isEmpty()) { - promise.reject("error", "oldFile, newFile, patchFile can not be null or empty") - return - } - if (oldFile == newFile || oldFile == patchFile || newFile == patchFile) { - promise.reject("error", "oldFile, newFile, patchFile can not be the same") - return - } - val oldFileObj = File(getFileDir(oldFile)) - if (!oldFileObj.exists()) { - promise.reject("error", "oldFile: $oldFile not exist") - return - } - val patchFileObj = File(getFileDir(patchFile)) - if (!patchFileObj.exists()) { - promise.reject("error", "patchFile: $patchFile not exist") - return - } - val newFileObj = File(getFileDir(newFile)) - if (newFileObj.exists()) { - promise.reject("error", "newFile: $newFile already exist") - return - } - try { - val result = bsPatchFile(oldFileObj.absolutePath, newFileObj.absolutePath, patchFileObj.absolutePath) - promise.resolve(result) - } catch (e: Exception) { - promise.reject("error", e.message) - } - } - - @ReactMethod - fun diff(oldFile: String?, newFile: String?, patchFile: String?, promise: Promise) { - if (oldFile == null || newFile == null || patchFile == null || oldFile.isEmpty() || newFile.isEmpty() || patchFile.isEmpty()) { - promise.reject("error", "oldFile, newFile, patchFile can not be null or empty") - return - } - if (oldFile == newFile || oldFile == patchFile || newFile == patchFile) { - promise.reject("error", "oldFile, newFile, patchFile can not be the same") - return - } - val oldFileObj = File(getFileDir(oldFile)) - if (!oldFileObj.exists()) { - promise.reject("error", "oldFile: $oldFile not exist") - return - } - val newFileObj = File(getFileDir(newFile)) - if (!newFileObj.exists()) { - promise.reject("error", "newFile: $newFile not exist") - return - } - val patchFileObj = File(getFileDir(patchFile)) - if (patchFileObj.exists()) { - promise.reject("error", "patchFile: $patchFile already exist") - return - } - try { - val result = bsDiffFile(oldFileObj.absolutePath, newFileObj.absolutePath, patchFileObj.absolutePath) - promise.resolve(result) - } catch (e: Exception) { - promise.reject("error", e.message) - } - } - - companion object { - const val NAME = "BsDiffPatch" - - init { - System.loadLibrary("react-native-bs-diff-patch") - } - - // Used to load the 'native-lib' library on application startup. - // get new file from old file and patch file - private external fun bsPatchFile(oldFile: String, newFile: String, patchFile: String): Int - - // generate patch file - private external fun bsDiffFile(oldFile: String, newFile: String, patchFile: String): Int - } -} diff --git a/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchNative.kt b/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchNative.kt new file mode 100644 index 0000000..2bf283e --- /dev/null +++ b/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchNative.kt @@ -0,0 +1,95 @@ +package com.jimmydaddy.bsdiffpatch + +import java.io.File + +internal object BsDiffPatchNative { + const val NAME = "BsDiffPatch" + + init { + System.loadLibrary("react-native-bs-diff-patch") + } + + fun patch(oldFile: String, newFile: String, patchFile: String): Int { + validateNonEmpty(oldFile, "oldFile") + validateNonEmpty(newFile, "newFile") + validateNonEmpty(patchFile, "patchFile") + validateDistinct(oldFile, newFile, patchFile) + + val oldFileObj = File(normalizePath(oldFile)) + val newFileObj = File(normalizePath(newFile)) + val patchFileObj = File(normalizePath(patchFile)) + + if (!oldFileObj.exists()) { + throw BsDiffPatchException("ENOENT", "oldFile: $oldFile does not exist") + } + if (!patchFileObj.exists()) { + throw BsDiffPatchException("ENOENT", "patchFile: $patchFile does not exist") + } + if (newFileObj.exists()) { + throw BsDiffPatchException("EEXIST", "newFile: $newFile already exists") + } + + return bsPatchFile( + oldFileObj.absolutePath, + newFileObj.absolutePath, + patchFileObj.absolutePath + ) + } + + fun diff(oldFile: String, newFile: String, patchFile: String): Int { + validateNonEmpty(oldFile, "oldFile") + validateNonEmpty(newFile, "newFile") + validateNonEmpty(patchFile, "patchFile") + validateDistinct(oldFile, newFile, patchFile) + + val oldFileObj = File(normalizePath(oldFile)) + val newFileObj = File(normalizePath(newFile)) + val patchFileObj = File(normalizePath(patchFile)) + + if (!oldFileObj.exists()) { + throw BsDiffPatchException("ENOENT", "oldFile: $oldFile does not exist") + } + if (!newFileObj.exists()) { + throw BsDiffPatchException("ENOENT", "newFile: $newFile does not exist") + } + if (patchFileObj.exists()) { + throw BsDiffPatchException("EEXIST", "patchFile: $patchFile already exists") + } + + return bsDiffFile( + oldFileObj.absolutePath, + newFileObj.absolutePath, + patchFileObj.absolutePath + ) + } + + private fun validateNonEmpty(value: String, fieldName: String) { + if (value.isEmpty()) { + throw BsDiffPatchException("EINVAL", "$fieldName can not be null or empty") + } + } + + private fun validateDistinct(oldFile: String, newFile: String, patchFile: String) { + if (oldFile == newFile || oldFile == patchFile || newFile == patchFile) { + throw BsDiffPatchException( + "EINVAL", + "oldFile, newFile, patchFile can not be the same" + ) + } + } + + private fun normalizePath(path: String): String { + return if (path.startsWith("file://")) path.substring(7) else path + } + + @JvmStatic + private external fun bsPatchFile(oldFile: String, newFile: String, patchFile: String): Int + + @JvmStatic + private external fun bsDiffFile(oldFile: String, newFile: String, patchFile: String): Int +} + +internal class BsDiffPatchException( + val code: String, + override val message: String +) : IllegalArgumentException(message) diff --git a/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt b/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt deleted file mode 100644 index fba9624..0000000 --- a/android/src/main/java/com/jimmydaddy/bsdiffpatch/BsDiffPatchPackage.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.jimmydaddy.bsdiffpatch - -import com.facebook.react.ReactPackage -import com.facebook.react.bridge.NativeModule -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.uimanager.ViewManager - -class BsDiffPatchPackage : ReactPackage { - override fun createNativeModules(reactContext: ReactApplicationContext): List { - val modules: MutableList = ArrayList() - modules.add(BsDiffPatchModule(reactContext)) - return modules - } - - override fun createViewManagers(reactContext: ReactApplicationContext): List> { - return emptyList() - } -} diff --git a/ios/BsDiffPatch.mm b/ios/BsDiffPatch.mm index cc6886c..eb6560d 100644 --- a/ios/BsDiffPatch.mm +++ b/ios/BsDiffPatch.mm @@ -1,5 +1,10 @@ #import "BsDiffPatch.h" +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#endif + @implementation BsDiffPatch RCT_EXPORT_MODULE() @@ -89,4 +94,12 @@ @implementation BsDiffPatch resolve(result); } +#ifdef RCT_NEW_ARCH_ENABLED +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} +#endif + @end diff --git a/package.json b/package.json index b7c1d17..1657254 100644 --- a/package.json +++ b/package.json @@ -161,5 +161,13 @@ } ] ] + }, + "codegenConfig": { + "name": "RNBsDiffPatchSpec", + "type": "modules", + "jsSrcsDir": "src", + "android": { + "javaPackageName": "com.jimmydaddy.bsdiffpatch" + } } } diff --git a/src/NativeBsDiffPatch.ts b/src/NativeBsDiffPatch.ts new file mode 100644 index 0000000..c017957 --- /dev/null +++ b/src/NativeBsDiffPatch.ts @@ -0,0 +1,9 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + patch(oldFile: string, newFile: string, patchFile: string): Promise; + diff(oldFile: string, newFile: string, patchFile: string): Promise; +} + +export default TurboModuleRegistry.getEnforcing('BsDiffPatch'); diff --git a/src/index.ts b/src/index.ts index b74e1c1..e00b0f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,4 @@ -import { NativeModules, Platform } from 'react-native'; - -const LINKING_ERROR = - `The package 'react-native-bs-diff-patch' doesn't seem to be linked. Make sure: \n\n` + - Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + - '- You rebuilt the app after installing the package\n' + - '- You are not using Expo Go\n'; - -const BsDiffPatch = NativeModules.BsDiffPatch - ? NativeModules.BsDiffPatch - : new Proxy( - {}, - { - get() { - throw new Error(LINKING_ERROR); - }, - } - ); +import BsDiffPatch from './NativeBsDiffPatch'; /** * generate new file from old file and patch file