From 5852938f13a65380ff95ae62deb04b261c173fd3 Mon Sep 17 00:00:00 2001 From: Tomek Zawadzki Date: Fri, 28 Jun 2024 21:21:09 +0200 Subject: [PATCH] Use Hermes instead of JSC on iOS (#404) --- RNLiveMarkdown.podspec | 2 + example/ios/Podfile.lock | 110 +++------------------------------------ ios/RCTMarkdownUtils.mm | 77 +++++++++++++++------------ 3 files changed, 52 insertions(+), 137 deletions(-) diff --git a/RNLiveMarkdown.podspec b/RNLiveMarkdown.podspec index 781f3d49c..b1620ad13 100644 --- a/RNLiveMarkdown.podspec +++ b/RNLiveMarkdown.podspec @@ -18,6 +18,8 @@ Pod::Spec.new do |s| s.resources = "parser/react-native-live-markdown-parser.js" + s.dependency "hermes-engine" + install_modules_dependencies(s) if ENV['USE_FRAMEWORKS'] && ENV['RCT_NEW_ARCH_ENABLED'] diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index feface4ed..207205639 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,6 +1,5 @@ PODS: - boost (1.83.0) - - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - FBLazyVector (0.73.4) - FBReactNativeSpec (0.73.4): @@ -10,69 +9,12 @@ PODS: - React-Core (= 0.73.4) - React-jsi (= 0.73.4) - ReactCommon/turbomodule/core (= 0.73.4) - - Flipper (0.201.0): - - Flipper-Folly (~> 2.6) - - Flipper-Boost-iOSX (1.76.0.1.11) - - Flipper-DoubleConversion (3.2.0.1) - - Flipper-Fmt (7.1.7) - - Flipper-Folly (2.6.10): - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt (= 7.1.7) - - Flipper-Glog - - libevent (~> 2.1.12) - - OpenSSL-Universal (= 1.1.1100) - - Flipper-Glog (0.5.0.5) - - Flipper-PeerTalk (0.0.4) - - FlipperKit (0.201.0): - - FlipperKit/Core (= 0.201.0) - - FlipperKit/Core (0.201.0): - - Flipper (~> 0.201.0) - - FlipperKit/CppBridge - - FlipperKit/FBCxxFollyDynamicConvert - - FlipperKit/FBDefines - - FlipperKit/FKPortForwarding - - SocketRocket (~> 0.6.0) - - FlipperKit/CppBridge (0.201.0): - - Flipper (~> 0.201.0) - - FlipperKit/FBCxxFollyDynamicConvert (0.201.0): - - Flipper-Folly (~> 2.6) - - FlipperKit/FBDefines (0.201.0) - - FlipperKit/FKPortForwarding (0.201.0): - - CocoaAsyncSocket (~> 7.6) - - Flipper-PeerTalk (~> 0.0.4) - - FlipperKit/FlipperKitHighlightOverlay (0.201.0) - - FlipperKit/FlipperKitLayoutHelpers (0.201.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutTextSearchable - - FlipperKit/FlipperKitLayoutIOSDescriptors (0.201.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - FlipperKit/FlipperKitLayoutPlugin (0.201.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - FlipperKit/FlipperKitLayoutIOSDescriptors - - FlipperKit/FlipperKitLayoutTextSearchable - - FlipperKit/FlipperKitLayoutTextSearchable (0.201.0) - - FlipperKit/FlipperKitNetworkPlugin (0.201.0): - - FlipperKit/Core - - FlipperKit/FlipperKitReactPlugin (0.201.0): - - FlipperKit/Core - - FlipperKit/FlipperKitUserDefaultsPlugin (0.201.0): - - FlipperKit/Core - - FlipperKit/SKIOSNetworkPlugin (0.201.0): - - FlipperKit/Core - - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - hermes-engine (0.73.4): - hermes-engine/Pre-built (= 0.73.4) - hermes-engine/Pre-built (0.73.4) - libevent (2.1.12) - - OpenSSL-Universal (1.1.1100) - RCT-Folly (2022.05.16.00): - boost - DoubleConversion @@ -1111,13 +1053,15 @@ PODS: - React-jsi (= 0.73.4) - React-logger (= 0.73.4) - React-perflogger (= 0.73.4) - - RNLiveMarkdown (0.1.79): + - RNLiveMarkdown (0.1.92): - glog + - hermes-engine - RCT-Folly (= 2022.05.16.00) - React-Core - - RNLiveMarkdown/common (= 0.1.79) - - RNLiveMarkdown/common (0.1.79): + - RNLiveMarkdown/common (= 0.1.92) + - RNLiveMarkdown/common (0.1.92): - glog + - hermes-engine - RCT-Folly (= 2022.05.16.00) - React-Core - SocketRocket (0.6.1) @@ -1128,30 +1072,9 @@ DEPENDENCIES: - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - - Flipper (= 0.201.0) - - Flipper-Boost-iOSX (= 1.76.0.1.11) - - Flipper-DoubleConversion (= 3.2.0.1) - - Flipper-Fmt (= 7.1.7) - - Flipper-Folly (= 2.6.10) - - Flipper-Glog (= 0.5.0.5) - - Flipper-PeerTalk (= 0.0.4) - - FlipperKit (= 0.201.0) - - FlipperKit/Core (= 0.201.0) - - FlipperKit/CppBridge (= 0.201.0) - - FlipperKit/FBCxxFollyDynamicConvert (= 0.201.0) - - FlipperKit/FBDefines (= 0.201.0) - - FlipperKit/FKPortForwarding (= 0.201.0) - - FlipperKit/FlipperKitHighlightOverlay (= 0.201.0) - - FlipperKit/FlipperKitLayoutPlugin (= 0.201.0) - - FlipperKit/FlipperKitLayoutTextSearchable (= 0.201.0) - - FlipperKit/FlipperKitNetworkPlugin (= 0.201.0) - - FlipperKit/FlipperKitReactPlugin (= 0.201.0) - - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.201.0) - - FlipperKit/SKIOSNetworkPlugin (= 0.201.0) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - libevent (~> 2.1.12) - - OpenSSL-Universal (= 1.1.1100) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) @@ -1160,7 +1083,6 @@ DEPENDENCIES: - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) - React-Codegen (from `build/generated/ios`) - React-Core (from `../node_modules/react-native/`) - - React-Core/DevSupport (from `../node_modules/react-native/`) - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) @@ -1201,18 +1123,8 @@ DEPENDENCIES: SPEC REPOS: trunk: - - CocoaAsyncSocket - - Flipper - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt - - Flipper-Folly - - Flipper-Glog - - Flipper-PeerTalk - - FlipperKit - fmt - libevent - - OpenSSL-Universal - SocketRocket EXTERNAL SOURCES: @@ -1318,23 +1230,13 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 - CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 FBLazyVector: 84f6edbe225f38aebd9deaf1540a4160b1f087d7 FBReactNativeSpec: d0086a479be91c44ce4687a962956a352d2dc697 - Flipper: c7a0093234c4bdd456e363f2f19b2e4b27652d44 - Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c - Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 - Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b - Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3 - Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446 - Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 - FlipperKit: 37525a5d056ef9b93d1578e04bc3ea1de940094f fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: b2669ce35fc4ac14f523b307aff8896799829fe2 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0 RCTRequired: ab7f915c15569f04a49669e573e6e319a53f9faa RCTTypeSafety: 63b97ced7b766865057e7154db0e81ce4ee6cf1e @@ -1376,7 +1278,7 @@ SPEC CHECKSUMS: React-runtimescheduler: ed48e5faac6751e66ee1261c4bd01643b436f112 React-utils: 6e5ad394416482ae21831050928ae27348f83487 ReactCommon: 840a955d37b7f3358554d819446bffcf624b2522 - RNLiveMarkdown: a4ddf419a109cd3f916db22ee19f8b8293b4f7e4 + RNLiveMarkdown: c04da6410e95af38ccb615b8de8bb4eefc9f6e2c SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 diff --git a/ios/RCTMarkdownUtils.mm b/ios/RCTMarkdownUtils.mm index f188429a2..2ce276de9 100644 --- a/ios/RCTMarkdownUtils.mm +++ b/ios/RCTMarkdownUtils.mm @@ -2,7 +2,11 @@ #import "react_native_assert.h" #import #import -#import + +#include +#include + +using namespace facebook; @implementation RCTMarkdownUtils { NSString *_prevInputString; @@ -23,20 +27,26 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA return _prevAttributedString; } - static JSContext *ctx = nil; - static JSValue *function = nil; - if (ctx == nil) { + static std::shared_ptr runtime; + if (runtime == nullptr) { NSString *path = [[NSBundle mainBundle] pathForResource:@"react-native-live-markdown-parser" ofType:@"js"]; assert(path != nil && "[react-native-live-markdown] Markdown parser bundle not found"); NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; assert(content != nil && "[react-native-live-markdown] Markdown parser bundle is empty"); - ctx = [[JSContext alloc] init]; - [ctx evaluateScript:content]; - function = ctx[@"parseExpensiMarkToRanges"]; + runtime = facebook::hermes::makeHermesRuntime(); + auto codeBuffer = std::make_shared([content UTF8String]); + runtime->evaluateJavaScript(codeBuffer, "evaluateJavaScript"); } - JSValue *result = [function callWithArguments:@[inputString]]; - NSArray *ranges = [result toArray]; + jsi::Runtime &rt = *runtime; + auto text = jsi::String::createFromUtf8(rt, [inputString UTF8String]); + + auto func = rt.global().getPropertyAsFunction(rt, "parseExpensiMarkToRanges"); + auto output = func.call(rt, text); + if (output.isUndefined()) { + return input; + } + const auto &ranges = output.asObject(rt).asArray(rt); NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:attributes]; [attributedString beginEditing]; @@ -48,42 +58,43 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA _blockquoteRangesAndLevels = [NSMutableArray new]; - [ranges enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - NSDictionary *item = obj; - NSString *type = [item valueForKey:@"type"]; - NSInteger location = [[item valueForKey:@"start"] unsignedIntegerValue]; - NSInteger length = [[item valueForKey:@"length"] unsignedIntegerValue]; - NSInteger depth = [[item valueForKey:@"depth"] unsignedIntegerValue] ?: 1; + for (size_t i = 0, n = ranges.size(rt); i < n; ++i) { + const auto &item = ranges.getValueAtIndex(rt, i).asObject(rt); + const auto &type = item.getProperty(rt, "type").asString(rt).utf8(rt); + const auto &location = static_cast(item.getProperty(rt, "start").asNumber()); + const auto &length = static_cast(item.getProperty(rt, "length").asNumber()); + const auto &depth = item.hasProperty(rt, "depth") ? static_cast(item.getProperty(rt, "depth").asNumber()) : 1; + NSRange range = NSMakeRange(location, length); - if ([type isEqualToString:@"bold"] || [type isEqualToString:@"italic"] || [type isEqualToString:@"code"] || [type isEqualToString:@"pre"] || [type isEqualToString:@"h1"] || [type isEqualToString:@"emoji"]) { + if (type == "bold" || type == "italic" || type == "code" || type == "pre" || type == "h1" || type == "emoji") { UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:location effectiveRange:NULL]; - if ([type isEqualToString:@"bold"]) { + if (type == "bold") { font = [RCTFont updateFont:font withWeight:@"bold"]; - } else if ([type isEqualToString:@"italic"]) { + } else if (type == "italic") { font = [RCTFont updateFont:font withStyle:@"italic"]; - } else if ([type isEqualToString:@"code"]) { + } else if (type == "code") { font = [RCTFont updateFont:font withFamily:_markdownStyle.codeFontFamily size:[NSNumber numberWithFloat:_markdownStyle.codeFontSize] weight:nil style:nil variant:nil scaleMultiplier:0]; - } else if ([type isEqualToString:@"pre"]) { + } else if (type == "pre") { font = [RCTFont updateFont:font withFamily:_markdownStyle.preFontFamily size:[NSNumber numberWithFloat:_markdownStyle.preFontSize] weight:nil style:nil variant:nil scaleMultiplier:0]; - } else if ([type isEqualToString:@"h1"]) { + } else if (type == "h1") { font = [RCTFont updateFont:font withFamily:nil size:[NSNumber numberWithFloat:_markdownStyle.h1FontSize] weight:@"bold" style:nil variant:nil scaleMultiplier:0]; - } else if ([type isEqualToString:@"emoji"]) { + } else if (type == "emoji") { font = [RCTFont updateFont:font withFamily:nil size:[NSNumber numberWithFloat:_markdownStyle.emojiFontSize] weight:nil @@ -94,27 +105,27 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA [attributedString addAttribute:NSFontAttributeName value:font range:range]; } - if ([type isEqualToString:@"syntax"]) { + if (type == "syntax") { [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.syntaxColor range:range]; - } else if ([type isEqualToString:@"strikethrough"]) { + } else if (type == "strikethrough") { [attributedString addAttribute:NSStrikethroughStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range]; - } else if ([type isEqualToString:@"code"]) { + } else if (type == "code") { [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.codeColor range:range]; [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.codeBackgroundColor range:range]; - } else if ([type isEqualToString:@"mention-here"]) { + } else if (type == "mention-here") { [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionHereColor range:range]; [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionHereBackgroundColor range:range]; - } else if ([type isEqualToString:@"mention-user"]) { + } else if (type == "mention-user") { // TODO: change mention color when it mentions current user [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionUserColor range:range]; [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionUserBackgroundColor range:range]; - } else if ([type isEqualToString:@"mention-report"]) { + } else if (type == "mention-report") { [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.mentionReportColor range:range]; [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.mentionReportBackgroundColor range:range]; - } else if ([type isEqualToString:@"link"]) { + } else if (type == "link") { [attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:range]; [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.linkColor range:range]; - } else if ([type isEqualToString:@"blockquote"]) { + } else if (type == "blockquote") { CGFloat indent = (_markdownStyle.blockquoteMarginLeft + _markdownStyle.blockquoteBorderWidth + _markdownStyle.blockquotePaddingLeft) * depth; NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; paragraphStyle.firstLineHeadIndent = indent; @@ -124,17 +135,17 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA @"range": [NSValue valueWithRange:range], @"depth": @(depth) }]; - } else if ([type isEqualToString:@"pre"]) { + } else if (type == "pre") { [attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range]; NSRange rangeForBackground = [inputString characterAtIndex:range.location] == '\n' ? NSMakeRange(range.location + 1, range.length - 1) : range; [attributedString addAttribute:NSBackgroundColorAttributeName value:_markdownStyle.preBackgroundColor range:rangeForBackground]; // TODO: pass background color and ranges to layout manager - } else if ([type isEqualToString:@"h1"]) { + } else if (type == "h1") { NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; NSRange rangeWithHashAndSpace = NSMakeRange(range.location - 2, range.length + 2); // we also need to include prepending "# " [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:rangeWithHashAndSpace]; } - }]; + } RCTApplyBaselineOffset(attributedString);