react-native/React/Base/RCTBridge.m
Martín Bigio 4ffb241647 Hot Loading E2E basic flow
Summary:
public

Implement all the necessary glue code for several diffs submitted before to get Hot Loading work end to end:

- Simplify `HMRClient`: we don't need to make it stateful allowing to enable and disable it because both when we enable and disable the interface we need to reload the bundle.
- On the native side we introduced a singleton to process the bundle URL. This new class might alter the url to include the `hot` attribute. I'm not 100% sure this is the best way to implement this but we cannot use `CTLSettings` for this as it's are not available on oss and I didn't want to contaminate `RCTBridge` with something specific to hot loading. Also, we could potentially use this processor for other things in the future. Please let me know if you don't like this approach or you have a better idea :).
- Use this processor to alter the default bundle URL and request a `hot` bundle when hot loading is enabled. Also make sure to enable the HMR interface when the client activates it on the dev menu.
- Add packager `hot` option.
- Include gaeron's `react-transform` on Facebook's JS transformer.

The current implementation couples a bit React Native to this feature because `react-transform-hmr` is required on `InitializeJavaScriptAppEngine`. Ideally, the packager should accept an additional list of requires and include them on the bundle among all their dependencies. Note this is not the same as the option `runBeforeMainModule` as that one only adds a require to the provided module but doesn't include all the dependencies that module amy have that the entry point doesn't. I'll address this in a follow up task to enable asap hot loading (9536142)

I had to remove 2 `.babelrc` files from `react-proxy` and `react-deep-force-update`. There's an internal task for fixing the underlaying issue to avoid doing this horrible hack (t9515889).

Reviewed By: vjeux

Differential Revision: D2790806

fb-gh-sync-id: d4b78a2acfa071d6b3accc2e6716ef5611ad4fda
2015-12-28 16:44:59 -08:00

318 lines
7.8 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBridge+Private.h"
#import <objc/runtime.h>
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerformanceLogger.h"
#import "RCTUtils.h"
#import "RCTBundleURLProcessor.h"
NSString *const RCTReloadNotification = @"RCTReloadNotification";
NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification";
@interface RCTBatchedBridge : RCTBridge <RCTInvalidating>
@property (nonatomic, weak) RCTBridge *parentBridge;
- (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
@end
static NSMutableArray<Class> *RCTModuleClasses;
NSArray<Class> *RCTGetModuleClasses(void);
NSArray<Class> *RCTGetModuleClasses(void)
{
return RCTModuleClasses;
}
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
*/
void RCTRegisterModule(Class);
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
});
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
moduleClass);
// Register module
[RCTModuleClasses addObject:moduleClass];
}
/**
* This function returns the module name for a given class.
*/
NSString *RCTBridgeModuleNameForClass(Class cls)
{
#if RCT_DEV
RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], @"Bridge module classes must conform to RCTBridgeModule");
#endif
NSString *name = [cls moduleName];
if (name.length == 0) {
name = NSStringFromClass(cls);
}
if ([name hasPrefix:@"RK"]) {
name = [name stringByReplacingCharactersInRange:(NSRange){0,@"RK".length} withString:@"RCT"];
}
return name;
}
/**
* This function checks if a class has been registered
*/
BOOL RCTBridgeModuleClassIsRegistered(Class cls)
{
return [objc_getAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered) ?: @YES boolValue];
}
@implementation RCTBridge
dispatch_queue_t RCTJSThread;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Set up JS thread
RCTJSThread = (id)kCFNull;
#if RCT_DEBUG
// Set up module classes
static unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
{
if (![RCTModuleClasses containsObject:cls]) {
RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
"RCT_EXPORT_MODULE()?", cls);
RCTRegisterModule(cls);
objc_setAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered,
@NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
break;
}
superclass = class_getSuperclass(superclass);
}
}
free(classes);
#endif
});
}
static RCTBridge *RCTCurrentBridgeInstance = nil;
/**
* The last current active bridge instance. This is set automatically whenever
* the bridge is accessed. It can be useful for static functions or singletons
* that need to access the bridge for purposes such as logging, but should not
* be relied upon to return any particular instance, due to race conditions.
*/
+ (instancetype)currentBridge
{
return RCTCurrentBridgeInstance;
}
+ (void)setCurrentBridge:(RCTBridge *)currentBridge
{
RCTCurrentBridgeInstance = currentBridge;
}
- (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
launchOptions:(NSDictionary *)launchOptions
{
RCTAssertMainThread();
if ((self = [super init])) {
RCTPerformanceLoggerStart(RCTPLTTI);
_delegate = delegate;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
}
return self;
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
RCTAssertMainThread();
if ((self = [super init])) {
RCTPerformanceLoggerStart(RCTPLTTI);
_bundleURL = bundleURL;
_moduleProvider = block;
_launchOptions = [launchOptions copy];
[self setUp];
[self bindKeys];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)dealloc
{
/**
* This runs only on the main thread, but crashes the subclass
* RCTAssertMainThread();
*/
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self invalidate];
}
- (void)bindKeys
{
RCTAssertMainThread();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
#if TARGET_IPHONE_SIMULATOR
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(__unused UIKeyCommand *command) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
object:nil
userInfo:nil];
}];
#endif
}
- (NSArray<Class> *)moduleClasses
{
return self.batchedBridge.moduleClasses;
}
- (id)moduleForName:(NSString *)moduleName
{
return [self.batchedBridge moduleForName:moduleName];
}
- (id)moduleForClass:(Class)moduleClass
{
return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)];
}
- (RCTEventDispatcher *)eventDispatcher
{
return [self moduleForClass:[RCTEventDispatcher class]];
}
- (void)reload
{
/**
* AnyThread
*/
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
[self setUp];
});
}
- (void)setUp
{
RCTAssertMainThread();
_bundleURL = [self.delegate sourceURLForBridge:self] ?: _bundleURL;
_bundleURL = [[RCTBundleURLProcessor sharedProcessor] process: _bundleURL];
// Sanitize the bundle URL
_bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
self.batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
}
- (BOOL)isLoading
{
return self.batchedBridge.loading;
}
- (BOOL)isValid
{
return self.batchedBridge.valid;
}
- (BOOL)isBatchActive
{
return [_batchedBridge isBatchActive];
}
- (void)invalidate
{
RCTBatchedBridge *batchedBridge = (RCTBatchedBridge *)self.batchedBridge;
self.batchedBridge = nil;
if (batchedBridge) {
RCTExecuteOnMainThread(^{
[batchedBridge invalidate];
}, NO);
}
}
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
{
[self.batchedBridge enqueueJSCall:moduleDotMethod args:args];
}
- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
{
[self.batchedBridge enqueueCallback:cbID args:args];
}
@end
@implementation RCTBridge(Deprecated)
NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules";
- (NSDictionary *)modules
{
return self.batchedBridge.modules;
}
@end