mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-13 17:26:34 +08:00
Decouple Module System from Native Calls
Summary: The JavaScript ecosystem doesn't have the notion of a built-in native module loader. Even Node is decoupled from its module loader. The module loader system is just JS that runs on top of the global `process` object which has all the built-in goodies. Additionally there is no such thing as a global require. That is something unique to our providesModule system. In other module systems such as node, every require is contextual. Even registered npm names are localized by version. The only global namespace that is accessible to the host environment is the global object. Normally module systems attaches itself onto the hooks provided by the host environment on the global object. Currently, we have two forms of dispatch that reaches directly into the module system. executeJSCall which reaches directly into require. Everything now calls through the BatchedBridge module (except one RCTLog edge case that I will fix). I propose that the executors calls directly onto `BatchedBridge` through an instance on the global so that everything is guaranteed to go through it. It becomes the main communication hub. I also propose that we drop the dynamic requires inside of MessageQueue/BatchBridge and instead have the modules register themselves with the bridge. executeJSCall was originally modeled after the XHP equivalent. The XHP equivalent was designed that way because the act of doing the call was the thing that defined a dependency on the module from the page. However, that is not how React Native works. The JS side is driving the dependencies by virtue of requiring new modules and frameworks and the existence of dependencies is driven by the JS side, so this design doesn't make as much sense. The main driver for this is to be able to introduce a new module system like Prepack's module system. However, it also unlocks the possibility to do dead module elimination even in our current module system. It is currently not possible because we don't know which module might be called from native. Since the module system now becomes decoupled we could publish all our providesModule modules as npm/CommonJS modules using a rewrite script. That's what React Core does. That way people could use any CommonJS bundler such as Webpack, Closure Compiler, Rollup or some new innovation to create a JS bundle. This diff expands the executeJSCalls to the BatchedBridge's three individual pieces to make them first class instead of being dynamic. This removes one layer of abstraction. Hopefully we can also remove more of the things that register themselves with the BatchedBridge (various EventEmitters) and instead have everything go through the public protocol. ReactMethod/RCT_EXPORT_METHOD. public Reviewed By: vjeux Differential Revision: D2717535 fb-gh-sync-id: 70114f05483124f5ac5c4570422bb91a60a727f6
This commit is contained in:
committed by
facebook-github-bot-5
parent
99bba8ca4e
commit
8d397b4cbc
@@ -11,6 +11,9 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
var ReactNative = require('ReactNative');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var renderApplication = require('renderApplication');
|
||||
|
||||
@@ -37,6 +40,10 @@ type AppConfig = {
|
||||
* for the app and then actually run the app when it's ready by invoking
|
||||
* `AppRegistry.runApplication`.
|
||||
*
|
||||
* To "stop" an application when a view should be destroyed, call
|
||||
* `AppRegistry.unmountApplicationComponentAtRootTag` with the tag that was
|
||||
* pass into `runApplication`. These should always be used as a pair.
|
||||
*
|
||||
* `AppRegistry` should be `require`d early in the `require` sequence to make
|
||||
* sure the JS execution environment is setup before other modules are
|
||||
* `require`d.
|
||||
@@ -87,6 +94,16 @@ var AppRegistry = {
|
||||
);
|
||||
runnables[appKey].run(appParameters);
|
||||
},
|
||||
|
||||
unmountApplicationComponentAtRootTag: function(rootTag : number) {
|
||||
ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'AppRegistry',
|
||||
AppRegistry
|
||||
);
|
||||
|
||||
module.exports = AppRegistry;
|
||||
|
||||
@@ -10,11 +10,27 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
let MessageQueue = require('MessageQueue');
|
||||
const MessageQueue = require('MessageQueue');
|
||||
|
||||
let BatchedBridge = new MessageQueue(
|
||||
const BatchedBridge = new MessageQueue(
|
||||
__fbBatchedBridgeConfig.remoteModuleConfig,
|
||||
__fbBatchedBridgeConfig.localModulesConfig,
|
||||
);
|
||||
|
||||
// TODO: Move these around to solve the cycle in a cleaner way.
|
||||
|
||||
const BridgeProfiling = require('BridgeProfiling');
|
||||
const JSTimersExecution = require('JSTimersExecution');
|
||||
|
||||
BatchedBridge.registerCallableModule('BridgeProfiling', BridgeProfiling);
|
||||
BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);
|
||||
|
||||
// Wire up the batched bridge on the global object so that we can call into it.
|
||||
// Ideally, this would be the inverse relationship. I.e. the native environment
|
||||
// provides this global directly with its script embedded. Then this module
|
||||
// would export it. A possible fix would be to trim the dependencies in
|
||||
// MessageQueue to its minimal features and embed that in the native runtime.
|
||||
|
||||
Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });
|
||||
|
||||
module.exports = BatchedBridge;
|
||||
|
||||
@@ -11,7 +11,13 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTEventEmitter',
|
||||
ReactNativeEventEmitter
|
||||
);
|
||||
|
||||
// Completely locally implemented - no native hooks.
|
||||
module.exports = ReactNativeEventEmitter;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
var DebugComponentOwnershipModule = require('NativeModules').DebugComponentOwnershipModule;
|
||||
var InspectorUtils = require('InspectorUtils');
|
||||
var ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
@@ -35,7 +36,7 @@ function getRootTagForTag(tag: number): ?number {
|
||||
return ReactNativeTagHandles.rootNodeIDToTag[rootID];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
var RCTDebugComponentOwnership = {
|
||||
|
||||
/**
|
||||
* Asynchronously returns the owner hierarchy as an array of strings. Request id is
|
||||
@@ -53,3 +54,10 @@ module.exports = {
|
||||
DebugComponentOwnershipModule.receiveOwnershipHierarchy(requestID, tag, ownerHierarchy);
|
||||
},
|
||||
};
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTDebugComponentOwnership',
|
||||
RCTDebugComponentOwnership
|
||||
);
|
||||
|
||||
module.exports = RCTDebugComponentOwnership;
|
||||
|
||||
@@ -12,7 +12,13 @@
|
||||
'use strict';
|
||||
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
|
||||
var RCTDeviceEventEmitter = new EventEmitter();
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTDeviceEventEmitter',
|
||||
RCTDeviceEventEmitter
|
||||
);
|
||||
|
||||
module.exports = RCTDeviceEventEmitter;
|
||||
|
||||
@@ -11,8 +11,14 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
var EventEmitter = require('EventEmitter');
|
||||
|
||||
var RCTNativeAppEventEmitter = new EventEmitter();
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTNativeAppEventEmitter',
|
||||
RCTNativeAppEventEmitter
|
||||
);
|
||||
|
||||
module.exports = RCTNativeAppEventEmitter;
|
||||
|
||||
@@ -43,10 +43,10 @@ var guard = (fn) => {
|
||||
|
||||
class MessageQueue {
|
||||
|
||||
constructor(remoteModules, localModules, customRequire) {
|
||||
constructor(remoteModules, localModules) {
|
||||
this.RemoteModules = {};
|
||||
|
||||
this._require = customRequire || require;
|
||||
this._callableModules = {};
|
||||
this._queue = [[],[],[]];
|
||||
this._moduleTable = {};
|
||||
this._methodTable = {};
|
||||
@@ -154,8 +154,22 @@ class MessageQueue {
|
||||
if (__DEV__ && SPY_MODE) {
|
||||
console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
|
||||
}
|
||||
module = this._require(module);
|
||||
module[method].apply(module, args);
|
||||
var moduleMethods = this._callableModules[module];
|
||||
if (!moduleMethods) {
|
||||
// TODO: Register all the remaining test modules and make this an invariant. #9317773
|
||||
// Fallback to require temporarily. A follow up diff will clean up the remaining
|
||||
// modules and make this an invariant.
|
||||
console.warn('Module is not registered:', module);
|
||||
moduleMethods = require(module);
|
||||
/*
|
||||
invariant(
|
||||
!!moduleMethods,
|
||||
'Module %s is not a registered callable module.',
|
||||
module
|
||||
);
|
||||
*/
|
||||
}
|
||||
moduleMethods[method].apply(moduleMethods, args);
|
||||
BridgeProfiling.profileEnd();
|
||||
}
|
||||
|
||||
@@ -337,6 +351,10 @@ class MessageQueue {
|
||||
return fn;
|
||||
}
|
||||
|
||||
registerCallableModule(name, methods) {
|
||||
this._callableModules[name] = methods;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function moduleHasConstants(moduleArray: Array<Object|Array<>>): boolean {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
|
||||
var performanceNow = require('performanceNow');
|
||||
|
||||
@@ -140,4 +141,9 @@ var PerformanceLogger = {
|
||||
}
|
||||
};
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'PerformanceLogger',
|
||||
PerformanceLogger
|
||||
);
|
||||
|
||||
module.exports = PerformanceLogger;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var BatchedBridge = require('BatchedBridge');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var levelsMap = {
|
||||
@@ -39,4 +41,9 @@ class RCTLog {
|
||||
}
|
||||
}
|
||||
|
||||
BatchedBridge.registerCallableModule(
|
||||
'RCTLog',
|
||||
RCTLog
|
||||
);
|
||||
|
||||
module.exports = RCTLog;
|
||||
|
||||
@@ -39,10 +39,11 @@ describe('MessageQueue', () => {
|
||||
beforeEach(() => {
|
||||
queue = new MessageQueue(
|
||||
remoteModulesConfig,
|
||||
localModulesConfig,
|
||||
customRequire,
|
||||
localModulesConfig
|
||||
);
|
||||
|
||||
queue.registerCallableModule('one', TestModule);
|
||||
|
||||
TestModule.testHook1 = jasmine.createSpy();
|
||||
TestModule.testHook2 = jasmine.createSpy();
|
||||
});
|
||||
|
||||
@@ -161,13 +161,31 @@ RCT_EXPORT_MODULE()
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
|
||||
- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
[self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete];
|
||||
}
|
||||
|
||||
- (void)callFunctionOnModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)args
|
||||
callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
[self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete];
|
||||
}
|
||||
|
||||
- (void)invokeCallbackID:(NSNumber *)cbID
|
||||
arguments:(NSArray *)args
|
||||
callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
[self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete];
|
||||
}
|
||||
|
||||
- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
|
||||
NSDictionary<NSString *, id> *message = @{
|
||||
@"method": @"executeJSCall",
|
||||
@"moduleName": name,
|
||||
@"moduleMethod": method,
|
||||
@"method": method,
|
||||
@"arguments": arguments
|
||||
};
|
||||
[self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary<NSString *, id> *reply) {
|
||||
|
||||
Reference in New Issue
Block a user