From 1ab9b139cdcb39391111539e6d63128289ae40d3 Mon Sep 17 00:00:00 2001 From: war-in Date: Thu, 2 Oct 2025 18:51:02 +0200 Subject: [PATCH 01/22] wip --- RNLiveMarkdown.podspec | 33 ++++++++++++++++--- android/build.gradle | 32 +++++++++++++++--- android/src/main/cpp/CMakeLists.txt | 36 +++++++++++++++++++-- cpp/MarkdownGlobal.cpp | 12 +++++++ cpp/MarkdownGlobal.h | 10 +++++- cpp/RuntimeDecorator.cpp | 7 ++++ example/babel.config.js | 2 +- example/ios/Podfile.lock | 6 ++-- example/package.json | 2 +- package-lock.json | 50 ++++++++++++++++++++++++----- package.json | 12 +++++-- src/MarkdownTextInput.tsx | 45 +++++++++++++++++++++++--- src/parseExpensiMark.ts | 2 +- 13 files changed, 215 insertions(+), 34 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 21de59da4..c8aee54f1 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -5,8 +5,25 @@ 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 + +find_installed_package = ->(name) do + package_path = File.join(react_native_node_modules_dir, name) + File.directory?(package_path) +end + +is_reanimated = find_installed_package.call('react-native-reanimated') +is_worklets = find_installed_package.call('react-native-worklets') + +package_name = if is_reanimated + 'react-native-reanimated/package.json' + elsif is_worklets + 'react-native-worklets/package.json' + else + raise "Error!" + end + +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('#{package_name}')"`) +react_native_worklets_node_modules_dir_from_pods_root = Pathname.new(react_native_worklets_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,13 +41,19 @@ Pod::Spec.new do |s| s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}" - s.dependency "RNReanimated/worklets" + if reanimated + s.dependency "RNReanimated/worklets" + elsif worklets + s.dependency "RNWorklets" + else + raise "Error!" + end s.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_node_modules_dir_from_pods_root}/apple\"", + "\"$(PODS_ROOT)/#{react_native_worklets_node_modules_dir_from_pods_root}/Common/cpp\"", ].join(' '), } diff --git a/android/build.gradle b/android/build.gradle index 49d41ef53..fca99374e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -58,6 +58,16 @@ def resolveReactNativeDirectory() { throw new GradleException("[react-native-live-markdown] Unable to resolve react-native location in node_modules. Your app should define `REACT_NATIVE_NODE_MODULES_DIR` extension property in `app/build.gradle` with a path to react-native in node_modules.") } +println resolveReactNativeDirectory().parent + +def findInstalledPackage = { name -> + def reactNativeLocation = resolveReactNativeDirectory().parent + println reactNativeLocation + if (!reactNativeLocation) return false + def packagePath = new File(reactNativeLocation, name) + return packagePath.isDirectory() +} + def getReactNativeMinorVersion() { def reactNativeRootDir = resolveReactNativeDirectory() def reactNativeProperties = new Properties() @@ -67,6 +77,8 @@ def getReactNativeMinorVersion() { } def REACT_NATIVE_MINOR_VERSION = getReactNativeMinorVersion() +def isReanimated = findInstalledPackage('react-native-reanimated') +def isWorklets = findInstalledPackage('react-native-worklets') android { if (supportsNamespace()) { @@ -94,7 +106,8 @@ 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()}" abiFilters (*reactNativeArchitectures()) } } @@ -173,11 +186,15 @@ repositories { google() } - 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 (isReanimated) { + implementation project(":react-native-reanimated") + } else if (isWorklets) { + implementation project(":react-native-worklets") + } } // This fixes linking errors due to undefined symbols from libworklets.so. @@ -185,6 +202,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 (isReanimated) { + prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabDebugPackage") + prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabReleasePackage") + } else if (isWorklets) { + prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabDebugPackage") + prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabReleasePackage") + } } diff --git a/android/src/main/cpp/CMakeLists.txt b/android/src/main/cpp/CMakeLists.txt index e5ba0eaed..74777d9d7 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -4,6 +4,23 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) +set(NODE_MODULES_DIR "${REACT_NATIVE_ROOT_DIR}/..") +set(REACT_NATIVE_WORKLETS_DIR "${NODE_MODULES_DIR}/react-native-worklets") +set(REACT_NATIVE_REANIMATED_DIR "${NODE_MODULES_DIR}/react-native-reanimated") + +if(EXISTS "${REACT_NATIVE_WORKLETS_DIR}") + set(IS_WORKLETS TRUE) +endif() + +if(EXISTS "${REACT_NATIVE_REANIMATED_DIR}") + set(IS_REANIMATED TRUE) +endif() + +if(IS_WORKLETS) + 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 +32,29 @@ 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}) +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR} "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsiexecutor") find_package(fbjni REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG) -find_package(react-native-reanimated REQUIRED CONFIG) + +if(IS_REANIMATED) + find_package(react-native-reanimated CONFIG QUIET) +endif() +if(IS_WORKLETS) + find_package(react-native-worklets REQUIRED CONFIG) + add_definitions(-DIS_WORKLETS) +endif() target_link_libraries( ${CMAKE_PROJECT_NAME} fbjni::fbjni ReactAndroid::jsi ReactAndroid::reactnative - react-native-reanimated::worklets + react-native-worklets::worklets ) + +if(IS_REANIMATED) + target_link_libraries(${CMAKE_PROJECT_NAME} react-native-reanimated::worklets) +elseif(IS_WORKLETS) + target_link_libraries(${CMAKE_PROJECT_NAME} react-native-worklets::worklets) +endif() diff --git a/cpp/MarkdownGlobal.cpp b/cpp/MarkdownGlobal.cpp index 56fd6de88..f3d966e42 100644 --- a/cpp/MarkdownGlobal.cpp +++ b/cpp/MarkdownGlobal.cpp @@ -17,11 +17,19 @@ std::shared_ptr getMarkdownRuntime() { return globalMarkdownWorkletRuntime; } +#ifdef IS_WORKLETS +std::unordered_map> globalMarkdownShareableWorklets; +#else std::unordered_map> globalMarkdownShareableWorklets; +#endif std::mutex globalMarkdownShareableWorkletsMutex; int nextParserId = 1; +#ifdef IS_WORKLETS +const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { +#else const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { +#endif assert(markdownWorklet != nullptr); auto parserId = nextParserId++; std::unique_lock lock(globalMarkdownShareableWorkletsMutex); @@ -34,7 +42,11 @@ void unregisterMarkdownWorklet(const int parserId) { globalMarkdownShareableWorklets.erase(parserId); } +#ifdef IS_WORKLETS +std::shared_ptr getMarkdownWorklet(const int parserId) { +#else std::shared_ptr getMarkdownWorklet(const int parserId) { +#endif std::unique_lock lock(globalMarkdownShareableWorkletsMutex); return globalMarkdownShareableWorklets.at(parserId); } diff --git a/cpp/MarkdownGlobal.h b/cpp/MarkdownGlobal.h index 1edfb45b1..5d32453fe 100644 --- a/cpp/MarkdownGlobal.h +++ b/cpp/MarkdownGlobal.h @@ -14,11 +14,19 @@ void setMarkdownRuntime(const std::shared_ptr &markdownWorkletRu std::shared_ptr getMarkdownRuntime(); +#ifdef IS_WORKLETS +const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); +#else const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); +#endif void unregisterMarkdownWorklet(const int parserId); -std::shared_ptr getMarkdownWorklet(const int parserId); +#ifdef IS_WORKLETS +std::shared_ptr getMarkdownWorklet(const int parserId); +#else +const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); +#endif } // namespace livemarkdown } // namespace expensify diff --git a/cpp/RuntimeDecorator.cpp b/cpp/RuntimeDecorator.cpp index 5332e30df..80eb71420 100644 --- a/cpp/RuntimeDecorator.cpp +++ b/cpp/RuntimeDecorator.cpp @@ -1,5 +1,6 @@ #include "RuntimeDecorator.h" #include "MarkdownGlobal.h" +#include using namespace facebook; using namespace worklets; @@ -23,7 +24,13 @@ 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 { + #ifdef IS_WORKLETS + auto parserId = registerMarkdownWorklet( + extractSerializableOrThrow(rt, args[0]) + ); + #else auto parserId = registerMarkdownWorklet(extractShareableOrThrow(rt, args[0])); + #endif return jsi::Value(parserId); })); diff --git a/example/babel.config.js b/example/babel.config.js index 353c7d446..058e85775 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -13,6 +13,6 @@ module.exports = { }, }, ], - 'react-native-reanimated/plugin', + 'react-native-worklets/plugin', ], }; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ae0f76328..aaf64c2bd 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 @@ -2735,10 +2735,10 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 433ddfb4536948630aadd5bd925aff8a632d2fe3 ReactCodegen: adf5027f30e34c68b5a09f0b68acb3e5ef9e1a5d ReactCommon: 394c6b92765cf6d211c2c3f7f6bc601dffb316a6 - RNLiveMarkdown: 0a98549fd315dc5fdc8b7ed937fdd6414d47565f + RNLiveMarkdown: e441098898bcfb03fe2edc4bd56a1efe987a09e6 RNReanimated: 08fa7bb56d5f6d47c32245cf9cf53efb19befa0c SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 922d794dce2af9c437f864bf4093abfa7a131adb + Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d PODFILE CHECKSUM: 3f53660915b3f926239de7f89ab29581306a2614 diff --git a/example/package.json b/example/package.json index 12bd2a94b..c1c1a721f 100644 --- a/example/package.json +++ b/example/package.json @@ -12,7 +12,7 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-reanimated": "3.19.0" + "react-native-worklets": "0.6.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/package-lock.json b/package-lock.json index 3c8d67b9e..5ffc2277d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,20 +52,28 @@ "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" }, "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-reanimated": { + "optional": true + }, + "react-native-worklets": { + "optional": true + } } }, "example": { @@ -75,7 +83,7 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-reanimated": "3.19.0" + "react-native-worklets": "0.6.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -23270,6 +23278,8 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.19.0.tgz", "integrity": "sha512-FNfqLuPuVHsW9KcsZtnJqIPlMQvuySnSFJXgSt9fVDPqptbSUkiAF6MthUwd4Mxt05hCRcbV+T65CENgVS5iCg==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", @@ -23322,6 +23332,30 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/react-native-worklets": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.0.tgz", + "integrity": "sha512-yETMNuCcivdYWteuG4eRqgiAk2DzRCrVAaEBIEWPo4emrf3BNjadFo85L5QvyEusrX9QKE3ZEAx8U5A/nbyFgg==", + "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 +24761,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" @@ -27045,7 +27079,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 2f3f1b29b..faeacb7ad 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "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", @@ -108,7 +107,16 @@ "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 + }, + "react-native-reanimated": { + "optional": true + } }, "overrides": { "@expo/webpack-config": { diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index 82ee8953e..544aed82c 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -1,10 +1,13 @@ 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 {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 {createWorkletRuntime, makeShareableCloneRecursive } from 'react-native-worklets'; +// import type {ShareableRef, WorkletFunction} from 'react-native-reanimated/lib/typescript/commonTypes'; +// import type {WorkletRuntime as WorkletRuntimeReanimated} from 'react-native-reanimated'; +import type {WorkletRuntime as WorkletRuntimeWorklets} from 'react-native-worklets'; import MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent'; import type {MarkdownStyle} from './MarkdownTextInputDecoratorViewNativeComponent'; import NativeLiveMarkdownModule from './NativeLiveMarkdownModule'; @@ -12,6 +15,8 @@ import {mergeMarkdownStyleWithDefault} from './styleUtils'; import type {PartialMarkdownStyle} from './styleUtils'; import type {InlineImagesInputProps, MarkdownRange} from './commonTypes'; +type WorkletRuntime = WorkletRuntimeReanimated | WorkletRuntimeWorklets; + declare global { // eslint-disable-next-line no-var var jsi_setMarkdownRuntime: (runtime: WorkletRuntime) => void; @@ -43,13 +48,45 @@ function initializeLiveMarkdownIfNeeded() { if (!global.jsi_setMarkdownRuntime) { throw new Error('[react-native-live-markdown] global.jsi_setMarkdownRuntime is not available'); } + + let createWorkletRuntime; + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + createWorkletRuntime = require('react-native-worklets').createWorkletRuntime; + } catch { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + createWorkletRuntime = require('react-native-reanimated').createWorkletRuntime; + } catch { + /* empty */ + } + } + workletRuntime = createWorkletRuntime('LiveMarkdownRuntime'); + if (!workletRuntime) { + return; + } + global.jsi_setMarkdownRuntime(workletRuntime); initialized = true; } function registerParser(parser: (input: string) => MarkdownRange[]): number { initializeLiveMarkdownIfNeeded(); + + let makeShareableCloneRecursive; + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + makeShareableCloneRecursive = require('react-native-worklets').makeShareableCloneRecursive; + } catch { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + makeShareableCloneRecursive = require('react-native-reanimated').makeShareableCloneRecursive; + } catch { + /* empty */ + } + } + const shareableWorklet = makeShareableCloneRecursive(parser) as ShareableRef>; const parserId = global.jsi_registerMarkdownWorklet(shareableWorklet); return parserId; diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index 15f520a86..509532ad8 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -4,7 +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 {WorkletFunction} from 'react-native-reanimated/lib/typescript/commonTypes'; import type {MarkdownType, MarkdownRange} from './commonTypes'; import {groupRanges, sortRanges, excludeRangeTypesFromFormatting, getRangesToExcludeFormatting} from './rangeUtils'; From a3f46db1fff0e064e82f17b0fdbb7e5d033bb3ff Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 10:57:10 +0200 Subject: [PATCH 02/22] use a proper library in babel.config.js --- example/babel.config.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/example/babel.config.js b/example/babel.config.js index 058e85775..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-worklets/plugin', + workletsPlugin, ], }; From bbb432ac9dd7c97bea5c72776a5fab8df4e8ec5c Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 10:58:26 +0200 Subject: [PATCH 03/22] fix errors in cpp --- android/src/main/cpp/CMakeLists.txt | 1 - cpp/MarkdownGlobal.h | 2 +- cpp/RuntimeDecorator.cpp | 4 +--- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/android/src/main/cpp/CMakeLists.txt b/android/src/main/cpp/CMakeLists.txt index 74777d9d7..6e64089fc 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -50,7 +50,6 @@ target_link_libraries( fbjni::fbjni ReactAndroid::jsi ReactAndroid::reactnative - react-native-worklets::worklets ) if(IS_REANIMATED) diff --git a/cpp/MarkdownGlobal.h b/cpp/MarkdownGlobal.h index 5d32453fe..270b9d8aa 100644 --- a/cpp/MarkdownGlobal.h +++ b/cpp/MarkdownGlobal.h @@ -25,7 +25,7 @@ void unregisterMarkdownWorklet(const int parserId); #ifdef IS_WORKLETS std::shared_ptr getMarkdownWorklet(const int parserId); #else -const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); +std::shared_ptr getMarkdownWorklet(const int parserId); #endif } // namespace livemarkdown diff --git a/cpp/RuntimeDecorator.cpp b/cpp/RuntimeDecorator.cpp index 80eb71420..254133c95 100644 --- a/cpp/RuntimeDecorator.cpp +++ b/cpp/RuntimeDecorator.cpp @@ -25,9 +25,7 @@ void injectJSIBindings(jsi::Runtime &rt) { 1, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { #ifdef IS_WORKLETS - auto parserId = registerMarkdownWorklet( - extractSerializableOrThrow(rt, args[0]) - ); + auto parserId = registerMarkdownWorklet(extractSerializableOrThrow(rt, args[0])); #else auto parserId = registerMarkdownWorklet(extractShareableOrThrow(rt, args[0])); #endif From 40b237c62875516d44acf950a94e4f9a52ffebea Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 11:06:57 +0200 Subject: [PATCH 04/22] add missing types --- example/package.json | 2 +- package-lock.json | 31 +++---------------------------- package.json | 3 ++- src/MarkdownTextInput.tsx | 13 +++++-------- src/parseExpensiMark.ts | 2 ++ 5 files changed, 13 insertions(+), 38 deletions(-) diff --git a/example/package.json b/example/package.json index c1c1a721f..12bd2a94b 100644 --- a/example/package.json +++ b/example/package.json @@ -12,7 +12,7 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-worklets": "0.6.0" + "react-native-reanimated": "3.19.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/package-lock.json b/package-lock.json index 5ffc2277d..7658051fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "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", @@ -83,7 +84,7 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-worklets": "0.6.0" + "react-native-reanimated": "3.19.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -23278,8 +23279,6 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.19.0.tgz", "integrity": "sha512-FNfqLuPuVHsW9KcsZtnJqIPlMQvuySnSFJXgSt9fVDPqptbSUkiAF6MthUwd4Mxt05hCRcbV+T65CENgVS5iCg==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", @@ -23332,30 +23331,6 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, - "node_modules/react-native-worklets": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.0.tgz", - "integrity": "sha512-yETMNuCcivdYWteuG4eRqgiAk2DzRCrVAaEBIEWPo4emrf3BNjadFo85L5QvyEusrX9QKE3ZEAx8U5A/nbyFgg==", - "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", @@ -27079,7 +27054,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index faeacb7ad..dac181daa 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,8 @@ "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": "3.19.0" }, "peerDependencies": { "expensify-common": ">=2.0.148", diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index 544aed82c..55151c5ff 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -1,13 +1,6 @@ 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 {createWorkletRuntime, makeShareableCloneRecursive } from 'react-native-worklets'; -// import type {ShareableRef, WorkletFunction} from 'react-native-reanimated/lib/typescript/commonTypes'; -// import type {WorkletRuntime as WorkletRuntimeReanimated} from 'react-native-reanimated'; -import type {WorkletRuntime as WorkletRuntimeWorklets} from 'react-native-worklets'; import MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent'; import type {MarkdownStyle} from './MarkdownTextInputDecoratorViewNativeComponent'; import NativeLiveMarkdownModule from './NativeLiveMarkdownModule'; @@ -15,7 +8,11 @@ import {mergeMarkdownStyleWithDefault} from './styleUtils'; import type {PartialMarkdownStyle} from './styleUtils'; import type {InlineImagesInputProps, MarkdownRange} from './commonTypes'; -type WorkletRuntime = WorkletRuntimeReanimated | WorkletRuntimeWorklets; +type WorkletRuntime = any; +type WorkletFunction = Args | ReturnValue | any; +type ShareableRef = { + __hostObjectShareableJSRef: T; +}; declare global { // eslint-disable-next-line no-var diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index 509532ad8..60d45dbaa 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -16,6 +16,8 @@ function isJest() { return !!global.process.env.JEST_WORKER_ID; } +type WorkletFunction = any; + // eslint-disable-next-line no-underscore-dangle if (__DEV__ && !isWeb() && !isJest() && (decode as WorkletFunction).__workletHash === undefined) { throw new Error( From e77ccf3538882cb77925cb2ed53be3874bd8247b Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 11:32:43 +0200 Subject: [PATCH 05/22] fix variable name --- RNLiveMarkdown.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index c8aee54f1..a45f3fa7e 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -41,9 +41,9 @@ Pod::Spec.new do |s| s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}" - if reanimated + if is_reanimated s.dependency "RNReanimated/worklets" - elsif worklets + elsif is_worklets s.dependency "RNWorklets" else raise "Error!" From 864ab7c3602ba413875829e58ec09ee8569ab49b Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 12:46:44 +0200 Subject: [PATCH 06/22] fix android build scripts --- android/build.gradle | 16 ++++++++-------- android/src/main/cpp/CMakeLists.txt | 18 +++++++++++------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index fca99374e..30e9fc05f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -107,7 +107,7 @@ android { "-DANDROID_TOOLCHAIN=clang", "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", - "-DREACT_NATIVE_ROOT_DIR=${resolveReactNativeDirectory()}" + "-DREACT_NATIVE_ROOT_DIR=${resolveReactNativeDirectory().path}" abiFilters (*reactNativeArchitectures()) } } @@ -190,10 +190,10 @@ dependencies { implementation "com.facebook.react:react-android" // version substituted by RNGP implementation "com.facebook.react:hermes-android" // version substituted by RNGP - if (isReanimated) { - implementation project(":react-native-reanimated") - } else if (isWorklets) { + if (isWorklets) { implementation project(":react-native-worklets") + } else if (isReanimated) { + implementation project(":react-native-reanimated") } } @@ -202,11 +202,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 { - if (isReanimated) { - prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabDebugPackage") - prepareKotlinBuildScriptModel.dependsOn(":react-native-reanimated:prefabReleasePackage") - } else if (isWorklets) { + if (isWorklets) { prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabDebugPackage") prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabReleasePackage") + } else if (isReanimated) { + 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 6e64089fc..23da33ebb 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -32,19 +32,23 @@ 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} "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsiexecutor") +if(IS_WORKLETS) + target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR} "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsiexecutor") +elseif(IS_REANIMATED) + target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR}) +endif() find_package(fbjni REQUIRED CONFIG) find_package(ReactAndroid REQUIRED CONFIG) -if(IS_REANIMATED) - find_package(react-native-reanimated CONFIG QUIET) -endif() if(IS_WORKLETS) find_package(react-native-worklets REQUIRED CONFIG) add_definitions(-DIS_WORKLETS) +elseif(IS_REANIMATED) + find_package(react-native-reanimated REQUIRED CONFIG) endif() + target_link_libraries( ${CMAKE_PROJECT_NAME} fbjni::fbjni @@ -52,8 +56,8 @@ target_link_libraries( ReactAndroid::reactnative ) -if(IS_REANIMATED) - target_link_libraries(${CMAKE_PROJECT_NAME} react-native-reanimated::worklets) -elseif(IS_WORKLETS) +if(IS_WORKLETS) target_link_libraries(${CMAKE_PROJECT_NAME} react-native-worklets::worklets) +elseif(IS_REANIMATED) + target_link_libraries(${CMAKE_PROJECT_NAME} react-native-reanimated::worklets) endif() From 52d57beb86dcce649e4f307e4ec93fd8e660cfc8 Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 13:04:25 +0200 Subject: [PATCH 07/22] fix RNLiveMarkdown.podspec --- RNLiveMarkdown.podspec | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index a45f3fa7e..7f5cacc10 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -14,10 +14,10 @@ end is_reanimated = find_installed_package.call('react-native-reanimated') is_worklets = find_installed_package.call('react-native-worklets') -package_name = if is_reanimated - 'react-native-reanimated/package.json' - elsif is_worklets +package_name = if is_worklets 'react-native-worklets/package.json' + elsif is_reanimated + 'react-native-reanimated/package.json' else raise "Error!" end @@ -41,10 +41,10 @@ Pod::Spec.new do |s| s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}" - if is_reanimated - s.dependency "RNReanimated/worklets" - elsif is_worklets + if is_worklets s.dependency "RNWorklets" + elsif is_reanimated + s.dependency "RNReanimated/worklets" else raise "Error!" end From 8136e19c3d05253a3893c960a4f8510d37a54d0b Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 13:29:33 +0200 Subject: [PATCH 08/22] add IS_WORKLETS flag for iOS --- RNLiveMarkdown.podspec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 7f5cacc10..5613dcbe1 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -56,6 +56,9 @@ Pod::Spec.new do |s| "\"$(PODS_ROOT)/#{react_native_worklets_node_modules_dir_from_pods_root}/Common/cpp\"", ].join(' '), } + if is_worklets + s.xcconfig["OTHER_CFLAGS"] << " -DIS_WORKLETS=1" + end s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp\"" } From 0fd3c5f40014f644fa9a10180964b6d0c4e09711 Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 13:47:35 +0200 Subject: [PATCH 09/22] fix last `ShareableWorklet` usage --- apple/MarkdownParser.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apple/MarkdownParser.mm b/apple/MarkdownParser.mm index 9d585e3fa..e945b135d 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 IS_WORKLETS + std::shared_ptr markdownWorklet; +#else std::shared_ptr markdownWorklet; +#endif try { markdownWorklet = expensify::livemarkdown::getMarkdownWorklet([parserId intValue]); } catch (const std::out_of_range &error) { From 3a793588d8acfffd2c1eb43222cbb5badca8b5ed Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 14:13:38 +0200 Subject: [PATCH 10/22] rename variables on native side --- RNLiveMarkdown.podspec | 22 +++++++++------------- android/build.gradle | 17 +++++++---------- android/src/main/cpp/CMakeLists.txt | 23 +++++++++-------------- cpp/MarkdownGlobal.cpp | 6 +++--- cpp/MarkdownGlobal.h | 4 ++-- cpp/RuntimeDecorator.cpp | 3 +-- 6 files changed, 31 insertions(+), 44 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 5613dcbe1..58a6ed60b 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -6,22 +6,20 @@ react_native_minor_version = react_native_json['version'].split('.')[1].to_i pods_root = Pod::Config.instance.project_pods_root -find_installed_package = ->(name) do +is_package_installed = ->(name) do package_path = File.join(react_native_node_modules_dir, name) File.directory?(package_path) end -is_reanimated = find_installed_package.call('react-native-reanimated') -is_worklets = find_installed_package.call('react-native-worklets') +worklets_installed = is_package_installed.call('react-native-worklets') -package_name = if is_worklets +package_name = if worklets_installed 'react-native-worklets/package.json' - elsif is_reanimated - 'react-native-reanimated/package.json' else - raise "Error!" + 'react-native-reanimated/package.json' end +# TODO: is REACT_NATIVE_REANIMATED_NODE_MODULES_DIR needed, should we rename it or add new variable? 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('#{package_name}')"`) react_native_worklets_node_modules_dir_from_pods_root = Pathname.new(react_native_worklets_node_modules_dir).relative_path_from(pods_root).to_s @@ -41,12 +39,10 @@ Pod::Spec.new do |s| s.source_files = "apple/**/*.{h,m,mm}", "cpp/**/*.{h,cpp}" - if is_worklets + if worklets_installed s.dependency "RNWorklets" - elsif is_reanimated - s.dependency "RNReanimated/worklets" else - raise "Error!" + s.dependency "RNReanimated/worklets" end s.xcconfig = { @@ -56,8 +52,8 @@ Pod::Spec.new do |s| "\"$(PODS_ROOT)/#{react_native_worklets_node_modules_dir_from_pods_root}/Common/cpp\"", ].join(' '), } - if is_worklets - s.xcconfig["OTHER_CFLAGS"] << " -DIS_WORKLETS=1" + if worklets_installed + s.xcconfig["OTHER_CFLAGS"] << " -DWORKLETS_INSTALLED=1" end s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp\"" } diff --git a/android/build.gradle b/android/build.gradle index 30e9fc05f..714b9d976 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -58,11 +58,8 @@ def resolveReactNativeDirectory() { throw new GradleException("[react-native-live-markdown] Unable to resolve react-native location in node_modules. Your app should define `REACT_NATIVE_NODE_MODULES_DIR` extension property in `app/build.gradle` with a path to react-native in node_modules.") } -println resolveReactNativeDirectory().parent - -def findInstalledPackage = { name -> +def isPackageInstalled = { name -> def reactNativeLocation = resolveReactNativeDirectory().parent - println reactNativeLocation if (!reactNativeLocation) return false def packagePath = new File(reactNativeLocation, name) return packagePath.isDirectory() @@ -77,8 +74,7 @@ def getReactNativeMinorVersion() { } def REACT_NATIVE_MINOR_VERSION = getReactNativeMinorVersion() -def isReanimated = findInstalledPackage('react-native-reanimated') -def isWorklets = findInstalledPackage('react-native-worklets') +def workletsInstalled = isPackageInstalled('react-native-worklets') android { if (supportsNamespace()) { @@ -186,13 +182,14 @@ repositories { google() } + dependencies { implementation "com.facebook.react:react-android" // version substituted by RNGP implementation "com.facebook.react:hermes-android" // version substituted by RNGP - if (isWorklets) { + if (workletsInstalled) { implementation project(":react-native-worklets") - } else if (isReanimated) { + } else { implementation project(":react-native-reanimated") } } @@ -202,10 +199,10 @@ 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 { - if (isWorklets) { + if (workletsInstalled) { prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabDebugPackage") prepareKotlinBuildScriptModel.dependsOn(":react-native-worklets:prefabReleasePackage") - } else if (isReanimated) { + } 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 23da33ebb..2ad549bb1 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -6,17 +6,12 @@ set(CMAKE_VERBOSE_MAKEFILE on) set(NODE_MODULES_DIR "${REACT_NATIVE_ROOT_DIR}/..") set(REACT_NATIVE_WORKLETS_DIR "${NODE_MODULES_DIR}/react-native-worklets") -set(REACT_NATIVE_REANIMATED_DIR "${NODE_MODULES_DIR}/react-native-reanimated") if(EXISTS "${REACT_NATIVE_WORKLETS_DIR}") - set(IS_WORKLETS TRUE) + set(WORKLETS_INSTALLED TRUE) endif() -if(EXISTS "${REACT_NATIVE_REANIMATED_DIR}") - set(IS_REANIMATED TRUE) -endif() - -if(IS_WORKLETS) +if(WORKLETS_INSTALLED) include("${REACT_NATIVE_ROOT_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake") add_compile_options(${folly_FLAGS}) endif() @@ -32,19 +27,19 @@ file(GLOB CPP_SRC CONFIGURE_DEPENDS "${CPP_DIR}/*.cpp") add_library(${CMAKE_PROJECT_NAME} SHARED ${ANDROID_SRC} ${CPP_SRC}) -if(IS_WORKLETS) +if(WORKLETS_INSTALLED) target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR} "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsiexecutor") -elseif(IS_REANIMATED) +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) -if(IS_WORKLETS) +if(WORKLETS_INSTALLED) find_package(react-native-worklets REQUIRED CONFIG) - add_definitions(-DIS_WORKLETS) -elseif(IS_REANIMATED) + add_definitions(-DWORKLETS_INSTALLED) +else find_package(react-native-reanimated REQUIRED CONFIG) endif() @@ -56,8 +51,8 @@ target_link_libraries( ReactAndroid::reactnative ) -if(IS_WORKLETS) +if(WORKLETS_INSTALLED) target_link_libraries(${CMAKE_PROJECT_NAME} react-native-worklets::worklets) -elseif(IS_REANIMATED) +else target_link_libraries(${CMAKE_PROJECT_NAME} react-native-reanimated::worklets) endif() diff --git a/cpp/MarkdownGlobal.cpp b/cpp/MarkdownGlobal.cpp index f3d966e42..39357194f 100644 --- a/cpp/MarkdownGlobal.cpp +++ b/cpp/MarkdownGlobal.cpp @@ -17,7 +17,7 @@ std::shared_ptr getMarkdownRuntime() { return globalMarkdownWorkletRuntime; } -#ifdef IS_WORKLETS +#ifdef WORKLETS_INSTALLED std::unordered_map> globalMarkdownShareableWorklets; #else std::unordered_map> globalMarkdownShareableWorklets; @@ -25,7 +25,7 @@ std::unordered_map> globalMarkdownShareab std::mutex globalMarkdownShareableWorkletsMutex; int nextParserId = 1; -#ifdef IS_WORKLETS +#ifdef WORKLETS_INSTALLED const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { #else const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { @@ -42,7 +42,7 @@ void unregisterMarkdownWorklet(const int parserId) { globalMarkdownShareableWorklets.erase(parserId); } -#ifdef IS_WORKLETS +#ifdef WORKLETS_INSTALLED std::shared_ptr getMarkdownWorklet(const int parserId) { #else std::shared_ptr getMarkdownWorklet(const int parserId) { diff --git a/cpp/MarkdownGlobal.h b/cpp/MarkdownGlobal.h index 270b9d8aa..14be4b8e4 100644 --- a/cpp/MarkdownGlobal.h +++ b/cpp/MarkdownGlobal.h @@ -14,7 +14,7 @@ void setMarkdownRuntime(const std::shared_ptr &markdownWorkletRu std::shared_ptr getMarkdownRuntime(); -#ifdef IS_WORKLETS +#ifdef WORKLETS_INSTALLED const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); #else const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); @@ -22,7 +22,7 @@ const int registerMarkdownWorklet(const std::shared_ptr &markd void unregisterMarkdownWorklet(const int parserId); -#ifdef IS_WORKLETS +#ifdef WORKLETS_INSTALLED std::shared_ptr getMarkdownWorklet(const int parserId); #else std::shared_ptr getMarkdownWorklet(const int parserId); diff --git a/cpp/RuntimeDecorator.cpp b/cpp/RuntimeDecorator.cpp index 254133c95..ffca072da 100644 --- a/cpp/RuntimeDecorator.cpp +++ b/cpp/RuntimeDecorator.cpp @@ -1,6 +1,5 @@ #include "RuntimeDecorator.h" #include "MarkdownGlobal.h" -#include using namespace facebook; using namespace worklets; @@ -24,7 +23,7 @@ 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 { - #ifdef IS_WORKLETS + #ifdef WORKLETS_INSTALLED auto parserId = registerMarkdownWorklet(extractSerializableOrThrow(rt, args[0])); #else auto parserId = registerMarkdownWorklet(extractShareableOrThrow(rt, args[0])); From 49b7213e8e6f3839befe291e22f84a3e24d11006 Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 15:01:12 +0200 Subject: [PATCH 11/22] fix remaining native errors --- RNLiveMarkdown.podspec | 5 +++-- android/src/main/cpp/CMakeLists.txt | 6 +++--- apple/MarkdownParser.mm | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 58a6ed60b..55b85afdc 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -45,7 +45,7 @@ Pod::Spec.new do |s| 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_worklets_node_modules_dir_from_pods_root}/apple\"", @@ -53,8 +53,9 @@ Pod::Spec.new do |s| ].join(' '), } if worklets_installed - s.xcconfig["OTHER_CFLAGS"] << " -DWORKLETS_INSTALLED=1" + 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/src/main/cpp/CMakeLists.txt b/android/src/main/cpp/CMakeLists.txt index 2ad549bb1..77f7842ce 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -29,7 +29,7 @@ add_library(${CMAKE_PROJECT_NAME} SHARED ${ANDROID_SRC} ${CPP_SRC}) if(WORKLETS_INSTALLED) target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR} "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsiexecutor") -else +else() target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CPP_DIR}) endif() @@ -39,7 +39,7 @@ find_package(ReactAndroid REQUIRED CONFIG) if(WORKLETS_INSTALLED) find_package(react-native-worklets REQUIRED CONFIG) add_definitions(-DWORKLETS_INSTALLED) -else +else() find_package(react-native-reanimated REQUIRED CONFIG) endif() @@ -53,6 +53,6 @@ target_link_libraries( if(WORKLETS_INSTALLED) target_link_libraries(${CMAKE_PROJECT_NAME} react-native-worklets::worklets) -else +else() target_link_libraries(${CMAKE_PROJECT_NAME} react-native-reanimated::worklets) endif() diff --git a/apple/MarkdownParser.mm b/apple/MarkdownParser.mm index e945b135d..c4cfb79b0 100644 --- a/apple/MarkdownParser.mm +++ b/apple/MarkdownParser.mm @@ -19,7 +19,7 @@ @implementation MarkdownParser { const auto &markdownRuntime = expensify::livemarkdown::getMarkdownRuntime(); jsi::Runtime &rt = markdownRuntime->getJSIRuntime(); -#ifdef IS_WORKLETS +#ifdef WORKLETS_INSTALLED std::shared_ptr markdownWorklet; #else std::shared_ptr markdownWorklet; From 8c55455c8eecb7fcf25259820758384f2c6d833f Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 15:01:35 +0200 Subject: [PATCH 12/22] migrate example app to worklets & fix typescript types --- example/ios/Podfile.lock | 82 ++++++--------------------------------- example/package.json | 2 +- package-lock.json | 28 ++++++++++++- package.json | 3 +- src/MarkdownTextInput.tsx | 11 +++--- 5 files changed, 47 insertions(+), 79 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index aaf64c2bd..ed5475749 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2279,10 +2279,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/worklets + - RNWorklets - SocketRocket - Yoga - - RNReanimated (3.19.0): + - RNWorklets (0.6.0): - boost - DoubleConversion - fast_float @@ -2309,11 +2309,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated (= 3.19.0) - - RNReanimated/worklets (= 3.19.0) + - RNWorklets/worklets (= 0.6.0) - SocketRocket - Yoga - - RNReanimated/reanimated (3.19.0): + - RNWorklets/worklets (0.6.0): - boost - DoubleConversion - fast_float @@ -2340,69 +2339,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated/apple (= 3.19.0) + - RNWorklets/worklets/apple (= 0.6.0) - SocketRocket - Yoga - - RNReanimated/reanimated/apple (3.19.0): - - 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 - - SocketRocket - - Yoga - - RNReanimated/worklets (3.19.0): - - 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 - - RNReanimated/worklets/apple (= 3.19.0) - - SocketRocket - - Yoga - - RNReanimated/worklets/apple (3.19.0): + - RNWorklets/worklets/apple (0.6.0): - boost - DoubleConversion - fast_float @@ -2507,7 +2447,7 @@ DEPENDENCIES: - ReactCodegen (from `build/generated/ios`) - 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`) @@ -2659,8 +2599,8 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native/ReactCommon" RNLiveMarkdown: :path: "../.." - RNReanimated: - :path: "../../node_modules/react-native-reanimated" + RNWorklets: + :path: "../../node_modules/react-native-worklets" Yoga: :path: "../../node_modules/react-native/ReactCommon/yoga" @@ -2735,8 +2675,8 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 433ddfb4536948630aadd5bd925aff8a632d2fe3 ReactCodegen: adf5027f30e34c68b5a09f0b68acb3e5ef9e1a5d ReactCommon: 394c6b92765cf6d211c2c3f7f6bc601dffb316a6 - RNLiveMarkdown: e441098898bcfb03fe2edc4bd56a1efe987a09e6 - RNReanimated: 08fa7bb56d5f6d47c32245cf9cf53efb19befa0c + RNLiveMarkdown: 30f59122c1f27ea2ec2ad43935b1be214e10c697 + RNWorklets: 3302bb9ae6518de0d19e37ae770fec85a8780a72 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d diff --git a/example/package.json b/example/package.json index 12bd2a94b..c1c1a721f 100644 --- a/example/package.json +++ b/example/package.json @@ -12,7 +12,7 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-reanimated": "3.19.0" + "react-native-worklets": "0.6.0" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/package-lock.json b/package-lock.json index 7658051fb..4bc724349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,7 @@ "react-native-builder-bob": "^0.30.2", "react-native-reanimated": "3.19.0", "react-native-web": "^0.20.0", + "react-native-worklets": "0.6.0", "release-it": "^15.0.0", "turbo": "^1.10.7", "typescript": "^5.8.3" @@ -84,7 +85,7 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-reanimated": "3.19.0" + "react-native-worklets": "0.6.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -23278,6 +23279,7 @@ "version": "3.19.0", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.19.0.tgz", "integrity": "sha512-FNfqLuPuVHsW9KcsZtnJqIPlMQvuySnSFJXgSt9fVDPqptbSUkiAF6MthUwd4Mxt05hCRcbV+T65CENgVS5iCg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", @@ -23331,6 +23333,30 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/react-native-worklets": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.0.tgz", + "integrity": "sha512-yETMNuCcivdYWteuG4eRqgiAk2DzRCrVAaEBIEWPo4emrf3BNjadFo85L5QvyEusrX9QKE3ZEAx8U5A/nbyFgg==", + "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", diff --git a/package.json b/package.json index dac181daa..503589721 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,8 @@ "release-it": "^15.0.0", "turbo": "^1.10.7", "typescript": "^5.8.3", - "react-native-reanimated": "3.19.0" + "react-native-reanimated": "3.19.0", + "react-native-worklets": "0.6.0" }, "peerDependencies": { "expensify-common": ">=2.0.148", diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index 55151c5ff..17d7943f3 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -1,6 +1,9 @@ import {StyleSheet, TextInput, processColor} from 'react-native'; import React from 'react'; import type {TextInputProps} from 'react-native'; +import type {WorkletRuntime as WorkletRuntimeWorklets, WorkletFunction as WorkletFunctionWorklets, SerializableRef as SerializableRefWorklets} from 'react-native-worklets'; +import type {WorkletRuntime as WorkletRuntimeReanimated} from 'react-native-reanimated'; +import type {ShareableRef as ShereableRefReanimated, WorkletFunction as WorkletFunctionReanimated} from 'react-native-reanimated/lib/typescript/commonTypes'; import MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent'; import type {MarkdownStyle} from './MarkdownTextInputDecoratorViewNativeComponent'; import NativeLiveMarkdownModule from './NativeLiveMarkdownModule'; @@ -8,11 +11,9 @@ import {mergeMarkdownStyleWithDefault} from './styleUtils'; import type {PartialMarkdownStyle} from './styleUtils'; import type {InlineImagesInputProps, MarkdownRange} from './commonTypes'; -type WorkletRuntime = any; -type WorkletFunction = Args | ReturnValue | any; -type ShareableRef = { - __hostObjectShareableJSRef: T; -}; +type WorkletRuntime = WorkletRuntimeWorklets | WorkletRuntimeReanimated; +type WorkletFunction = WorkletFunctionWorklets | WorkletFunctionReanimated; +type ShareableRef = SerializableRefWorklets | ShereableRefReanimated; declare global { // eslint-disable-next-line no-var From 2e125b8ee4268424bed20989cd73bb7f8707d6fe Mon Sep 17 00:00:00 2001 From: war-in Date: Fri, 3 Oct 2025 15:12:17 +0200 Subject: [PATCH 13/22] fix WorkletFunction type --- src/parseExpensiMark.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/parseExpensiMark.ts b/src/parseExpensiMark.ts index 60d45dbaa..f5f44a566 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -4,10 +4,13 @@ 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 {WorkletFunction as WorkletFunctionReanimated} from 'react-native-reanimated/lib/typescript/commonTypes'; +import type {WorkletFunction as WorkletFunctionWorklets} from 'react-native-worklets'; import type {MarkdownType, MarkdownRange} from './commonTypes'; import {groupRanges, sortRanges, excludeRangeTypesFromFormatting, getRangesToExcludeFormatting} from './rangeUtils'; +type WorkletFunction = WorkletFunctionWorklets | WorkletFunctionReanimated; + function isWeb() { return Platform.OS === 'web'; } @@ -16,8 +19,6 @@ function isJest() { return !!global.process.env.JEST_WORKER_ID; } -type WorkletFunction = any; - // eslint-disable-next-line no-underscore-dangle if (__DEV__ && !isWeb() && !isJest() && (decode as WorkletFunction).__workletHash === undefined) { throw new Error( From 955060b266ab0794f48a04b9e64ade8331a17e55 Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 6 Oct 2025 14:13:29 +0200 Subject: [PATCH 14/22] fix part 1 of review comments --- RNLiveMarkdown.podspec | 18 +++------- android/build.gradle | 9 +---- src/MarkdownTextInput.tsx | 13 ++----- src/commonTypes.ts | 76 ++++++++++++++++++++++++++++++++++++++- src/parseExpensiMark.ts | 8 ++--- 5 files changed, 85 insertions(+), 39 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 55b85afdc..8a74f5e8b 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -6,21 +6,11 @@ react_native_minor_version = react_native_json['version'].split('.')[1].to_i pods_root = Pod::Config.instance.project_pods_root -is_package_installed = ->(name) do - package_path = File.join(react_native_node_modules_dir, name) - File.directory?(package_path) -end - -worklets_installed = is_package_installed.call('react-native-worklets') - -package_name = if worklets_installed - 'react-native-worklets/package.json' - else - 'react-native-reanimated/package.json' - end +worklets_installed = system(`cd "#{Pod::Config.instance.installation_root.to_s}" && node -e "require.resolve(\'react-native-worklets/package.json\')" 2>/dev/null`) +package_name = worklets_installed ? 'react-native-worklets/package.json' : 'react-native-reanimated/package.json' -# TODO: is REACT_NATIVE_REANIMATED_NODE_MODULES_DIR needed, should we rename it or add new variable? -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('#{package_name}')"`) +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('#{package_name}')"`) react_native_worklets_node_modules_dir_from_pods_root = Pathname.new(react_native_worklets_node_modules_dir).relative_path_from(pods_root).to_s package = JSON.parse(File.read(File.join(__dir__, "package.json"))) diff --git a/android/build.gradle b/android/build.gradle index 714b9d976..fbe20a8fa 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -58,13 +58,6 @@ def resolveReactNativeDirectory() { throw new GradleException("[react-native-live-markdown] Unable to resolve react-native location in node_modules. Your app should define `REACT_NATIVE_NODE_MODULES_DIR` extension property in `app/build.gradle` with a path to react-native in node_modules.") } -def isPackageInstalled = { name -> - def reactNativeLocation = resolveReactNativeDirectory().parent - if (!reactNativeLocation) return false - def packagePath = new File(reactNativeLocation, name) - return packagePath.isDirectory() -} - def getReactNativeMinorVersion() { def reactNativeRootDir = resolveReactNativeDirectory() def reactNativeProperties = new Properties() @@ -74,7 +67,7 @@ def getReactNativeMinorVersion() { } def REACT_NATIVE_MINOR_VERSION = getReactNativeMinorVersion() -def workletsInstalled = isPackageInstalled('react-native-worklets') +def workletsInstalled = rootProject.findProject(':react-native-worklets') != null android { if (supportsNamespace()) { diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index 17d7943f3..7d7b96dc3 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -1,19 +1,12 @@ import {StyleSheet, TextInput, processColor} from 'react-native'; import React from 'react'; import type {TextInputProps} from 'react-native'; -import type {WorkletRuntime as WorkletRuntimeWorklets, WorkletFunction as WorkletFunctionWorklets, SerializableRef as SerializableRefWorklets} from 'react-native-worklets'; -import type {WorkletRuntime as WorkletRuntimeReanimated} from 'react-native-reanimated'; -import type {ShareableRef as ShereableRefReanimated, WorkletFunction as WorkletFunctionReanimated} 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'; - -type WorkletRuntime = WorkletRuntimeWorklets | WorkletRuntimeReanimated; -type WorkletFunction = WorkletFunctionWorklets | WorkletFunctionReanimated; -type ShareableRef = SerializableRefWorklets | ShereableRefReanimated; +import type {InlineImagesInputProps, MarkdownRange, ShareableRef, WorkletFunction, WorkletRuntime} from './commonTypes'; declare global { // eslint-disable-next-line no-var @@ -49,11 +42,11 @@ function initializeLiveMarkdownIfNeeded() { let createWorkletRuntime; try { - // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + // eslint-disable-next-line @typescript-eslint/no-var-requires createWorkletRuntime = require('react-native-worklets').createWorkletRuntime; } catch { try { - // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + // eslint-disable-next-line @typescript-eslint/no-var-requires createWorkletRuntime = require('react-native-reanimated').createWorkletRuntime; } catch { /* empty */ 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 f5f44a566..85185533b 100644 --- a/src/parseExpensiMark.ts +++ b/src/parseExpensiMark.ts @@ -4,13 +4,9 @@ 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 as WorkletFunctionReanimated} from 'react-native-reanimated/lib/typescript/commonTypes'; -import type {WorkletFunction as WorkletFunctionWorklets} from 'react-native-worklets'; -import type {MarkdownType, MarkdownRange} from './commonTypes'; +import type {MarkdownType, MarkdownRange, WorkletFunction} from './commonTypes'; import {groupRanges, sortRanges, excludeRangeTypesFromFormatting, getRangesToExcludeFormatting} from './rangeUtils'; -type WorkletFunction = WorkletFunctionWorklets | WorkletFunctionReanimated; - function isWeb() { return Platform.OS === 'web'; } @@ -20,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.", ); From d5134ada78b3e5df68b78548d0e9bbc4689fae36 Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 6 Oct 2025 14:24:58 +0200 Subject: [PATCH 15/22] remove unnecessary worklets check from CMakeLists.txt --- android/build.gradle | 3 ++- android/src/main/cpp/CMakeLists.txt | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index fbe20a8fa..f78d677f5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -96,7 +96,8 @@ android { "-DANDROID_TOOLCHAIN=clang", "-DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", - "-DREACT_NATIVE_ROOT_DIR=${resolveReactNativeDirectory().path}" + "-DREACT_NATIVE_ROOT_DIR=${resolveReactNativeDirectory().path}", + "-DWORKLETS_INSTALLED=${workletsInstalled}" abiFilters (*reactNativeArchitectures()) } } diff --git a/android/src/main/cpp/CMakeLists.txt b/android/src/main/cpp/CMakeLists.txt index 77f7842ce..19d9db854 100644 --- a/android/src/main/cpp/CMakeLists.txt +++ b/android/src/main/cpp/CMakeLists.txt @@ -4,13 +4,6 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -set(NODE_MODULES_DIR "${REACT_NATIVE_ROOT_DIR}/..") -set(REACT_NATIVE_WORKLETS_DIR "${NODE_MODULES_DIR}/react-native-worklets") - -if(EXISTS "${REACT_NATIVE_WORKLETS_DIR}") - set(WORKLETS_INSTALLED TRUE) -endif() - if(WORKLETS_INSTALLED) include("${REACT_NATIVE_ROOT_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake") add_compile_options(${folly_FLAGS}) From 1e3ab8b805a61857061b78b368332b1a46e57430 Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 6 Oct 2025 15:21:35 +0200 Subject: [PATCH 16/22] remove unnecessary try/catch --- src/MarkdownTextInput.tsx | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index 7d7b96dc3..d9e215c9b 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -40,23 +40,15 @@ function initializeLiveMarkdownIfNeeded() { throw new Error('[react-native-live-markdown] global.jsi_setMarkdownRuntime is not available'); } - let createWorkletRuntime; + let createWorkletRuntime: (name: string) => WorkletRuntime; try { // eslint-disable-next-line @typescript-eslint/no-var-requires createWorkletRuntime = require('react-native-worklets').createWorkletRuntime; } catch { - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - createWorkletRuntime = require('react-native-reanimated').createWorkletRuntime; - } catch { - /* empty */ - } + // eslint-disable-next-line @typescript-eslint/no-var-requires + createWorkletRuntime = require('react-native-reanimated').createWorkletRuntime; } - workletRuntime = createWorkletRuntime('LiveMarkdownRuntime'); - if (!workletRuntime) { - return; - } global.jsi_setMarkdownRuntime(workletRuntime); initialized = true; @@ -70,12 +62,8 @@ function registerParser(parser: (input: string) => MarkdownRange[]): number { // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved makeShareableCloneRecursive = require('react-native-worklets').makeShareableCloneRecursive; } catch { - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved - makeShareableCloneRecursive = require('react-native-reanimated').makeShareableCloneRecursive; - } catch { - /* empty */ - } + // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + makeShareableCloneRecursive = require('react-native-reanimated').makeShareableCloneRecursive; } const shareableWorklet = makeShareableCloneRecursive(parser) as ShareableRef>; From 423312bfe7ccb0a1a1b798c4d10255ce67d8afe5 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 7 Oct 2025 10:40:31 +0200 Subject: [PATCH 17/22] fix iOS build issue --- RNLiveMarkdown.podspec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 8a74f5e8b..becd7b04f 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -6,7 +6,10 @@ react_native_minor_version = react_native_json['version'].split('.')[1].to_i pods_root = Pod::Config.instance.project_pods_root -worklets_installed = system(`cd "#{Pod::Config.instance.installation_root.to_s}" && node -e "require.resolve(\'react-native-worklets/package.json\')" 2>/dev/null`) +worklets_installed = system(%Q[ + cd "#{Pod::Config.instance.installation_root}" && + node -e "require.resolve('react-native-worklets/package.json')" > /dev/null 2>&1 +]) package_name = worklets_installed ? 'react-native-worklets/package.json' : 'react-native-reanimated/package.json' react_native_worklets_node_modules_dir = ENV['REACT_NATIVE_REANIMATED_NODE_MODULES_DIR'] || From a8a5765f98ec1c2e0422b215eebb23d8cc8b09f0 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 7 Oct 2025 10:40:43 +0200 Subject: [PATCH 18/22] fix metro errors on reanimated v3 --- src/MarkdownTextInput.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index d9e215c9b..584d94a74 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -29,7 +29,7 @@ function getWorkletRuntime(): WorkletRuntime { return workletRuntime; } -function initializeLiveMarkdownIfNeeded() { +function initializeLiveMarkdownIfNeeded(workletsPackageName = 'react-native-worklets') { if (initialized) { return; } @@ -43,7 +43,7 @@ function initializeLiveMarkdownIfNeeded() { let createWorkletRuntime: (name: string) => WorkletRuntime; try { // eslint-disable-next-line @typescript-eslint/no-var-requires - createWorkletRuntime = require('react-native-worklets').createWorkletRuntime; + createWorkletRuntime = require(workletsPackageName).createWorkletRuntime; } catch { // eslint-disable-next-line @typescript-eslint/no-var-requires createWorkletRuntime = require('react-native-reanimated').createWorkletRuntime; @@ -54,15 +54,15 @@ function initializeLiveMarkdownIfNeeded() { initialized = true; } -function registerParser(parser: (input: string) => MarkdownRange[]): number { +function registerParser(parser: (input: string) => MarkdownRange[], workletsPackageName = 'react-native-worklets'): number { initializeLiveMarkdownIfNeeded(); let makeShareableCloneRecursive; try { - // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved - makeShareableCloneRecursive = require('react-native-worklets').makeShareableCloneRecursive; + // eslint-disable-next-line @typescript-eslint/no-var-requires + makeShareableCloneRecursive = require(workletsPackageName).makeShareableCloneRecursive; } catch { - // eslint-disable-next-line @typescript-eslint/no-var-requires,import/no-unresolved + // eslint-disable-next-line @typescript-eslint/no-var-requires makeShareableCloneRecursive = require('react-native-reanimated').makeShareableCloneRecursive; } From ad71c95e7554b6e461ef6bf64c86fe17c2cb9ad9 Mon Sep 17 00:00:00 2001 From: war-in Date: Wed, 8 Oct 2025 11:12:45 +0200 Subject: [PATCH 19/22] chore(deps): bump `react-native-reanimated` --- package-lock.json | 34 +++++++++++++--------------------- package.json | 4 ++-- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4bc724349..0eb134258 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "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.2", "react-native-web": "^0.20.0", "react-native-worklets": "0.6.0", "release-it": "^15.0.0", @@ -67,7 +67,7 @@ "react": "*", "react-native": "*", "react-native-reanimated": ">=3.17.0", - "react-native-worklets": "^0.6.0" + "react-native-worklets": ">=0.6.0" }, "peerDependenciesMeta": { "react-native-reanimated": { @@ -23267,38 +23267,30 @@ } }, "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.2", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.2.tgz", + "integrity": "sha512-qzmQiFrvjm62pRBcj97QI9Xckc3EjgHQoY1F2yjktd0kpjhoyePeuTEXjYRCAVIy7IV/1cfeSup34+zFThFoHQ==", "dev": true, "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": { diff --git a/package.json b/package.json index 503589721..e8c7a1cc3 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "release-it": "^15.0.0", "turbo": "^1.10.7", "typescript": "^5.8.3", - "react-native-reanimated": "3.19.0", + "react-native-reanimated": "4.1.2", "react-native-worklets": "0.6.0" }, "peerDependencies": { @@ -110,7 +110,7 @@ "react": "*", "react-native": "*", "react-native-reanimated": ">=3.17.0", - "react-native-worklets": "^0.6.0" + "react-native-worklets": ">=0.6.0" }, "peerDependenciesMeta": { "react-native-worklets": { From 15c34d53c65fc3ccfb1518ba553b4c37702d669a Mon Sep 17 00:00:00 2001 From: war-in Date: Wed, 8 Oct 2025 16:09:16 +0200 Subject: [PATCH 20/22] fix part of review comments --- RNLiveMarkdown.podspec | 14 ++++++++------ cpp/MarkdownGlobal.cpp | 12 ------------ cpp/MarkdownGlobal.h | 12 ++++-------- cpp/RuntimeDecorator.cpp | 5 +++-- src/MarkdownTextInput.tsx | 8 ++++---- 5 files changed, 19 insertions(+), 32 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index becd7b04f..748f4f238 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -10,11 +10,13 @@ worklets_installed = system(%Q[ cd "#{Pod::Config.instance.installation_root}" && node -e "require.resolve('react-native-worklets/package.json')" > /dev/null 2>&1 ]) -package_name = worklets_installed ? 'react-native-worklets/package.json' : 'react-native-reanimated/package.json' +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_node_modules_dir = ENV['REACT_NATIVE_REANIMATED_NODE_MODULES_DIR'] || - File.dirname(`cd "#{Pod::Config.instance.installation_root.to_s}" && node --print "require.resolve('#{package_name}')"`) -react_native_worklets_node_modules_dir_from_pods_root = Pathname.new(react_native_worklets_node_modules_dir).relative_path_from(pods_root).to_s +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' @@ -41,8 +43,8 @@ Pod::Spec.new do |s| xcconfig = { "OTHER_CFLAGS" => "$(inherited) -DREACT_NATIVE_MINOR_VERSION=#{react_native_minor_version}", "HEADER_SEARCH_PATHS" => [ - "\"$(PODS_ROOT)/#{react_native_worklets_node_modules_dir_from_pods_root}/apple\"", - "\"$(PODS_ROOT)/#{react_native_worklets_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 diff --git a/cpp/MarkdownGlobal.cpp b/cpp/MarkdownGlobal.cpp index 39357194f..67f93eb43 100644 --- a/cpp/MarkdownGlobal.cpp +++ b/cpp/MarkdownGlobal.cpp @@ -17,19 +17,11 @@ std::shared_ptr getMarkdownRuntime() { return globalMarkdownWorkletRuntime; } -#ifdef WORKLETS_INSTALLED std::unordered_map> globalMarkdownShareableWorklets; -#else -std::unordered_map> globalMarkdownShareableWorklets; -#endif std::mutex globalMarkdownShareableWorkletsMutex; int nextParserId = 1; -#ifdef WORKLETS_INSTALLED const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { -#else -const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet) { -#endif assert(markdownWorklet != nullptr); auto parserId = nextParserId++; std::unique_lock lock(globalMarkdownShareableWorkletsMutex); @@ -42,11 +34,7 @@ void unregisterMarkdownWorklet(const int parserId) { globalMarkdownShareableWorklets.erase(parserId); } -#ifdef WORKLETS_INSTALLED std::shared_ptr getMarkdownWorklet(const int parserId) { -#else -std::shared_ptr getMarkdownWorklet(const int parserId) { -#endif std::unique_lock lock(globalMarkdownShareableWorkletsMutex); return globalMarkdownShareableWorklets.at(parserId); } diff --git a/cpp/MarkdownGlobal.h b/cpp/MarkdownGlobal.h index 14be4b8e4..677b39110 100644 --- a/cpp/MarkdownGlobal.h +++ b/cpp/MarkdownGlobal.h @@ -10,23 +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(); -#ifdef WORKLETS_INSTALLED const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); -#else -const int registerMarkdownWorklet(const std::shared_ptr &markdownWorklet); -#endif void unregisterMarkdownWorklet(const int parserId); -#ifdef WORKLETS_INSTALLED std::shared_ptr getMarkdownWorklet(const int parserId); -#else -std::shared_ptr getMarkdownWorklet(const int parserId); -#endif } // namespace livemarkdown } // namespace expensify diff --git a/cpp/RuntimeDecorator.cpp b/cpp/RuntimeDecorator.cpp index ffca072da..66f70b0c2 100644 --- a/cpp/RuntimeDecorator.cpp +++ b/cpp/RuntimeDecorator.cpp @@ -24,10 +24,11 @@ void injectJSIBindings(jsi::Runtime &rt) { 1, [](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value { #ifdef WORKLETS_INSTALLED - auto parserId = registerMarkdownWorklet(extractSerializableOrThrow(rt, args[0])); + auto worklet = extractSerializableOrThrow(rt, args[0]); #else - auto parserId = registerMarkdownWorklet(extractShareableOrThrow(rt, args[0])); + auto worklet = extractShareableOrThrow(rt, args[0]); #endif + auto parserId = registerMarkdownWorklet(worklet); return jsi::Value(parserId); })); diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index 584d94a74..af1634be7 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -29,7 +29,7 @@ function getWorkletRuntime(): WorkletRuntime { return workletRuntime; } -function initializeLiveMarkdownIfNeeded(workletsPackageName = 'react-native-worklets') { +function initializeLiveMarkdownIfNeeded() { if (initialized) { return; } @@ -43,7 +43,7 @@ function initializeLiveMarkdownIfNeeded(workletsPackageName = 'react-native-work let createWorkletRuntime: (name: string) => WorkletRuntime; try { // eslint-disable-next-line @typescript-eslint/no-var-requires - createWorkletRuntime = require(workletsPackageName).createWorkletRuntime; + createWorkletRuntime = require('react-native-worklets').createWorkletRuntime; } catch { // eslint-disable-next-line @typescript-eslint/no-var-requires createWorkletRuntime = require('react-native-reanimated').createWorkletRuntime; @@ -54,13 +54,13 @@ function initializeLiveMarkdownIfNeeded(workletsPackageName = 'react-native-work initialized = true; } -function registerParser(parser: (input: string) => MarkdownRange[], workletsPackageName = 'react-native-worklets'): number { +function registerParser(parser: (input: string) => MarkdownRange[]): number { initializeLiveMarkdownIfNeeded(); let makeShareableCloneRecursive; try { // eslint-disable-next-line @typescript-eslint/no-var-requires - makeShareableCloneRecursive = require(workletsPackageName).makeShareableCloneRecursive; + makeShareableCloneRecursive = require('react-native-worklets').makeShareableCloneRecursive; } catch { // eslint-disable-next-line @typescript-eslint/no-var-requires makeShareableCloneRecursive = require('react-native-reanimated').makeShareableCloneRecursive; From a1b25f632138d86068cc546450982d57e8227ce5 Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 13 Oct 2025 11:52:02 +0200 Subject: [PATCH 21/22] fix: make reanimated required again & fix webpack issues --- example/ios/Podfile.lock | 108 +++++++++++++++++++++++++++++++++++--- example/package.json | 3 +- package-lock.json | 23 ++++---- package.json | 7 +-- src/MarkdownTextInput.tsx | 23 +------- 5 files changed, 118 insertions(+), 46 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index ed5475749..7d98ed6b5 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2282,7 +2282,7 @@ PODS: - RNWorklets - SocketRocket - Yoga - - RNWorklets (0.6.0): + - RNReanimated (4.1.3): - boost - DoubleConversion - fast_float @@ -2309,10 +2309,102 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets (= 0.6.0) + - RNReanimated/reanimated (= 4.1.3) + - RNWorklets + - SocketRocket + - Yoga + - RNReanimated/reanimated (4.1.3): + - 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 + - RNReanimated/reanimated/apple (= 4.1.3) + - RNWorklets + - SocketRocket + - Yoga + - RNReanimated/reanimated/apple (4.1.3): + - 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 + - SocketRocket + - Yoga + - RNWorklets (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 (= 0.6.1) - SocketRocket - Yoga - - RNWorklets/worklets (0.6.0): + - RNWorklets/worklets (0.6.1): - boost - DoubleConversion - fast_float @@ -2339,10 +2431,10 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNWorklets/worklets/apple (= 0.6.0) + - RNWorklets/worklets/apple (= 0.6.1) - SocketRocket - Yoga - - RNWorklets/worklets/apple (0.6.0): + - RNWorklets/worklets/apple (0.6.1): - boost - DoubleConversion - fast_float @@ -2447,6 +2539,7 @@ DEPENDENCIES: - ReactCodegen (from `build/generated/ios`) - 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`) @@ -2599,6 +2692,8 @@ EXTERNAL SOURCES: :path: "../../node_modules/react-native/ReactCommon" RNLiveMarkdown: :path: "../.." + RNReanimated: + :path: "../../node_modules/react-native-reanimated" RNWorklets: :path: "../../node_modules/react-native-worklets" Yoga: @@ -2676,7 +2771,8 @@ SPEC CHECKSUMS: ReactCodegen: adf5027f30e34c68b5a09f0b68acb3e5ef9e1a5d ReactCommon: 394c6b92765cf6d211c2c3f7f6bc601dffb316a6 RNLiveMarkdown: 30f59122c1f27ea2ec2ad43935b1be214e10c697 - RNWorklets: 3302bb9ae6518de0d19e37ae770fec85a8780a72 + RNReanimated: e04fc279d4e0d936a27f50c9a170cfa269c3434d + RNWorklets: 879b69a98caa893353b964b6fece154a819145af SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d diff --git a/example/package.json b/example/package.json index c1c1a721f..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-worklets": "0.6.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 0eb134258..5e9e6e5d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,9 +52,9 @@ "react-dom": "19.1.0", "react-native": "0.81.4", "react-native-builder-bob": "^0.30.2", - "react-native-reanimated": "4.1.2", + "react-native-reanimated": "4.1.3", "react-native-web": "^0.20.0", - "react-native-worklets": "0.6.0", + "react-native-worklets": "^0.6.1", "release-it": "^15.0.0", "turbo": "^1.10.7", "typescript": "^5.8.3" @@ -70,9 +70,6 @@ "react-native-worklets": ">=0.6.0" }, "peerDependenciesMeta": { - "react-native-reanimated": { - "optional": true - }, "react-native-worklets": { "optional": true } @@ -85,7 +82,8 @@ "expensify-common": "2.0.148", "react": "19.1.0", "react-native": "0.81.4", - "react-native-worklets": "0.6.0" + "react-native-reanimated": "4.1.3", + "react-native-worklets": "0.6.1" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -23277,10 +23275,9 @@ } }, "node_modules/react-native-reanimated": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.2.tgz", - "integrity": "sha512-qzmQiFrvjm62pRBcj97QI9Xckc3EjgHQoY1F2yjktd0kpjhoyePeuTEXjYRCAVIy7IV/1cfeSup34+zFThFoHQ==", - "dev": true, + "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": { "react-native-is-edge-to-edge": "^1.2.1", @@ -23326,9 +23323,9 @@ "license": "MIT" }, "node_modules/react-native-worklets": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.0.tgz", - "integrity": "sha512-yETMNuCcivdYWteuG4eRqgiAk2DzRCrVAaEBIEWPo4emrf3BNjadFo85L5QvyEusrX9QKE3ZEAx8U5A/nbyFgg==", + "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", diff --git a/package.json b/package.json index e8c7a1cc3..c8a4549e2 100644 --- a/package.json +++ b/package.json @@ -102,8 +102,8 @@ "release-it": "^15.0.0", "turbo": "^1.10.7", "typescript": "^5.8.3", - "react-native-reanimated": "4.1.2", - "react-native-worklets": "0.6.0" + "react-native-reanimated": "4.1.3", + "react-native-worklets": "^0.6.1" }, "peerDependencies": { "expensify-common": ">=2.0.148", @@ -115,9 +115,6 @@ "peerDependenciesMeta": { "react-native-worklets": { "optional": true - }, - "react-native-reanimated": { - "optional": true } }, "overrides": { diff --git a/src/MarkdownTextInput.tsx b/src/MarkdownTextInput.tsx index af1634be7..84f9684ca 100644 --- a/src/MarkdownTextInput.tsx +++ b/src/MarkdownTextInput.tsx @@ -1,6 +1,7 @@ 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 MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent'; import type {MarkdownStyle} from './MarkdownTextInputDecoratorViewNativeComponent'; import NativeLiveMarkdownModule from './NativeLiveMarkdownModule'; @@ -39,34 +40,14 @@ function initializeLiveMarkdownIfNeeded() { if (!global.jsi_setMarkdownRuntime) { throw new Error('[react-native-live-markdown] global.jsi_setMarkdownRuntime is not available'); } - - let createWorkletRuntime: (name: string) => WorkletRuntime; - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - createWorkletRuntime = require('react-native-worklets').createWorkletRuntime; - } catch { - // eslint-disable-next-line @typescript-eslint/no-var-requires - createWorkletRuntime = require('react-native-reanimated').createWorkletRuntime; - } workletRuntime = createWorkletRuntime('LiveMarkdownRuntime'); - global.jsi_setMarkdownRuntime(workletRuntime); initialized = true; } function registerParser(parser: (input: string) => MarkdownRange[]): number { initializeLiveMarkdownIfNeeded(); - - let makeShareableCloneRecursive; - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - makeShareableCloneRecursive = require('react-native-worklets').makeShareableCloneRecursive; - } catch { - // eslint-disable-next-line @typescript-eslint/no-var-requires - makeShareableCloneRecursive = require('react-native-reanimated').makeShareableCloneRecursive; - } - - const shareableWorklet = makeShareableCloneRecursive(parser) as ShareableRef>; + const shareableWorklet = makeShareableCloneRecursive(parser) as unknown as ShareableRef>; const parserId = global.jsi_registerMarkdownWorklet(shareableWorklet); return parserId; } From 7f50feea776282b4e4c0a6e4317425e083dd72d4 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 14 Oct 2025 15:41:42 +0200 Subject: [PATCH 22/22] fix: lint --- cpp/RuntimeDecorator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/RuntimeDecorator.cpp b/cpp/RuntimeDecorator.cpp index 66f70b0c2..74746277b 100644 --- a/cpp/RuntimeDecorator.cpp +++ b/cpp/RuntimeDecorator.cpp @@ -23,11 +23,11 @@ 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 { - #ifdef WORKLETS_INSTALLED +#ifdef WORKLETS_INSTALLED auto worklet = extractSerializableOrThrow(rt, args[0]); - #else +#else auto worklet = extractShareableOrThrow(rt, args[0]); - #endif +#endif auto parserId = registerMarkdownWorklet(worklet); return jsi::Value(parserId); }));