From d138403c2e162a1fa76774a784e3364ae80b3273 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 10 Dec 2015 04:27:45 -0800 Subject: [PATCH] Added deferred module loading feature Reviewed By: javache Differential Revision: D2717687 fb-gh-sync-id: 4c03c721c794a2e7ac67a0b20474129fde7f0a0d --- .../BatchedBridgedModules/NativeModules.js | 74 +++++++++++- Libraries/Utilities/MessageQueue.js | 107 ++++++++---------- .../Utilities/nativeModulePrefixNormalizer.js | 32 ------ React/Base/RCTBatchedBridge.m | 51 +++++---- React/Base/RCTModuleData.m | 7 ++ React/Executors/RCTContextExecutor.m | 24 +++- 6 files changed, 176 insertions(+), 119 deletions(-) delete mode 100644 Libraries/Utilities/nativeModulePrefixNormalizer.js diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js index e3a179887..837534ac3 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js @@ -11,10 +11,78 @@ */ 'use strict'; -var NativeModules = require('BatchedBridge').RemoteModules; +const BatchedBridge = require('BatchedBridge'); +const RemoteModules = BatchedBridge.RemoteModules; -var nativeModulePrefixNormalizer = require('nativeModulePrefixNormalizer'); +function normalizePrefix(moduleName: string): string { + return moduleName.replace(/^(RCT|RK)/, ''); +} -nativeModulePrefixNormalizer(NativeModules); +/** + * Dirty hack to support old (RK) and new (RCT) native module name conventions. + */ +Object.keys(RemoteModules).forEach((moduleName) => { + const strippedName = normalizePrefix(moduleName); + if (RemoteModules['RCT' + strippedName] && RemoteModules['RK' + strippedName]) { + throw new Error( + 'Module cannot be registered as both RCT and RK: ' + moduleName + ); + } + if (strippedName !== moduleName) { + RemoteModules[strippedName] = RemoteModules[moduleName]; + delete RemoteModules[moduleName]; + } +}); + +/** + * Define lazy getters for each module. + * These will return the module if already loaded, or load it if not. + */ +const NativeModules = {}; +Object.keys(RemoteModules).forEach((moduleName) => { + Object.defineProperty(NativeModules, moduleName, { + enumerable: true, + get: () => { + let module = RemoteModules[moduleName]; + if (module && module.moduleID) { + const json = global.nativeRequireModuleConfig(moduleName); + const config = json && JSON.parse(json); + module = config && BatchedBridge.processModuleConfig(config, module.moduleID); + RemoteModules[moduleName] = module; + } + return module; + }, + }); +}); + +/** + * Copies the ViewManager constants into UIManager. This is only + * needed for iOS, which puts the constants in the ViewManager + * namespace instead of UIManager, unlike Android. + * + * We'll eventually move this logic to UIManager.js, once all + * the call sites accessing NativeModules.UIManager directly have + * been removed #9344445 + */ +const UIManager = NativeModules.UIManager; +UIManager && Object.keys(UIManager).forEach(viewName => { + const viewConfig = UIManager[viewName]; + const constants = {}; + if (viewConfig.Manager) { + Object.defineProperty(viewConfig, 'Constants', { + enumerable: true, + get: () => { + const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)]; + viewManager && Object.keys(viewManager).forEach(key => { + const value = viewManager[key]; + if (typeof value !== 'function') { + constants[key] = value; + } + }); + return constants; + }, + }); + } +}); module.exports = NativeModules; diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index b7dd38a24..adf9376e3 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -66,8 +66,6 @@ class MessageQueue { this._genModulesConfig(localModules),this._moduleTable, this._methodTable ); - this._copyNativeComponentConstants(this.RemoteModules); - this._debugInfo = {}; this._remoteModuleTable = {}; this._remoteMethodTable = {}; @@ -105,6 +103,12 @@ class MessageQueue { return queue[0].length ? queue : null; } + processModuleConfig(config, moduleID) { + const module = this._genModule(config, moduleID); + this._genLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable) + return module; + } + /** * "Private" methods */ @@ -200,30 +204,6 @@ class MessageQueue { * Private helper methods */ - /** - * Copies the ViewManager constants into UIManager. This is only - * needed for iOS, which puts the constants in the ViewManager - * namespace instead of UIManager, unlike Android. - */ - _copyNativeComponentConstants(remoteModules) { - let UIManager = remoteModules.RCTUIManager; - UIManager && Object.keys(UIManager).forEach(viewName => { - let viewConfig = UIManager[viewName]; - if (viewConfig.Manager) { - const viewManager = remoteModules[viewConfig.Manager]; - viewManager && Object.keys(viewManager).forEach(key => { - const value = viewManager[key]; - if (typeof value !== 'function') { - if (!viewConfig.Constants) { - viewConfig.Constants = {}; - } - viewConfig.Constants[key] = value; - } - }); - } - }); - } - /** * Converts the old, object-based module structure to the new * array-based structure. TODO (t8823865) Removed this @@ -269,55 +249,62 @@ class MessageQueue { } _genLookupTables(modulesConfig, moduleTable, methodTable) { - modulesConfig.forEach((module, moduleID) => { - if (!module) { - return; - } - - let moduleName, methods; - if (moduleHasConstants(module)) { - [moduleName, , methods] = module; - } else { - [moduleName, methods] = module; - } - - moduleTable[moduleID] = moduleName; - methodTable[moduleID] = Object.assign({}, methods); + modulesConfig.forEach((config, moduleID) => { + this._genLookup(config, moduleID, moduleTable, methodTable); }); } + + _genLookup(config, moduleID, moduleTable, methodTable) { + if (!config) { + return; + } + + let moduleName, methods; + if (moduleHasConstants(config)) { + [moduleName, , methods] = config; + } else { + [moduleName, methods] = config; + } + + moduleTable[moduleID] = moduleName; + methodTable[moduleID] = Object.assign({}, methods); + } _genModules(remoteModules) { - remoteModules.forEach((module, moduleID) => { - if (!module) { - return; - } - - let moduleName, constants, methods, asyncMethods; - if (moduleHasConstants(module)) { - [moduleName, constants, methods, asyncMethods] = module; - } else { - [moduleName, methods, asyncMethods] = module; - } - - const moduleConfig = {moduleID, constants, methods, asyncMethods}; - this.RemoteModules[moduleName] = this._genModule({}, moduleConfig); + remoteModules.forEach((config, moduleID) => { + this._genModule(config, moduleID); }); } - _genModule(module, moduleConfig) { - const {moduleID, constants, methods = [], asyncMethods = []} = moduleConfig; + _genModule(config, moduleID) { + if (!config) { + return; + } - methods.forEach((methodName, methodID) => { + let moduleName, constants, methods, asyncMethods; + if (moduleHasConstants(config)) { + [moduleName, constants, methods, asyncMethods] = config; + } else { + [moduleName, methods, asyncMethods] = config; + } + + let module = {}; + methods && methods.forEach((methodName, methodID) => { const methodType = - arrayContains(asyncMethods, methodID) ? + asyncMethods && arrayContains(asyncMethods, methodID) ? MethodTypes.remoteAsync : MethodTypes.remote; module[methodName] = this._genMethod(moduleID, methodID, methodType); }); Object.assign(module, constants); - + + if (!constants && !methods && !asyncMethods) { + module.moduleID = moduleID; + } + + this.RemoteModules[moduleName] = module; return module; } - + _genMethod(module, method, type) { let fn = null; let self = this; diff --git a/Libraries/Utilities/nativeModulePrefixNormalizer.js b/Libraries/Utilities/nativeModulePrefixNormalizer.js deleted file mode 100644 index 151417bc5..000000000 --- a/Libraries/Utilities/nativeModulePrefixNormalizer.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * 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. - * - * @providesModule nativeModulePrefixNormalizer - * @flow - */ -'use strict'; - -// Dirty hack to support old (RK) and new (RCT) native module name conventions -function nativeModulePrefixNormalizer( - modules: {[key: string]: any} -): void { - Object.keys(modules).forEach((moduleName) => { - var strippedName = moduleName.replace(/^(RCT|RK)/, ''); - if (modules['RCT' + strippedName] && modules['RK' + strippedName]) { - throw new Error( - 'Module cannot be registered as both RCT and RK: ' + moduleName - ); - } - if (strippedName !== moduleName) { - modules[strippedName] = modules[moduleName]; - delete modules[moduleName]; - } - }); -} - -module.exports = nativeModulePrefixNormalizer; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index b3b290d72..a0190a0ae 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -128,7 +128,7 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); dispatch_group_leave(initModulesAndLoadSource); }]; - // Synchronously initialize all native modules that cannot be deferred + // Synchronously initialize all native modules that cannot be loaded lazily [self initModules]; #if RCT_DEBUG @@ -236,16 +236,21 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); - (id)moduleForName:(NSString *)moduleName { RCTModuleData *moduleData = _moduleDataByName[moduleName]; - if (RCT_DEBUG) { - Class moduleClass = moduleData.moduleClass; - if (!RCTBridgeModuleClassIsRegistered(moduleClass)) { - RCTLogError(@"Class %@ was not exported. Did you forget to use " - "RCT_EXPORT_MODULE()?", moduleClass); - } - } return moduleData.instance; } +- (NSArray *)configForModuleName:(NSString *)moduleName +{ + RCTModuleData *moduleData = _moduleDataByName[moduleName]; + if (!moduleData) { + moduleData = _moduleDataByName[[@"RCT" stringByAppendingString:moduleName]]; + } + if (moduleData) { + return moduleData.config; + } + return (id)kCFNull; +} + - (void)initModules { RCTAssertMainThread(); @@ -344,21 +349,29 @@ RCT_EXTERN NSArray *RCTGetModuleClasses(void); [_javaScriptExecutor setUp]; } +- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData +{ + if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { + [_frameUpdateObservers addObject:moduleData]; + id observer = (id)moduleData.instance; + __weak typeof(self) weakSelf = self; + __weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor; + observer.pauseCallback = ^{ + [weakJavaScriptExecutor executeBlockOnJavaScriptQueue:^{ + [weakSelf updateJSDisplayLinkState]; + }]; + }; + } +} + - (NSString *)moduleConfig { NSMutableArray *config = [NSMutableArray new]; for (RCTModuleData *moduleData in _moduleDataByID) { - [config addObject:RCTNullIfNil(moduleData.config)]; - if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { - [_frameUpdateObservers addObject:moduleData]; - id observer = (id)moduleData.instance; - __weak typeof(self) weakSelf = self; - __weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor; - observer.pauseCallback = ^{ - [weakJavaScriptExecutor executeBlockOnJavaScriptQueue:^{ - [weakSelf updateJSDisplayLinkState]; - }]; - }; + if (self.executorClass == [RCTContextExecutor class]) { + [config addObject:@[moduleData.name]]; + } else { + [config addObject:RCTNullIfNil(moduleData.config)]; } } diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index 011159db8..b8fa17049 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -14,6 +14,12 @@ #import "RCTLog.h" #import "RCTUtils.h" +@interface RCTBridge (Private) + +- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData; + +@end + @implementation RCTModuleData { NSString *_queueName; @@ -88,6 +94,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init); "or provide your own setter method.", self.name); } } + [bridge registerModuleForFrameUpdates:self]; } - (NSString *)name diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 3c22398d5..d37f6c7b9 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -98,10 +98,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init) @end -// Private bridge interface to allow middle-batch calls +// Private bridge interface @interface RCTBridge (RCTContextExecutor) - (void)handleBuffer:(NSArray *)buffer batchEnded:(BOOL)hasEnded; +- (NSArray *)configForModuleName:(NSString *)moduleName; @end @@ -351,13 +352,24 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"]; [strongSelf _addNativeHook:RCTNoop withName:"noop"]; - __weak RCTBridge *bridge = strongSelf->_bridge; + __weak RCTBridge *weakBridge = strongSelf->_bridge; + + strongSelf->_context.context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) { + if (!weakSelf.valid) { + return nil; + } + NSArray *config = [weakBridge configForModuleName:moduleName]; + if (config) { + return RCTJSONStringify(config, NULL); + } + return nil; + }; + strongSelf->_context.context[@"nativeFlushQueueImmediate"] = ^(NSArray *calls){ if (!weakSelf.valid || !calls) { return; } - - [bridge handleBuffer:calls batchEnded:NO]; + [weakBridge handleBuffer:calls batchEnded:NO]; }; strongSelf->_context.context[@"RCTPerformanceNow"] = ^{ @@ -365,6 +377,7 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) }; #if RCT_DEV + if (RCTProfileIsProfiling()) { strongSelf->_context.context[@"__RCTProfileIsProfiling"] = @YES; } @@ -394,7 +407,9 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) name:event object:nil]; } + #endif + }]; } @@ -417,7 +432,6 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) JSStringRef JSName = JSStringCreateWithUTF8CString(name); JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL); JSStringRelease(JSName); - } - (void)invalidate