diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 21de59da4..748f4f238 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -5,8 +5,18 @@ react_native_json = JSON.parse(File.read(File.join(react_native_node_modules_dir react_native_minor_version = react_native_json['version'].split('.')[1].to_i pods_root = Pod::Config.instance.project_pods_root -react_native_reanimated_node_modules_dir = ENV['REACT_NATIVE_REANIMATED_NODE_MODULES_DIR'] || File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('react-native-reanimated/package.json')"`) -react_native_reanimated_node_modules_dir_from_pods_root = Pathname.new(react_native_reanimated_node_modules_dir).relative_path_from(pods_root).to_s + +worklets_installed = system(%Q[ + cd "#{Pod::Config.instance.installation_root}" && + node -e "require.resolve('react-native-worklets/package.json')" > /dev/null 2>&1 +]) +react_native_worklets_path = `cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('react-native-worklets/package.json')"` +worklets_installed = react_native_worklets_path != "" +worklets_package_name = worklets_installed ? 'react-native-worklets' : 'react-native-reanimated' + +react_native_worklets_or_reanimated_node_modules_dir = ENV['REACT_NATIVE_WORKLETS_NODE_MODULES_DIR'] || ENV['REACT_NATIVE_REANIMATED_NODE_MODULES_DIR'] || + File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('#{worklets_package_name}/package.json')"`) +react_native_worklets_or_reanimated_node_modules_dir_from_pods_root = Pathname.new(react_native_worklets_or_reanimated_node_modules_dir).relative_path_from(pods_root).to_s 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' @@ -24,15 +34,23 @@ Pod::Spec.new do |s| s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}" - s.dependency "RNReanimated/worklets" + if worklets_installed + s.dependency "RNWorklets" + else + s.dependency "RNReanimated/worklets" + end - s.xcconfig = { + xcconfig = { "OTHER_CFLAGS" => "$(inherited) -DREACT_NATIVE_MINOR_VERSION=#{react_native_minor_version}", "HEADER_SEARCH_PATHS" => [ - "\"$(PODS_ROOT)/#{react_native_reanimated_node_modules_dir_from_pods_root}/apple\"", - "\"$(PODS_ROOT)/#{react_native_reanimated_node_modules_dir_from_pods_root}/Common/cpp\"", + "\"$(PODS_ROOT)/#{react_native_worklets_or_reanimated_node_modules_dir_from_pods_root}/apple\"", + "\"$(PODS_ROOT)/#{react_native_worklets_or_reanimated_node_modules_dir_from_pods_root}/Common/cpp\"", ].join(' '), } + if worklets_installed + xcconfig["OTHER_CFLAGS"] << " -DWORKLETS_INSTALLED=1" + end + s.xcconfig = xcconfig s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp\"" } diff --git a/android/build.gradle b/android/build.gradle index 49d41ef53..f78d677f5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -67,6 +67,7 @@ def getReactNativeMinorVersion() { } def REACT_NATIVE_MINOR_VERSION = getReactNativeMinorVersion() +def workletsInstalled = rootProject.findProject(':react-native-worklets') != null android { if (supportsNamespace()) { @@ -94,7 +95,9 @@ android { arguments "-DANDROID_STL=c++_shared", "-DANDROID_TOOLCHAIN=clang", "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}", - "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", + "-DREACT_NATIVE_ROOT_DIR=${resolveReactNativeDirectory().path}", + "-DWORKLETS_INSTALLED=${workletsInstalled}" abiFilters (*reactNativeArchitectures()) } } @@ -177,7 +180,12 @@ repositories { dependencies { implementation "com.facebook.react:react-android" // version substituted by RNGP implementation "com.facebook.react:hermes-android" // version substituted by RNGP - implementation project(":react-native-reanimated") + + if (workletsInstalled) { + implementation project(":react-native-worklets") + } else { + implementation project(":react-native-reanimated") + } } // This fixes linking errors due to undefined symbols from libworklets.so. @@ -185,6 +193,11 @@ dependencies { // like a header-only library. During build, config files are not regenerated // because the cache key does not change and AGP thinks that they are up-to-date. afterEvaluate { - prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabDebugPackage") - prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabReleasePackage") + if (workletsInstalled) { + prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabDebugPackage") + prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabReleasePackage") + } else { + prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabDebugPackage") + prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabReleasePackage") + } } diff --git a/android/src/main/cpp/CMakeLists.txt b/android/src/main/cpp/CMakeLists.txt index e5ba0eaed..19d9db854 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -4,6 +4,11 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) +if(WORKLETS_INSTALLED) + include("${REACT_NATIVE_ROOT_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake") + add_compile_options(${folly_FLAGS}) +endif() + add_compile_options(-fvisibility=hidden -fexceptions -frtti) string(APPEND CMAKE_CXX_FLAGS " -DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}") @@ -15,16 +20,32 @@ file(GLOB CPP_SRC CONFIGURE_DEPENDS "${CPP_DIR}/*.cpp") add_library(${CMAKE_PROJECT_NAME} SHARED ${ANDROID_SRC} ${CPP_SRC}) -target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR}) +if(WORKLETS_INSTALLED) + target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR} "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsiexecutor") +else() + target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR}) +endif() find_package(fbjni REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG) -find_package(react-native-reanimated REQUIRED CONFIG) + +if(WORKLETS_INSTALLED) + find_package(react-native-worklets REQUIRED CONFIG) + add_definitions(-DWORKLETS_INSTALLED) +else() + find_package(react-native-reanimated REQUIRED CONFIG) +endif() + target_link_libraries( ${CMAKE_PROJECT_NAME} fbjni::fbjni ReactAndroid::jsi ReactAndroid::reactnative - react-native-reanimated::worklets ) + +if(WORKLETS_INSTALLED) + target_link_libraries(${CMAKE_PROJECT_NAME} react-native-worklets::worklets) +else() + target_link_libraries(${CMAKE_PROJECT_NAME} react-native-reanimated::worklets) +endif() diff --git a/apple/MarkdownParser.mm b/apple/MarkdownParser.mm index 9d585e3fa..c4cfb79b0 100644 --- a/apple/MarkdownParser.mm +++ b/apple/MarkdownParser.mm @@ -19,7 +19,11 @@ @implementation MarkdownParser { const auto &markdownRuntime = expensify::livemarkdown::getMarkdownRuntime(); jsi::Runtime &rt = markdownRuntime->getJSIRuntime(); +#ifdef WORKLETS_INSTALLED + std::shared_ptr markdownWorklet; +#else std::shared_ptr markdownWorklet; +#endif try { markdownWorklet = expensify::livemarkdown::getMarkdownWorklet([parserId intValue]); } catch (const std::out_of_range &error) { diff --git a/cpp/MarkdownGlobal.cpp b/cpp/MarkdownGlobal.cpp index 56fd6de88..67f93eb43 100644 --- a/cpp/MarkdownGlobal.cpp +++ b/cpp/MarkdownGlobal.cpp @@ -17,11 +17,11 @@ std::shared_ptr getMarkdownRuntime() { return globalMarkdownWorkletRuntime; } -std::unordered_map> globalMarkdownShareableWorklets; +std::unordered_map> globalMarkdownShareableWorklets; std::mutex globalMarkdownShareableWorkletsMutex; int nextParserId = 1; -const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { +const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { assert(markdownWorklet != nullptr); auto parserId = nextParserId++; std::unique_lock lock(globalMarkdownShareableWorkletsMutex); @@ -34,7 +34,7 @@ void unregisterMarkdownWorklet(const int parserId) { globalMarkdownShareableWorklets.erase(parserId); } -std::shared_ptr getMarkdownWorklet(const int parserId) { +std::shared_ptr getMarkdownWorklet(const int parserId) { std::unique_lock lock(globalMarkdownShareableWorkletsMutex); return globalMarkdownShareableWorklets.at(parserId); } diff --git a/cpp/MarkdownGlobal.h b/cpp/MarkdownGlobal.h index 1edfb45b1..677b39110 100644 --- a/cpp/MarkdownGlobal.h +++ b/cpp/MarkdownGlobal.h @@ -10,15 +10,19 @@ using namespace worklets; namespace expensify { namespace livemarkdown { +#ifndef WORKLETS_INSTALLED +using SerializableWorklet = ShareableWorklet; // ShareableWorklet was renamed to SerializableWorklet +#endif + void setMarkdownRuntime(const std::shared_ptr &markdownWorkletRuntime); std::shared_ptr getMarkdownRuntime(); -const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); +const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); void unregisterMarkdownWorklet(const int parserId); -std::shared_ptr getMarkdownWorklet(const int parserId); +std::shared_ptr getMarkdownWorklet(const int parserId); } // namespace livemarkdown } // namespace expensify diff --git a/cpp/RuntimeDecorator.cpp b/cpp/RuntimeDecorator.cpp index 5332e30df..74746277b 100644 --- a/cpp/RuntimeDecorator.cpp +++ b/cpp/RuntimeDecorator.cpp @@ -23,7 +23,12 @@ void injectJSIBindings(jsi::Runtime &rt) { jsi::PropNameID::forAscii(rt, "jsi_registerMarkdownWorklet"), 1, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { - auto parserId = registerMarkdownWorklet(extractShareableOrThrow(rt, args[0])); +#ifdef WORKLETS_INSTALLED + auto worklet = extractSerializableOrThrow(rt, args[0]); +#else + auto worklet = extractShareableOrThrow(rt, args[0]); +#endif + auto parserId = registerMarkdownWorklet(worklet); return jsi::Value(parserId); })); diff --git a/example/babel.config.js b/example/babel.config.js index 353c7d446..f51a858ae 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -1,6 +1,14 @@ const path = require('path'); const pak = require('../package.json'); +let workletsPlugin = null; +try { + require.resolve('react-native-worklets'); + workletsPlugin = 'react-native-worklets/plugin'; +} catch (e) { + workletsPlugin = 'react-native-reanimated/plugin'; +} + module.exports = { presets: ['module:@react-native/babel-preset'], plugins: [ @@ -13,6 +21,6 @@ module.exports = { }, }, ], - 'react-native-reanimated/plugin', + workletsPlugin, ], }; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ae0f76328..7d98ed6b5 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2253,7 +2253,7 @@ PODS: - React-perflogger (= 0.81.4) - React-utils (= 0.81.4) - SocketRocket - - RNLiveMarkdown (0.1.303): + - RNLiveMarkdown (0.1.305): - boost - DoubleConversion - fast_float @@ -2279,10 +2279,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/worklets + - RNWorklets - SocketRocket - Yoga - - RNReanimated (3.19.0): + - RNReanimated (4.1.3): - boost - DoubleConversion - fast_float @@ -2309,11 +2309,11 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated (= 3.19.0) - - RNReanimated/worklets (= 3.19.0) + - RNReanimated/reanimated (= 4.1.3) + - RNWorklets - SocketRocket - Yoga - - RNReanimated/reanimated (3.19.0): + - RNReanimated/reanimated (4.1.3): - boost - DoubleConversion - fast_float @@ -2340,10 +2340,11 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated/apple (= 3.19.0) + - RNReanimated/reanimated/apple (= 4.1.3) + - RNWorklets - SocketRocket - Yoga - - RNReanimated/reanimated/apple (3.19.0): + - RNReanimated/reanimated/apple (4.1.3): - boost - DoubleConversion - fast_float @@ -2370,9 +2371,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - RNWorklets - SocketRocket - Yoga - - RNReanimated/worklets (3.19.0): + - RNWorklets (0.6.1): - boost - DoubleConversion - fast_float @@ -2399,10 +2401,40 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/worklets/apple (= 3.19.0) + - RNWorklets/worklets (= 0.6.1) - SocketRocket - Yoga - - RNReanimated/worklets/apple (3.19.0): + - RNWorklets/worklets (0.6.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNWorklets/worklets/apple (= 0.6.1) + - SocketRocket + - Yoga + - RNWorklets/worklets/apple (0.6.1): - boost - DoubleConversion - fast_float @@ -2508,6 +2540,7 @@ DEPENDENCIES: - ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`) - RNLiveMarkdown (from `../..`) - RNReanimated (from `../../node_modules/react-native-reanimated`) + - RNWorklets (from `../../node_modules/react-native-worklets`) - SocketRocket (~> 0.7.1) - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`) @@ -2661,6 +2694,8 @@ EXTERNAL SOURCES: :path: "../.." RNReanimated: :path: "../../node_modules/react-native-reanimated" + RNWorklets: + :path: "../../node_modules/react-native-worklets" Yoga: :path: "../../node_modules/react-native/ReactCommon/yoga" @@ -2735,10 +2770,11 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 433ddfb4536948630aadd5bd925aff8a632d2fe3 ReactCodegen: adf5027f30e34c68b5a09f0b68acb3e5ef9e1a5d ReactCommon: 394c6b92765cf6d211c2c3f7f6bc601dffb316a6 - RNLiveMarkdown: 0a98549fd315dc5fdc8b7ed937fdd6414d47565f - RNReanimated: 08fa7bb56d5f6d47c32245cf9cf53efb19befa0c + RNLiveMarkdown: 30f59122c1f27ea2ec2ad43935b1be214e10c697 + RNReanimated: e04fc279d4e0d936a27f50c9a170cfa269c3434d + RNWorklets: 879b69a98caa893353b964b6fece154a819145af SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 922d794dce2af9c437f864bf4093abfa7a131adb + Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d PODFILE CHECKSUM: 3f53660915b3f926239de7f89ab29581306a2614 diff --git a/example/package.json b/example/package.json index 12bd2a94b..588ef9438 100644 --- a/example/package.json +++ b/example/package.json @@ -12,7 +12,8 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-reanimated": "3.19.0" + "react-native-reanimated": "4.1.3", + "react-native-worklets": "0.6.1" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/package-lock.json b/package-lock.json index 3c8d67b9e..5e9e6e5d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,20 +52,27 @@ "react-dom": "19.1.0", "react-native": "0.81.4", "react-native-builder-bob": "^0.30.2", - "react-native-reanimated": "3.19.0", + "react-native-reanimated": "4.1.3", "react-native-web": "^0.20.0", + "react-native-worklets": "^0.6.1", "release-it": "^15.0.0", "turbo": "^1.10.7", "typescript": "^5.8.3" }, "engines": { - "node": ">= 20.19.4" + "node": ">= 18.0.0" }, "peerDependencies": { "expensify-common": ">=2.0.148", "react": "*", "react-native": "*", - "react-native-reanimated": ">=3.17.0" + "react-native-reanimated": ">=3.17.0", + "react-native-worklets": ">=0.6.0" + }, + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } } }, "example": { @@ -75,7 +82,8 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-reanimated": "3.19.0" + "react-native-reanimated": "4.1.3", + "react-native-worklets": "0.6.1" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -23257,37 +23265,29 @@ } }, "node_modules/react-native-is-edge-to-edge": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz", - "integrity": "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", + "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==", + "license": "MIT", "peerDependencies": { "react": "*", "react-native": "*" } }, "node_modules/react-native-reanimated": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.19.0.tgz", - "integrity": "sha512-FNfqLuPuVHsW9KcsZtnJqIPlMQvuySnSFJXgSt9fVDPqptbSUkiAF6MthUwd4Mxt05hCRcbV+T65CENgVS5iCg==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz", + "integrity": "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==", "license": "MIT", "dependencies": { - "@babel/plugin-transform-arrow-functions": "^7.0.0-0", - "@babel/plugin-transform-class-properties": "^7.0.0-0", - "@babel/plugin-transform-classes": "^7.0.0-0", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", - "@babel/plugin-transform-optional-chaining": "^7.0.0-0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", - "@babel/plugin-transform-template-literals": "^7.0.0-0", - "@babel/plugin-transform-unicode-regex": "^7.0.0-0", - "@babel/preset-typescript": "^7.16.7", - "convert-source-map": "^2.0.0", - "invariant": "^2.2.4", - "react-native-is-edge-to-edge": "1.1.7" + "react-native-is-edge-to-edge": "^1.2.1", + "semver": "7.7.2" }, "peerDependencies": { "@babel/core": "^7.0.0-0", "react": "*", - "react-native": "*" + "react-native": "*", + "react-native-worklets": ">=0.5.0" } }, "node_modules/react-native-web": { @@ -23322,6 +23322,30 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/react-native-worklets": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.1.tgz", + "integrity": "sha512-URca8l7c7Uog7gv4mcg9KILdJlnbvwdS5yfXQYf5TDkD2W1VY1sduEKrD+sA3lUPXH/TG1vmXAvNxCNwPMYgGg==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-class-properties": "^7.0.0-0", + "@babel/plugin-transform-classes": "^7.0.0-0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", + "@babel/plugin-transform-optional-chaining": "^7.0.0-0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", + "@babel/plugin-transform-template-literals": "^7.0.0-0", + "@babel/plugin-transform-unicode-regex": "^7.0.0-0", + "@babel/preset-typescript": "^7.16.7", + "convert-source-map": "^2.0.0", + "semver": "7.7.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0", + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native/node_modules/@react-native/codegen": { "version": "0.81.4", "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.81.4.tgz", @@ -24727,9 +24751,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index 2f3f1b29b..c8a4549e2 100644 --- a/package.json +++ b/package.json @@ -98,17 +98,24 @@ "react-dom": "19.1.0", "react-native": "0.81.4", "react-native-builder-bob": "^0.30.2", - "react-native-reanimated": "3.19.0", "react-native-web": "^0.20.0", "release-it": "^15.0.0", "turbo": "^1.10.7", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "react-native-reanimated": "4.1.3", + "react-native-worklets": "^0.6.1" }, "peerDependencies": { "expensify-common": ">=2.0.148", "react": "*", "react-native": "*", - "react-native-reanimated": ">=3.17.0" + "react-native-reanimated": ">=3.17.0", + "react-native-worklets": ">=0.6.0" + }, + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } }, "overrides": { "@expo/webpack-config": { diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index 82ee8953e..84f9684ca 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -2,15 +2,12 @@ import {StyleSheet, TextInput, processColor} from 'react-native'; import React from 'react'; import type {TextInputProps} from 'react-native'; import {createWorkletRuntime, makeShareableCloneRecursive} from 'react-native-reanimated'; -import type {WorkletRuntime} from 'react-native-reanimated'; -import type {ShareableRef, WorkletFunction} from 'react-native-reanimated/lib/typescript/commonTypes'; - import MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent'; import type {MarkdownStyle} from './MarkdownTextInputDecoratorViewNativeComponent'; import NativeLiveMarkdownModule from './NativeLiveMarkdownModule'; import {mergeMarkdownStyleWithDefault} from './styleUtils'; import type {PartialMarkdownStyle} from './styleUtils'; -import type {InlineImagesInputProps, MarkdownRange} from './commonTypes'; +import type {InlineImagesInputProps, MarkdownRange, ShareableRef, WorkletFunction, WorkletRuntime} from './commonTypes'; declare global { // eslint-disable-next-line no-var @@ -50,7 +47,7 @@ function initializeLiveMarkdownIfNeeded() { function registerParser(parser: (input: string) => MarkdownRange[]): number { initializeLiveMarkdownIfNeeded(); - const shareableWorklet = makeShareableCloneRecursive(parser) as ShareableRef>; + const shareableWorklet = makeShareableCloneRecursive(parser) as unknown as ShareableRef>; const parserId = global.jsi_registerMarkdownWorklet(shareableWorklet); return parserId; } diff --git a/src/commonTypes.ts b/src/commonTypes.ts index db74ea387..23e1fb737 100644 --- a/src/commonTypes.ts +++ b/src/commonTypes.ts @@ -28,4 +28,78 @@ type InlineImagesInputProps = { imagePreviewAuthRequiredURLs?: string[]; }; -export type {MarkdownType, MarkdownRange, InlineImagesInputProps}; +// Temporary types from `react-native-reanimated` and `react-native-worklets` +// TODO: remove once `react-native-reanimated` dependency is removed + +type WorkletStackDetails = [error: Error, lineOffset: number, columnOffset: number]; + +type WorkletClosure = Record; + +interface WorkletInitData { + code: string; + /** Only in dev builds. */ + location?: string; + /** Only in dev builds. */ + sourceMap?: string; +} + +interface WorkletProps { + __closure: WorkletClosure; + __workletHash: number; + /** Only in Legacy Bundling. */ + __initData?: WorkletInitData; + /** Only for Handles. */ + __init?: () => unknown; + /** `__stackDetails` is removed after parsing. */ + __stackDetails?: WorkletStackDetails; + /** Only in dev builds. */ + __pluginVersion?: string; +} + +type WorkletFunctionWorklets = ((...args: TArgs) => TReturn) & WorkletProps; + +interface WorkletInitDataCommon { + code: string; +} + +type WorkletInitDataRelease = WorkletInitDataCommon; + +interface WorkletInitDataDev extends WorkletInitDataCommon { + location: string; + sourceMap: string; + version: string; +} + +interface WorkletBaseCommon { + __closure: WorkletClosure; + __workletHash: number; +} + +interface WorkletBaseRelease extends WorkletBaseCommon { + __initData: WorkletInitDataRelease; +} + +interface WorkletBaseDev extends WorkletBaseCommon { + __initData: WorkletInitDataDev; + /** `__stackDetails` is removed after parsing. */ + __stackDetails?: WorkletStackDetails; +} + +type WorkletFunctionDev = ((...args: Args) => ReturnValue) & WorkletBaseDev; + +type WorkletFunctionRelease = ((...args: Args) => ReturnValue) & WorkletBaseRelease; + +type WorkletFunctionReanimated = WorkletFunctionDev | WorkletFunctionRelease; + +type WorkletFunction = WorkletFunctionWorklets | WorkletFunctionReanimated; + +type ShareableRef = { + __hostObjectShareableJSRef: T; +}; + +type WorkletRuntime = { + __hostObjectWorkletRuntime: never; + readonly name: string; +}; + +export type {MarkdownType, MarkdownRange, InlineImagesInputProps, WorkletFunction, ShareableRef, WorkletRuntime}; diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index 15f520a86..85185533b 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -4,8 +4,7 @@ import {Platform} from 'react-native'; import {ExpensiMark} from 'expensify-common'; import {unescapeText} from 'expensify-common/dist/utils'; import {decode} from 'html-entities'; -import type {WorkletFunction} from 'react-native-reanimated/lib/typescript/commonTypes'; -import type {MarkdownType, MarkdownRange} from './commonTypes'; +import type {MarkdownType, MarkdownRange, WorkletFunction} from './commonTypes'; import {groupRanges, sortRanges, excludeRangeTypesFromFormatting, getRangesToExcludeFormatting} from './rangeUtils'; function isWeb() { @@ -17,7 +16,7 @@ function isJest() { } // eslint-disable-next-line no-underscore-dangle -if (__DEV__ && !isWeb() && !isJest() && (decode as WorkletFunction).__workletHash === undefined) { +if (__DEV__ && !isWeb() && !isJest() && (decode as WorkletFunction).__workletHash === undefined) { throw new Error( "[react-native-live-markdown] `parseExpensiMark` requires `html-entities` package to be workletized. Please add `'worklet';` directive at the top of `node_modules/html-entities/lib/index.js` using patch-package. Make sure you've installed `html-entities` version 2.5.3 exactly as otherwise there is no `lib/` directory.", );