diff --git a/packages/react-native/React/Base/RCTBridgeModule.h b/packages/react-native/React/Base/RCTBridgeModule.h index 940e64f4dd06..3a2e43be59a1 100644 --- a/packages/react-native/React/Base/RCTBridgeModule.h +++ b/packages/react-native/React/Base/RCTBridgeModule.h @@ -22,6 +22,7 @@ @class RCTModuleRegistry; @class RCTViewRegistry; @class RCTCallableJSModules; +@class RCTBundleProvider; /** * The type of a block that is capable of sending a response to a bridged @@ -142,6 +143,16 @@ RCT_EXTERN_C_END */ @property (nonatomic, weak, readwrite) RCTBundleManager *bundleManager; +/** + * A reference to the RCTBundleProvider. Useful for modules that need to use + the app's JavaScript bundle and sourceURL. + * + * To implement this in your module, just add `@synthesize bundleProvider = + * _bundleProvider;`. If using Swift, add `@objc var bundleProvider: + * RCTBundleProvider!` to your module. + */ +@property (nonatomic, weak, readwrite) RCTBundleProvider *bundleProvider; + /** * A reference to an RCTCallableJSModules. Useful for modules that need to * call into methods on JavaScript modules registered as callable with diff --git a/packages/react-native/React/Base/RCTBridgeModuleDecorator.h b/packages/react-native/React/Base/RCTBridgeModuleDecorator.h index e7860967d1a7..4724ba8e4076 100644 --- a/packages/react-native/React/Base/RCTBridgeModuleDecorator.h +++ b/packages/react-native/React/Base/RCTBridgeModuleDecorator.h @@ -23,11 +23,13 @@ @property (nonatomic, strong, readonly) RCTViewRegistry *viewRegistry_DEPRECATED; @property (nonatomic, strong, readonly) RCTModuleRegistry *moduleRegistry; @property (nonatomic, strong, readonly) RCTBundleManager *bundleManager; +@property (nonatomic, strong, readonly) RCTBundleProvider *bundleProvider; @property (nonatomic, strong, readonly) RCTCallableJSModules *callableJSModules; - (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry moduleRegistry:(RCTModuleRegistry *)moduleRegistry bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider callableJSModules:(RCTCallableJSModules *)callableJSModules; - (void)attachInteropAPIsToModule:(id)bridgeModule; diff --git a/packages/react-native/React/Base/RCTBridgeModuleDecorator.m b/packages/react-native/React/Base/RCTBridgeModuleDecorator.m index 7f8e15417bc2..397df7e41786 100644 --- a/packages/react-native/React/Base/RCTBridgeModuleDecorator.m +++ b/packages/react-native/React/Base/RCTBridgeModuleDecorator.m @@ -12,12 +12,14 @@ @implementation RCTBridgeModuleDecorator - (instancetype)initWithViewRegistry:(RCTViewRegistry *)viewRegistry moduleRegistry:(RCTModuleRegistry *)moduleRegistry bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider callableJSModules:(RCTCallableJSModules *)callableJSModules { if (self = [super init]) { _viewRegistry_DEPRECATED = viewRegistry; _moduleRegistry = moduleRegistry; _bundleManager = bundleManager; + _bundleProvider = bundleProvider; _callableJSModules = callableJSModules; } return self; @@ -47,6 +49,17 @@ - (void)attachInteropAPIsToModule:(id)bridgeModule bridgeModule.bundleManager = _bundleManager; } + /** + * Attach the RCTBundleProvider to this TurboModule, which allows this TurboModule to + * read from the app's bundle and sourceURL. + * + * Usage: In the TurboModule @implementation, include: + * `@synthesize bundleProvider = _bundleProvider` + */ + if([bridgeModule respondsToSelector:@selector(setBundleProvider:)]) { + bridgeModule.bundleProvider = _bundleProvider; + } + /** * Attach the RCTCallableJSModules to this TurboModule, which allows this TurboModule * to call JS Module methods. diff --git a/packages/react-native/React/Base/RCTBundleProvider.h b/packages/react-native/React/Base/RCTBundleProvider.h new file mode 100644 index 000000000000..00711fdd549d --- /dev/null +++ b/packages/react-native/React/Base/RCTBundleProvider.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#ifdef __cplusplus +#import +#endif // __cplusplus + +@class NSBundleWrapper; + +/** + * Provides the interface needed to register a Bundle Provider module. + */ +@interface RCTBundleProvider : NSObject + +#ifdef __cplusplus + +- (std::shared_ptr)getBundle; +- (NSString *)getSourceURL; + +#endif // __cplusplus + +@end diff --git a/packages/react-native/React/Base/RCTBundleProvider.mm b/packages/react-native/React/Base/RCTBundleProvider.mm new file mode 100644 index 000000000000..f7abbc9b6ead --- /dev/null +++ b/packages/react-native/React/Base/RCTBundleProvider.mm @@ -0,0 +1,32 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTBundleProvider.h" + +using namespace facebook::react; + +@implementation RCTBundleProvider{ + std::shared_ptr _bundle; + NSString *_sourceURL; +} + +- (std::shared_ptr)getBundle { + return _bundle; +} + +- (void)setBundle:(std::shared_ptr)bundle { + _bundle = bundle; +} + +- (NSString *)getSourceURL { + return _sourceURL; +} + +- (void)setSourceURL:(NSString *)sourceURL { + _sourceURL = sourceURL; +} +@end diff --git a/packages/react-native/React/Base/RCTModuleData.h b/packages/react-native/React/Base/RCTModuleData.h index 812991e4d26a..e2008a5eff3b 100644 --- a/packages/react-native/React/Base/RCTModuleData.h +++ b/packages/react-native/React/Base/RCTModuleData.h @@ -18,6 +18,7 @@ @class RCTBundleManager; @class RCTCallableJSModules; @class RCTCallInvoker; +@class RCTBundleProvider; typedef id (^RCTBridgeModuleProvider)(void); @@ -34,6 +35,7 @@ typedef id (^RCTBridgeModuleProvider)(void); moduleRegistry:(RCTModuleRegistry *)moduleRegistry viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider callableJSModules:(RCTCallableJSModules *)callableJSModules NS_DESIGNATED_INITIALIZER __deprecated_msg("This API will be removed along with the legacy architecture."); @@ -42,6 +44,7 @@ typedef id (^RCTBridgeModuleProvider)(void); moduleRegistry:(RCTModuleRegistry *)moduleRegistry viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider callableJSModules:(RCTCallableJSModules *)callableJSModules NS_DESIGNATED_INITIALIZER __deprecated_msg("This API will be removed along with the legacy architecture."); diff --git a/packages/react-native/React/Base/RCTModuleData.mm b/packages/react-native/React/Base/RCTModuleData.mm index 918261456bec..9dee6da2782a 100644 --- a/packages/react-native/React/Base/RCTModuleData.mm +++ b/packages/react-native/React/Base/RCTModuleData.mm @@ -46,6 +46,7 @@ @implementation RCTModuleData { RCTModuleRegistry *_moduleRegistry; RCTViewRegistry *_viewRegistry_DEPRECATED; RCTBundleManager *_bundleManager; + RCTBundleProvider *_bundleProvider; RCTCallableJSModules *_callableJSModules; BOOL _isInitialized; } @@ -100,6 +101,7 @@ - (instancetype)initWithModuleClass:(Class)moduleClass moduleRegistry:(RCTModuleRegistry *)moduleRegistry viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider callableJSModules:(RCTCallableJSModules *)callableJSModules { if (self = [super init]) { @@ -121,6 +123,7 @@ - (instancetype)initWithModuleInstance:(id)instance moduleRegistry:(RCTModuleRegistry *)moduleRegistry viewRegistry_DEPRECATED:(RCTViewRegistry *)viewRegistry_DEPRECATED bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider callableJSModules:(RCTCallableJSModules *)callableJSModules { if (self = [super init]) { @@ -195,6 +198,7 @@ - (void)setUpInstanceAndBridge:(int32_t)requestId [[RCTBridgeModuleDecorator alloc] initWithViewRegistry:_viewRegistry_DEPRECATED moduleRegistry:_moduleRegistry bundleManager:_bundleManager + bundleProvider:_bundleProvider callableJSModules:_callableJSModules]; [moduleDecorator attachInteropAPIsToModule:_instance]; diff --git a/packages/react-native/React/CxxBridge/RCTCxxBridge.mm b/packages/react-native/React/CxxBridge/RCTCxxBridge.mm index a67f5bc646b9..cc1631fc1fc5 100644 --- a/packages/react-native/React/CxxBridge/RCTCxxBridge.mm +++ b/packages/react-native/React/CxxBridge/RCTCxxBridge.mm @@ -244,6 +244,7 @@ @implementation RCTCxxBridge { RCTModuleRegistry *_objCModuleRegistry; RCTViewRegistry *_viewRegistry_DEPRECATED; RCTBundleManager *_bundleManager; + RCTBundleProvider *_bundleProvider; RCTCallableJSModules *_callableJSModules; std::atomic _loading; std::atomic _valid; @@ -288,6 +289,7 @@ - (RCTBridgeModuleDecorator *)bridgeModuleDecorator return [[RCTBridgeModuleDecorator alloc] initWithViewRegistry:_viewRegistry_DEPRECATED moduleRegistry:_objCModuleRegistry bundleManager:_bundleManager + bundleProvider:_bundleProvider callableJSModules:_callableJSModules]; } @@ -810,6 +812,7 @@ - (void)updateModuleWithInstance:(id)instance moduleRegistry:_objCModuleRegistry viewRegistry_DEPRECATED:_viewRegistry_DEPRECATED bundleManager:_bundleManager + bundleProvider:_bundleProvider callableJSModules:_callableJSModules]; moduleData.callInvokerProvider = self; BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId); @@ -888,6 +891,7 @@ - (void)registerExtraModules moduleRegistry:_objCModuleRegistry viewRegistry_DEPRECATED:_viewRegistry_DEPRECATED bundleManager:_bundleManager + bundleProvider:_bundleProvider callableJSModules:_callableJSModules]; BridgeNativeModulePerfLogger::moduleDataCreateEnd([moduleName UTF8String], moduleDataId); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java index 1ccb8abad435..2e78c5400296 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java @@ -25,6 +25,7 @@ import com.facebook.react.common.annotations.internal.LegacyArchitecture; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; +import com.facebook.react.fabric.BundleWrapper;; import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; import java.util.Collection; import java.util.Objects; @@ -292,6 +293,16 @@ public CallInvokerHolder getJSCallInvokerHolder() { return Objects.requireNonNull(mCatalystInstance).getFabricUIManager(); } + /** + * Get the JS bundle. + * + * @return The JS bundle set when the bundle was loaded + */ + @Override + public @Nullable BundleWrapper getBundle() { + return mCatalystInstance == null ? null : mCatalystInstance.getBundle(); + } + /** * Get the sourceURL for the JS bundle from the CatalystInstance. This method is needed for * compatibility with bridgeless mode, which has no CatalystInstance. diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.kt index df0a40ec2409..430385dc5af3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.kt @@ -12,6 +12,7 @@ package com.facebook.react.bridge import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.bridge.queue.ReactQueueConfiguration import com.facebook.react.common.annotations.internal.LegacyArchitecture +import com.facebook.react.fabric.BundleWrapper import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder import com.facebook.react.turbomodule.core.interfaces.NativeMethodCallInvokerHolder @@ -39,6 +40,11 @@ public interface CatalystInstance : MemoryPressureListener, JSInstance, JSBundle */ public val sourceURL: String? + /** + * Get the JS bundle that was run or `null` if no JS bundle has been run yet. + */ + public val bundle: BundleWrapper? + // This is called from java code, so it won't be stripped anyway, but proguard will rename it, // which this prevents. @DoNotStrip public override fun invokeCallback(callbackID: Int, arguments: NativeArrayInterface) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 6485791b2089..507e611f9b1c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -28,6 +28,7 @@ import com.facebook.react.common.annotations.internal.LegacyArchitecture; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; +import com.facebook.react.fabric.BundleWrapper; import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags; import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry; import com.facebook.react.module.annotations.ReactModule; @@ -104,6 +105,7 @@ public String toString() { private boolean mJSBundleHasLoaded; private @Nullable String mSourceURL; + private @Nullable BundleWrapper mBundle; private JavaScriptContextHolder mJavaScriptContextHolder; private @Nullable TurboModuleRegistry mTurboModuleRegistry; @@ -276,6 +278,11 @@ public boolean hasRunJSBundle() { } } + @Override + public @Nullable BundleWrapper getBundle() { + return mBundle; + } + @Override public @Nullable String getSourceURL() { return mSourceURL; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index 7a0655081a86..c49dffd0dcac 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -26,6 +26,7 @@ import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.LifecycleState; import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; +import com.facebook.react.fabric.BundleWrapper; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.concurrent.CopyOnWriteArraySet; @@ -505,6 +506,13 @@ public boolean startActivityForResult(Intent intent, int code, Bundle bundle) { */ public abstract @Nullable UIManager getFabricUIManager(); + /** + * Get the BundleWrapper for the JS bundle. + * + * @return The BundleWrapper containing the JS bundle. + */ + public abstract @Nullable BundleWrapper getBundle(); + /** * Get the sourceURL for the JS bundle from the CatalystInstance. This method is needed for * compatibility with bridgeless mode, which has no CatalystInstance. diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/BundleWrapper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/BundleWrapper.kt new file mode 100644 index 000000000000..1a686f66bc19 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/BundleWrapper.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.fabric + +import android.annotation.SuppressLint +import android.content.res.AssetManager +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStripAny + +/** + * A wrapper around a JavaScript bundle that is backed by a native C++ object. + */ +@SuppressLint("MissingNativeLoadLibrary") +@DoNotStripAny +public class BundleWrapper { + + private val mHybridData: HybridData + + public constructor(fileName: String) { + mHybridData = initHybridFromFile(fileName) + } + + public constructor(assetManager: AssetManager, assetURL: String) { + mHybridData = initHybridFromAssets(assetManager, assetURL) + } + + private external fun initHybridFromFile(fileName: String): HybridData + + private external fun initHybridFromAssets( + assetManager: AssetManager, + assetURL: String + ): HybridData +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessCatalystInstance.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessCatalystInstance.kt index 1a65c44a5e61..d1cf126ab21e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessCatalystInstance.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessCatalystInstance.kt @@ -28,6 +28,7 @@ import com.facebook.react.common.annotations.VisibleForTesting import com.facebook.react.common.annotations.internal.LegacyArchitecture import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger +import com.facebook.react.fabric.BundleWrapper import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder import com.facebook.react.turbomodule.core.interfaces.NativeMethodCallInvokerHolder @@ -136,6 +137,9 @@ internal class BridgelessCatalystInstance(private val reactHost: ReactHostImpl) throw UnsupportedOperationException("Unimplemented method 'extendNativeModules'") } + override val bundle: BundleWrapper? + get() = throw UnsupportedOperationException("Unimplemented method 'getBundle'") + override val sourceURL: String get() = throw UnsupportedOperationException("Unimplemented method 'getSourceURL'") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.kt index 30a79dc06b95..f956a673250d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.kt @@ -26,6 +26,7 @@ import com.facebook.react.common.annotations.FrameworkAPI import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.common.build.ReactBuildConfig import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.fabric.BundleWrapper import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder @@ -46,6 +47,7 @@ import java.util.concurrent.atomic.AtomicReference */ internal class BridgelessReactContext(context: Context, private val reactHost: ReactHostImpl) : ReactApplicationContext(context), EventDispatcherProvider { + private val bundleRef = AtomicReference() private val sourceURLRef = AtomicReference() private val TAG: String = this.javaClass.simpleName @@ -57,6 +59,12 @@ internal class BridgelessReactContext(context: Context, private val reactHost: R override fun getEventDispatcher(): EventDispatcher = reactHost.eventDispatcher + override fun getBundle(): BundleWrapper? = bundleRef.get() + + fun setBundle(bundle: BundleWrapper?) { + bundleRef.set(bundle) + } + override fun getSourceURL(): String? = sourceURLRef.get() fun setSourceURL(sourceURL: String?) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt index 4999f8670504..bf674b6417f3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt @@ -43,6 +43,7 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI import com.facebook.react.devsupport.InspectorFlags.getIsProfilingBuild import com.facebook.react.devsupport.StackTraceHelper import com.facebook.react.devsupport.interfaces.DevSupportManager +import com.facebook.react.fabric.BundleWrapper import com.facebook.react.fabric.ComponentFactory import com.facebook.react.fabric.FabricUIManager import com.facebook.react.fabric.FabricUIManagerBinding @@ -298,6 +299,11 @@ internal class ReactInstance( } } + fun beforeLoad(bundle: BundleWrapper, sourceURL: String){ + context.setSourceURL(sourceURL) + context.setBundle(bundle) + } + fun loadJSBundle(bundleLoader: JSBundleLoader) { Systrace.beginSection(Systrace.TRACE_TAG_REACT, "ReactInstance.loadJSBundle") bundleLoader.loadScript( @@ -307,12 +313,17 @@ internal class ReactInstance( sourceURL: String, loadSynchronously: Boolean, ) { - context.sourceURL = sourceURL - loadJSBundleFromFile(fileName, sourceURL) + val bundle = BundleWrapper(fileName); + + beforeLoad(bundle, sourceURL); + loadJSBundle(bundle, sourceURL) } override fun loadSplitBundleFromFile(fileName: String, sourceURL: String) { - loadJSBundleFromFile(fileName, sourceURL) + val bundle = BundleWrapper(fileName) + + beforeLoad(bundle, sourceURL) + loadJSBundle(bundle, sourceURL) } override fun loadScriptFromAssets( @@ -320,8 +331,11 @@ internal class ReactInstance( assetURL: String, loadSynchronously: Boolean, ) { - context.sourceURL = assetURL - loadJSBundleFromAssets(assetManager, assetURL) + val sourceURL = assetURL.removePrefix("assets://") + val bundle = BundleWrapper(assetManager, sourceURL) + + beforeLoad(bundle, assetURL) + loadJSBundle(bundle, assetURL) } override fun setSourceURLs(deviceURL: String, remoteURL: String) { @@ -436,7 +450,7 @@ internal class ReactInstance( reactHostInspectorTarget: ReactHostInspectorTarget?, ): HybridData - private external fun loadJSBundleFromFile(fileName: String, sourceURL: String) + private external fun loadJSBundle(bundle: BundleWrapper, sourceURL: String) private external fun loadJSBundleFromAssets(assetManager: AssetManager, assetURL: String) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt index d91b7f1b1895..1257b3483660 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt @@ -21,6 +21,7 @@ import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.UIManager import com.facebook.react.common.annotations.internal.LegacyArchitecture +import com.facebook.react.fabric.BundleWrapper import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder /** @@ -149,6 +150,8 @@ public class ThemedReactContext( ) override fun getFabricUIManager(): UIManager? = reactApplicationContext.getFabricUIManager() + override fun getBundle(): BundleWrapper? = reactApplicationContext.getBundle() + override fun getSourceURL(): String? = reactApplicationContext.getSourceURL() override fun registerSegment(segmentId: Int, path: String?, callback: Callback?) { diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/BundleWrapper.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/BundleWrapper.cpp new file mode 100644 index 000000000000..3a606b1f3c64 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/BundleWrapper.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "BundleWrapper.h" +#include +#include + +using namespace facebook::jni; + +namespace facebook::react { +jni::local_ref BundleWrapper::initHybridFromFile( + jni::alias_ref jThis, + std::string fileName) { + std::unique_ptr script; + RecoverableError::runRethrowingAsRecoverable( + [&fileName, &script]() { script = JSBigFileString::fromPath(fileName); }); + return makeCxxInstance(std::move(script)); +} + +jni::local_ref BundleWrapper::initHybridFromAssets( + jni::alias_ref jThis, + jni::alias_ref assetManager, + const std::string& sourceURL) { + auto manager = extractAssetManager(assetManager); + auto script = loadScriptFromAssets(manager, sourceURL); + return makeCxxInstance(std::move(script)); +} + +BundleWrapper::BundleWrapper( + const std::shared_ptr& bundle) + : bundle_(bundle) {} + +std::shared_ptr BundleWrapper::getBundle() const { + return bundle_; +} + +void BundleWrapper::registerNatives() { + registerHybrid( + {makeNativeMethod( + "initHybridFromFile", BundleWrapper::initHybridFromFile), + makeNativeMethod( + "initHybridFromAssets", BundleWrapper::initHybridFromAssets)}); +} +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/BundleWrapper.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/BundleWrapper.h new file mode 100644 index 000000000000..e9a90aa4bbf9 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/BundleWrapper.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace facebook::react { + +class BundleWrapper : public jni::HybridClass { + public: + constexpr static const char* const kJavaDescriptor = + "Lcom/facebook/react/fabric/BundleWrapper;"; + + static void registerNatives(); + + [[nodiscard]] std::shared_ptr getBundle() const; + + private: + static jni::local_ref initHybridFromFile( + jni::alias_ref jThis, + std::string fileName); + + static jni::local_ref initHybridFromAssets( + jni::alias_ref jThis, + jni::alias_ref assetManager, + const std::string& assetURL); + + friend HybridBase; + + explicit BundleWrapper(const std::shared_ptr& bundle); + + const std::shared_ptr bundle_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt index bdef71c0d4a6..09ecad6e7b7b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt @@ -26,6 +26,7 @@ target_link_libraries( folly_runtime glog jsi + jsireact mapbufferjni react_codegen_rncore react_debug diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp index 2d411d4dc7af..8fec1ae37be0 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp @@ -7,6 +7,7 @@ #include +#include "BundleWrapper.h" #include "ComponentFactory.h" #include "EventBeatManager.h" #include "EventEmitterWrapper.h" @@ -22,5 +23,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*unused*/) { facebook::react::StateWrapperImpl::registerNatives(); facebook::react::ComponentFactory::registerNatives(); facebook::react::SurfaceHandlerBinding::registerNatives(); + facebook::react::BundleWrapper::registerNatives(); }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp index b2911f092b5f..749882022b10 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.cpp @@ -133,24 +133,11 @@ jni::local_ref JReactInstance::initHybrid( jReactHostInspectorTarget); } -void JReactInstance::loadJSBundleFromAssets( - jni::alias_ref assetManager, - const std::string& assetURL) { - const int kAssetsLength = 9; // strlen("assets://"); - auto sourceURL = assetURL.substr(kAssetsLength); - - auto manager = extractAssetManager(assetManager); - auto script = loadScriptFromAssets(manager, sourceURL); - instance_->loadScript(std::move(script), sourceURL); -} - -void JReactInstance::loadJSBundleFromFile( - const std::string& fileName, +void JReactInstance::loadJSBundle( + jni::alias_ref bundleWrapper, const std::string& sourceURL) { - std::unique_ptr script; - RecoverableError::runRethrowingAsRecoverable( - [&fileName, &script]() { script = JSBigFileString::fromPath(fileName); }); - instance_->loadScript(std::move(script), sourceURL); + auto bundle = bundleWrapper->cthis()->getBundle(); + instance_->loadScript(bundle, sourceURL); } /** @@ -212,10 +199,7 @@ void JReactInstance::unregisterFromInspector() { void JReactInstance::registerNatives() { registerHybrid({ makeNativeMethod("initHybrid", JReactInstance::initHybrid), - makeNativeMethod( - "loadJSBundleFromAssets", JReactInstance::loadJSBundleFromAssets), - makeNativeMethod( - "loadJSBundleFromFile", JReactInstance::loadJSBundleFromFile), + makeNativeMethod("loadJSBundle", JReactInstance::loadJSBundle), makeNativeMethod( "getJSCallInvokerHolder", JReactInstance::getJSCallInvokerHolder), makeNativeMethod( diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h index d58e52722b71..f558f59c4c8b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactInstance.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -49,9 +50,9 @@ class JReactInstance : public jni::HybridClass { static void registerNatives(); - void loadJSBundleFromAssets(jni::alias_ref assetManager, const std::string &assetURL); - - void loadJSBundleFromFile(const std::string &fileName, const std::string &sourceURL); + void loadJSBundle( + jni::alias_ref bundleWrapper, + const std::string& sourceURL); void callFunctionOnModule(const std::string &moduleName, const std::string &methodName, NativeArray *args); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm index 0f11ef114b9f..130060f3e862 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm @@ -791,6 +791,7 @@ - (BOOL)_shouldCreateObjCModule:(Class)moduleClass moduleRegistry:_bridge.moduleRegistry viewRegistry_DEPRECATED:nil bundleManager:nil + bundleProvider:nil callableJSModules:nil]; [_bridge registerModuleForFrameUpdates:(id)module withModuleData:data]; } diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp index 0e792803be45..c0d8ef42ba89 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp @@ -228,17 +228,16 @@ std::string simpleBasename(const std::string& path) { * JSThread, preferably via the runtimeExecutor_. */ void ReactInstance::loadScript( - std::unique_ptr script, + const std::shared_ptr& script, const std::string& sourceURL, std::function&& beforeLoad, std::function&& afterLoad) { - std::shared_ptr buffer(std::move(script)); std::string scriptName = simpleBasename(sourceURL); runtimeScheduler_->scheduleWork([this, scriptName, sourceURL, - buffer = std::move(buffer), + script, weakBufferedRuntimeExecuter = std::weak_ptr( bufferedRuntimeExecutor_), @@ -256,7 +255,7 @@ void ReactInstance::loadScript( ReactMarker::logMarkerBridgeless(ReactMarker::APP_STARTUP_START); } - // Check if the shermes unit is avaliable. + // Check if the shermes unit is available. auto* shUnitAPI = jsi::castInterface(&runtime); auto* shUnitCreator = shUnitAPI ? shUnitAPI->getSHUnitCreator() : nullptr; if (shUnitCreator) { @@ -265,7 +264,7 @@ void ReactInstance::loadScript( hermesAPI->evaluateSHUnit(shUnitCreator); } else { LOG(WARNING) << "ReactInstance: evaluateJavaScript() with JS bundle"; - runtime.evaluateJavaScript(buffer, sourceURL); + runtime.evaluateJavaScript(script, sourceURL); } /** diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.h b/packages/react-native/ReactCommon/react/runtime/ReactInstance.h index b850d0fac316..7f129a252667 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.h +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.h @@ -50,10 +50,10 @@ class ReactInstance final : private jsinspector_modern::InstanceTargetDelegate { void initializeRuntime(JSRuntimeFlags options, BindingsInstallFunc bindingsInstallFunc) noexcept; void loadScript( - std::unique_ptr script, - const std::string &sourceURL, - std::function &&beforeLoad = nullptr, - std::function &&afterLoad = nullptr); + const std::shared_ptr& script, + const std::string& sourceURL, + std::function&& beforeLoad = nullptr, + std::function&& afterLoad = nullptr); void registerSegment(uint32_t segmentId, const std::string &segmentPath); diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm index 07f4c0686816..c059cf9e4086 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm @@ -10,6 +10,7 @@ #import #import +#import #import #import #import @@ -116,6 +117,7 @@ @implementation RCTHost { NSURL *_oldDelegateBundleURL; NSURL *_bundleURL; RCTBundleManager *_bundleManager; + RCTBundleProvider *_bundleProvider; RCTHostBundleURLProvider _bundleURLProvider; RCTHostJSEngineProvider _jsEngineProvider; @@ -184,6 +186,7 @@ - (instancetype)initWithBundleURLProvider:(RCTHostBundleURLProvider)provider _turboModuleManagerDelegate = turboModuleManagerDelegate; _bundleManager = [[RCTBundleManager alloc] initWithBundleConfig:bundleConfiguration]; _moduleRegistry = [RCTModuleRegistry new]; + _bundleProvider = [RCTBundleProvider new]; _jsEngineProvider = [jsEngineProvider copy]; _launchOptions = [launchOptions copy]; @@ -265,6 +268,7 @@ - (void)start _instance = [[RCTInstance alloc] initWithDelegate:self jsRuntimeFactory:[self _provideJSEngine] bundleManager:_bundleManager + bundleProvider:_bundleProvider turboModuleManagerDelegate:_turboModuleManagerDelegate moduleRegistry:_moduleRegistry parentInspectorTarget:_inspectorTarget.get() @@ -469,6 +473,7 @@ - (void)_reloadWithShouldRestartSurfaces:(BOOL)shouldRestartSurfaces _instance = [[RCTInstance alloc] initWithDelegate:self jsRuntimeFactory:[self _provideJSEngine] bundleManager:_bundleManager + bundleProvider:_bundleProvider turboModuleManagerDelegate:_turboModuleManagerDelegate moduleRegistry:_moduleRegistry parentInspectorTarget:_inspectorTarget.get() diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h index 72b7585184e6..ea3e131c32dd 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.h @@ -26,6 +26,7 @@ RCT_EXTERN NSString *RCTInstanceRuntimeDiagnosticFlags(void); RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); @class RCTBundleManager; +@class RCTBundleProvider; @class RCTInstance; @class RCTJSThreadManager; @class RCTModuleRegistry; @@ -69,6 +70,7 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); - (instancetype)initWithDelegate:(id)delegate jsRuntimeFactory:(std::shared_ptr)jsRuntimeFactory bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider turboModuleManagerDelegate:(id)turboModuleManagerDelegate moduleRegistry:(RCTModuleRegistry *)moduleRegistry parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget @@ -77,6 +79,7 @@ RCT_EXTERN void RCTInstanceSetRuntimeDiagnosticFlags(NSString *_Nullable flags); - (instancetype)initWithDelegate:(id)delegate jsRuntimeFactory:(std::shared_ptr)jsRuntimeFactory bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider turboModuleManagerDelegate:(id)turboModuleManagerDelegate moduleRegistry:(RCTModuleRegistry *)moduleRegistry parentInspectorTarget:(facebook::react::jsinspector_modern::HostTarget *)parentInspectorTarget diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index d065b1b7943d..491e1a74f69f 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -72,6 +72,11 @@ void RCTInstanceSetRuntimeDiagnosticFlags(NSString *flags) sRuntimeDiagnosticFlags = [flags copy]; } +@interface RCTBundleProvider : NSObject +- (void)setBundle:(std::shared_ptr)bundleBuffer; +- (void)setSourceURL:(NSString *)sourceURL; +@end + @interface RCTBridgelessDisplayLinkModuleHolder : NSObject - (instancetype)initWithModule:(id)module; @end @@ -113,6 +118,7 @@ @implementation RCTInstance { RCTPerformanceLogger *_performanceLogger; RCTDisplayLink *_displayLink; RCTTurboModuleManager *_turboModuleManager; + RCTBundleProvider *_bundleProvider; std::mutex _invalidationMutex; std::atomic _valid; RCTJSThreadManager *_jsThreadManager; @@ -131,6 +137,7 @@ @implementation RCTInstance { - (instancetype)initWithDelegate:(id)delegate jsRuntimeFactory:(std::shared_ptr)jsRuntimeFactory bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider turboModuleManagerDelegate:(id)tmmDelegate moduleRegistry:(RCTModuleRegistry *)moduleRegistry parentInspectorTarget:(jsinspector_modern::HostTarget *)parentInspectorTarget @@ -139,6 +146,7 @@ - (instancetype)initWithDelegate:(id)delegate return [self initWithDelegate:delegate jsRuntimeFactory:jsRuntimeFactory bundleManager:bundleManager + bundleProvider:bundleProvider turboModuleManagerDelegate:tmmDelegate moduleRegistry:moduleRegistry parentInspectorTarget:parentInspectorTarget @@ -149,6 +157,7 @@ - (instancetype)initWithDelegate:(id)delegate - (instancetype)initWithDelegate:(id)delegate jsRuntimeFactory:(std::shared_ptr)jsRuntimeFactory bundleManager:(RCTBundleManager *)bundleManager + bundleProvider:(RCTBundleProvider *)bundleProvider turboModuleManagerDelegate:(id)tmmDelegate moduleRegistry:(RCTModuleRegistry *)moduleRegistry parentInspectorTarget:(jsinspector_modern::HostTarget *)parentInspectorTarget @@ -164,9 +173,11 @@ - (instancetype)initWithDelegate:(id)delegate _jsRuntimeFactory = jsRuntimeFactory; _appTMMDelegate = tmmDelegate; _jsThreadManager = [RCTJSThreadManager new]; + _bundleProvider = bundleProvider; _bridgeModuleDecorator = [[RCTBridgeModuleDecorator alloc] initWithViewRegistry:[RCTViewRegistry new] moduleRegistry:moduleRegistry bundleManager:bundleManager + bundleProvider:bundleProvider callableJSModules:[RCTCallableJSModules new]]; _devMenuConfigurationDecorator = #if RCT_DEV_MENU @@ -601,9 +612,12 @@ - (void)_loadScriptFromSource:(RCTSource *)source return; } - auto script = std::make_unique(source.data); + auto script = std::make_shared(source.data); const auto *url = deriveSourceURL(source.url).UTF8String; + [_bundleProvider setBundle:script]; + [_bundleProvider setSourceURL:@(url)]; + auto beforeLoad = [waitUntilModuleSetupComplete = self->_waitUntilModuleSetupComplete](jsi::Runtime &_) { if (waitUntilModuleSetupComplete) { waitUntilModuleSetupComplete(); @@ -612,7 +626,7 @@ - (void)_loadScriptFromSource:(RCTSource *)source auto afterLoad = [](jsi::Runtime &_) { [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTInstanceDidLoadBundle" object:nil]; }; - _reactInstance->loadScript(std::move(script), url, beforeLoad, afterLoad); + _reactInstance->loadScript(script, url, beforeLoad, afterLoad); } - (void)_handleJSError:(const JsErrorHandler::ProcessedError &)error withRuntime:(jsi::Runtime &)runtime