Added deferred module loading feature
Reviewed By: javache Differential Revision: D2717687 fb-gh-sync-id: 4c03c721c794a2e7ac67a0b20474129fde7f0a0d
This commit is contained in:
parent
5775d9e1d0
commit
d138403c2e
|
@ -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;
|
||||
|
|
|
@ -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,52 +249,59 @@ class MessageQueue {
|
|||
}
|
||||
|
||||
_genLookupTables(modulesConfig, moduleTable, methodTable) {
|
||||
modulesConfig.forEach((module, moduleID) => {
|
||||
if (!module) {
|
||||
modulesConfig.forEach((config, moduleID) => {
|
||||
this._genLookup(config, moduleID, moduleTable, methodTable);
|
||||
});
|
||||
}
|
||||
|
||||
_genLookup(config, moduleID, moduleTable, methodTable) {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let moduleName, methods;
|
||||
if (moduleHasConstants(module)) {
|
||||
[moduleName, , methods] = module;
|
||||
if (moduleHasConstants(config)) {
|
||||
[moduleName, , methods] = config;
|
||||
} else {
|
||||
[moduleName, methods] = module;
|
||||
[moduleName, methods] = config;
|
||||
}
|
||||
|
||||
moduleTable[moduleID] = moduleName;
|
||||
methodTable[moduleID] = Object.assign({}, methods);
|
||||
});
|
||||
}
|
||||
|
||||
_genModules(remoteModules) {
|
||||
remoteModules.forEach((module, moduleID) => {
|
||||
if (!module) {
|
||||
remoteModules.forEach((config, moduleID) => {
|
||||
this._genModule(config, moduleID);
|
||||
});
|
||||
}
|
||||
|
||||
_genModule(config, moduleID) {
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
let moduleName, constants, methods, asyncMethods;
|
||||
if (moduleHasConstants(module)) {
|
||||
[moduleName, constants, methods, asyncMethods] = module;
|
||||
if (moduleHasConstants(config)) {
|
||||
[moduleName, constants, methods, asyncMethods] = config;
|
||||
} else {
|
||||
[moduleName, methods, asyncMethods] = module;
|
||||
[moduleName, methods, asyncMethods] = config;
|
||||
}
|
||||
|
||||
const moduleConfig = {moduleID, constants, methods, asyncMethods};
|
||||
this.RemoteModules[moduleName] = this._genModule({}, moduleConfig);
|
||||
});
|
||||
}
|
||||
|
||||
_genModule(module, moduleConfig) {
|
||||
const {moduleID, constants, methods = [], asyncMethods = []} = moduleConfig;
|
||||
|
||||
methods.forEach((methodName, methodID) => {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -128,7 +128,7 @@ RCT_EXTERN NSArray<Class> *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<Class> *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,11 +349,8 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
|
|||
[_javaScriptExecutor setUp];
|
||||
}
|
||||
|
||||
- (NSString *)moduleConfig
|
||||
- (void)registerModuleForFrameUpdates:(RCTModuleData *)moduleData
|
||||
{
|
||||
NSMutableArray<NSArray *> *config = [NSMutableArray new];
|
||||
for (RCTModuleData *moduleData in _moduleDataByID) {
|
||||
[config addObject:RCTNullIfNil(moduleData.config)];
|
||||
if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
|
||||
[_frameUpdateObservers addObject:moduleData];
|
||||
id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
|
||||
|
@ -362,6 +364,17 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
|
|||
}
|
||||
}
|
||||
|
||||
- (NSString *)moduleConfig
|
||||
{
|
||||
NSMutableArray<NSArray *> *config = [NSMutableArray new];
|
||||
for (RCTModuleData *moduleData in _moduleDataByID) {
|
||||
if (self.executorClass == [RCTContextExecutor class]) {
|
||||
[config addObject:@[moduleData.name]];
|
||||
} else {
|
||||
[config addObject:RCTNullIfNil(moduleData.config)];
|
||||
}
|
||||
}
|
||||
|
||||
return RCTJSONStringify(@{
|
||||
@"remoteModuleConfig": config,
|
||||
}, NULL);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<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<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
|
||||
|
|
Loading…
Reference in New Issue