react-native-macos/Libraries/ReactNative/AppRegistry.js

354 строки
10 KiB
JavaScript
Исходник Обычный вид История

2015-01-30 04:10:49 +03:00
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
2015-01-30 04:10:49 +03:00
*
2015-03-25 05:34:12 +03:00
* @flow
* @format
2015-01-30 04:10:49 +03:00
*/
const BatchedBridge = require('../BatchedBridge/BatchedBridge');
const BugReporting = require('../BugReporting/BugReporting');
const ReactNative = require('../Renderer/shims/ReactNative');
const SceneTracker = require('../Utilities/SceneTracker');
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
2015-12-09 02:57:34 +03:00
const infoLog = require('../Utilities/infoLog');
const invariant = require('invariant');
const renderApplication = require('./renderApplication');
import type {IPerformanceLogger} from '../Utilities/createPerformanceLogger';
import {coerceDisplayMode} from './DisplayMode';
import createPerformanceLogger from '../Utilities/createPerformanceLogger';
import NativeHeadlessJsTaskSupport from './NativeHeadlessJsTaskSupport';
Allow headless JS tasks to retry (#23231) Summary: `setTimeout` inside a headless JS task does not always works; the function does not get invoked until the user starts an `Activity`. This was attempted to be used in the context of widgets. When the widget update or user interaction causes the process and React context to be created, the headless JS task may run before other app-specific JS initialisation logic has completed. If it's not possible to change the behaviour of the pre-requisites to be synchronous, then the headless JS task blocks such asynchronous JS work that it may depend on. A primitive solution is the use of `setTimeout` in order to wait for the pre-conditions to be met before continuing with the rest of the headless JS task. But as the function passed to `setTimeout` is not always called, the task will not run to completion. This PR solves this scenario by allowing the task to be retried again with a delay. If the task returns a promise that resolves to a `{'timeout': number}` object, `AppRegistry.js` will not notify that the task has finished as per master, instead it will tell `HeadlessJsContext` to `startTask` again (cleaning up any posted `Runnable`s beforehand) via a `Handler` within the `HeadlessJsContext`. Documentation also updated here: https://github.com/facebook/react-native-website/pull/771 ### AppRegistry.js If the task provider does not return any data, or if the data it returns does not contain `timeout` as a number, then it behaves as `master`; notifies that the task has finished. If the response does contain `{timeout: number}`, then it will attempt to queue a retry. If that fails, then it will behaves as if the task provider returned no response i.e. behaves as `master` again. If the retry was successfully queued, then there is nothing to do as we do not want the `Service` to stop itself. ### HeadlessJsTaskSupportModule.java Similar to notify start/finished, we simply check if the context is running, and if so, pass the request onto `HeadlessJsTaskContext`. The only difference here is that we return a `Promise`, so that `AppRegistry`, as above, knows whether the enqueuing failed and thus needs to perform the usual task clean-up. ### HeadlessJsTaskContext.java Before retrying, we need to clean-up any timeout `Runnable`'s posted for the first attempt. Then we need to copy the task config so that if this retry (second attempt) also fails, then on the third attempt (second retry) we do not run into a consumed exception. This is also why in `startTask` we copy the config before putting it in the `Map`, so that the initial attempt does leave the config's in the map as consumed. Then we post a `Runnable` to call `startTask` on the main thread's `Handler`. We use the same `taskId` because the `Service` is keeping track of active task IDs in order to calculate whether it needs to `stopSelf`. This negates the need to inform the `Service` of a new task id and us having to remove the old one. ## Changelog [Android][added] - Allow headless JS tasks to return a promise that will cause the task to be retried again with the specified delay Pull Request resolved: https://github.com/facebook/react-native/pull/23231 Differential Revision: D15646870 fbshipit-source-id: 4440f4b4392f1fa5c69aab7908b51b7007ba2c40
2019-06-06 21:54:08 +03:00
import HeadlessJsTaskError from './HeadlessJsTaskError';
import type {RootTag} from 'react-native/Libraries/Types/RootTagTypes';
type Task = (taskData: any) => Promise<void>;
export type TaskProvider = () => Task;
type TaskCanceller = () => void;
type TaskCancelProvider = () => TaskCanceller;
export type ComponentProvider = () => React$ComponentType<any>;
export type ComponentProviderInstrumentationHook = (
component: ComponentProvider,
scopedPerformanceLogger: IPerformanceLogger,
) => React$ComponentType<any>;
export type AppConfig = {
appKey: string,
component?: ComponentProvider,
run?: Function,
section?: boolean,
...
};
export type Runnable = {
component?: ComponentProvider,
run: Function,
...
};
export type Runnables = {[appKey: string]: Runnable, ...};
export type Registry = {
sections: Array<string>,
runnables: Runnables,
...
};
export type WrapperComponentProvider = any => React$ComponentType<any>;
2015-03-25 05:34:12 +03:00
const runnables: Runnables = {};
let runCount = 1;
const sections: Runnables = {};
const taskProviders: Map<string, TaskProvider> = new Map();
const taskCancelProviders: Map<string, TaskCancelProvider> = new Map();
let componentProviderInstrumentationHook: ComponentProviderInstrumentationHook =
(component: ComponentProvider) => component();
let wrapperComponentProvider: ?WrapperComponentProvider;
let showArchitectureIndicator = false;
/**
* `AppRegistry` is the JavaScript entry point to running all React Native apps.
*
* See https://reactnative.dev/docs/appregistry
*/
const AppRegistry = {
setWrapperComponentProvider(provider: WrapperComponentProvider) {
wrapperComponentProvider = provider;
},
enableArchitectureIndicator(enabled: boolean): void {
showArchitectureIndicator = enabled;
},
registerConfig(config: Array<AppConfig>): void {
config.forEach(appConfig => {
2015-03-25 05:34:12 +03:00
if (appConfig.run) {
AppRegistry.registerRunnable(appConfig.appKey, appConfig.run);
2015-01-30 04:10:49 +03:00
} else {
invariant(
appConfig.component != null,
'AppRegistry.registerConfig(...): Every config is expected to set ' +
'either `run` or `component`, but `%s` has neither.',
appConfig.appKey,
);
AppRegistry.registerComponent(
appConfig.appKey,
appConfig.component,
appConfig.section,
);
2015-01-30 04:10:49 +03:00
}
});
},
2015-01-30 04:10:49 +03:00
/**
* Registers an app's root component.
*
* See https://reactnative.dev/docs/appregistry#registercomponent
*/
registerComponent(
appKey: string,
componentProvider: ComponentProvider,
section?: boolean,
): string {
let scopedPerformanceLogger = createPerformanceLogger();
2015-01-30 04:10:49 +03:00
runnables[appKey] = {
componentProvider,
run: (appParameters, displayMode) => {
renderApplication(
componentProviderInstrumentationHook(
componentProvider,
scopedPerformanceLogger,
),
appParameters.initialProps,
appParameters.rootTag,
wrapperComponentProvider && wrapperComponentProvider(appParameters),
appParameters.fabric,
showArchitectureIndicator,
scopedPerformanceLogger,
appKey === 'LogBox',
appKey,
coerceDisplayMode(displayMode),
appParameters.concurrentRoot,
);
},
2015-01-30 04:10:49 +03:00
};
if (section) {
sections[appKey] = runnables[appKey];
}
2015-01-30 04:10:49 +03:00
return appKey;
},
2015-01-30 04:10:49 +03:00
registerRunnable(appKey: string, run: Function): string {
runnables[appKey] = {run};
2015-01-30 04:10:49 +03:00
return appKey;
},
2015-01-30 04:10:49 +03:00
registerSection(appKey: string, component: ComponentProvider): void {
AppRegistry.registerComponent(appKey, component, true);
},
getAppKeys(): Array<string> {
return Object.keys(runnables);
},
getSectionKeys(): Array<string> {
return Object.keys(sections);
},
getSections(): Runnables {
return {
...sections,
};
},
getRunnable(appKey: string): ?Runnable {
return runnables[appKey];
},
getRegistry(): Registry {
return {
sections: AppRegistry.getSectionKeys(),
runnables: {...runnables},
};
},
setComponentProviderInstrumentationHook(
hook: ComponentProviderInstrumentationHook,
) {
componentProviderInstrumentationHook = hook;
},
/**
* Loads the JavaScript bundle and runs the app.
*
* See https://reactnative.dev/docs/appregistry#runapplication
*/
runApplication(
appKey: string,
appParameters: any,
displayMode?: number,
): void {
if (appKey !== 'LogBox') {
const logParams = __DEV__
? '" with ' + JSON.stringify(appParameters)
: '';
const msg = 'Running "' + appKey + logParams;
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
}
2015-01-30 04:10:49 +03:00
invariant(
runnables[appKey] && runnables[appKey].run,
`"${appKey}" has not been registered. This can happen if:\n` +
'* Metro (the local dev server) is run from the wrong folder. ' +
'Check if Metro is running, stop it and restart it in the current project.\n' +
"* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.",
2015-01-30 04:10:49 +03:00
);
SceneTracker.setActiveScene({name: appKey});
runnables[appKey].run(appParameters, displayMode);
},
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
2015-12-09 02:57:34 +03:00
/**
* Update initial props for a surface that's already rendered
*/
setSurfaceProps(
appKey: string,
appParameters: any,
displayMode?: number,
): void {
if (appKey !== 'LogBox') {
const msg =
'Updating props for Surface "' +
appKey +
'" with ' +
JSON.stringify(appParameters);
infoLog(msg);
BugReporting.addSource(
'AppRegistry.setSurfaceProps' + runCount++,
() => msg,
);
}
invariant(
runnables[appKey] && runnables[appKey].run,
`"${appKey}" has not been registered. This can happen if:\n` +
'* Metro (the local dev server) is run from the wrong folder. ' +
'Check if Metro is running, stop it and restart it in the current project.\n' +
"* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.",
);
runnables[appKey].run(appParameters, displayMode);
},
/**
* Stops an application when a view should be destroyed.
*
* See https://reactnative.dev/docs/appregistry#unmountapplicationcomponentatroottag
*/
unmountApplicationComponentAtRootTag(rootTag: RootTag): void {
// NOTE: RootTag type
// $FlowFixMe[incompatible-call] RootTag: RootTag is incompatible with number, needs an updated synced version of the ReactNativeTypes.js file
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
2015-12-09 02:57:34 +03:00
ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag);
},
/**
* Register a headless task. A headless task is a bit of code that runs without a UI.
*
* See https://reactnative.dev/docs/appregistry#registerheadlesstask
*/
registerHeadlessTask(taskKey: string, taskProvider: TaskProvider): void {
// $FlowFixMe[object-this-reference]
this.registerCancellableHeadlessTask(taskKey, taskProvider, () => () => {
/* Cancel is no-op */
});
},
/**
* Register a cancellable headless task. A headless task is a bit of code that runs without a UI.
*
* See https://reactnative.dev/docs/appregistry#registercancellableheadlesstask
*/
registerCancellableHeadlessTask(
taskKey: string,
taskProvider: TaskProvider,
taskCancelProvider: TaskCancelProvider,
): void {
if (taskProviders.has(taskKey)) {
console.warn(
`registerHeadlessTask or registerCancellableHeadlessTask called multiple times for same key '${taskKey}'`,
);
}
taskProviders.set(taskKey, taskProvider);
taskCancelProviders.set(taskKey, taskCancelProvider);
},
/**
* Only called from native code. Starts a headless task.
*
* See https://reactnative.dev/docs/appregistry#startheadlesstask
*/
startHeadlessTask(taskId: number, taskKey: string, data: any): void {
const taskProvider = taskProviders.get(taskKey);
if (!taskProvider) {
console.warn(`No task registered for key ${taskKey}`);
if (NativeHeadlessJsTaskSupport) {
NativeHeadlessJsTaskSupport.notifyTaskFinished(taskId);
}
return;
}
taskProvider()(data)
.then(() => {
if (NativeHeadlessJsTaskSupport) {
NativeHeadlessJsTaskSupport.notifyTaskFinished(taskId);
}
})
.catch(reason => {
console.error(reason);
Allow headless JS tasks to retry (#23231) Summary: `setTimeout` inside a headless JS task does not always works; the function does not get invoked until the user starts an `Activity`. This was attempted to be used in the context of widgets. When the widget update or user interaction causes the process and React context to be created, the headless JS task may run before other app-specific JS initialisation logic has completed. If it's not possible to change the behaviour of the pre-requisites to be synchronous, then the headless JS task blocks such asynchronous JS work that it may depend on. A primitive solution is the use of `setTimeout` in order to wait for the pre-conditions to be met before continuing with the rest of the headless JS task. But as the function passed to `setTimeout` is not always called, the task will not run to completion. This PR solves this scenario by allowing the task to be retried again with a delay. If the task returns a promise that resolves to a `{'timeout': number}` object, `AppRegistry.js` will not notify that the task has finished as per master, instead it will tell `HeadlessJsContext` to `startTask` again (cleaning up any posted `Runnable`s beforehand) via a `Handler` within the `HeadlessJsContext`. Documentation also updated here: https://github.com/facebook/react-native-website/pull/771 ### AppRegistry.js If the task provider does not return any data, or if the data it returns does not contain `timeout` as a number, then it behaves as `master`; notifies that the task has finished. If the response does contain `{timeout: number}`, then it will attempt to queue a retry. If that fails, then it will behaves as if the task provider returned no response i.e. behaves as `master` again. If the retry was successfully queued, then there is nothing to do as we do not want the `Service` to stop itself. ### HeadlessJsTaskSupportModule.java Similar to notify start/finished, we simply check if the context is running, and if so, pass the request onto `HeadlessJsTaskContext`. The only difference here is that we return a `Promise`, so that `AppRegistry`, as above, knows whether the enqueuing failed and thus needs to perform the usual task clean-up. ### HeadlessJsTaskContext.java Before retrying, we need to clean-up any timeout `Runnable`'s posted for the first attempt. Then we need to copy the task config so that if this retry (second attempt) also fails, then on the third attempt (second retry) we do not run into a consumed exception. This is also why in `startTask` we copy the config before putting it in the `Map`, so that the initial attempt does leave the config's in the map as consumed. Then we post a `Runnable` to call `startTask` on the main thread's `Handler`. We use the same `taskId` because the `Service` is keeping track of active task IDs in order to calculate whether it needs to `stopSelf`. This negates the need to inform the `Service` of a new task id and us having to remove the old one. ## Changelog [Android][added] - Allow headless JS tasks to return a promise that will cause the task to be retried again with the specified delay Pull Request resolved: https://github.com/facebook/react-native/pull/23231 Differential Revision: D15646870 fbshipit-source-id: 4440f4b4392f1fa5c69aab7908b51b7007ba2c40
2019-06-06 21:54:08 +03:00
if (
NativeHeadlessJsTaskSupport &&
reason instanceof HeadlessJsTaskError
) {
NativeHeadlessJsTaskSupport.notifyTaskRetry(taskId).then(
retryPosted => {
Allow headless JS tasks to retry (#23231) Summary: `setTimeout` inside a headless JS task does not always works; the function does not get invoked until the user starts an `Activity`. This was attempted to be used in the context of widgets. When the widget update or user interaction causes the process and React context to be created, the headless JS task may run before other app-specific JS initialisation logic has completed. If it's not possible to change the behaviour of the pre-requisites to be synchronous, then the headless JS task blocks such asynchronous JS work that it may depend on. A primitive solution is the use of `setTimeout` in order to wait for the pre-conditions to be met before continuing with the rest of the headless JS task. But as the function passed to `setTimeout` is not always called, the task will not run to completion. This PR solves this scenario by allowing the task to be retried again with a delay. If the task returns a promise that resolves to a `{'timeout': number}` object, `AppRegistry.js` will not notify that the task has finished as per master, instead it will tell `HeadlessJsContext` to `startTask` again (cleaning up any posted `Runnable`s beforehand) via a `Handler` within the `HeadlessJsContext`. Documentation also updated here: https://github.com/facebook/react-native-website/pull/771 ### AppRegistry.js If the task provider does not return any data, or if the data it returns does not contain `timeout` as a number, then it behaves as `master`; notifies that the task has finished. If the response does contain `{timeout: number}`, then it will attempt to queue a retry. If that fails, then it will behaves as if the task provider returned no response i.e. behaves as `master` again. If the retry was successfully queued, then there is nothing to do as we do not want the `Service` to stop itself. ### HeadlessJsTaskSupportModule.java Similar to notify start/finished, we simply check if the context is running, and if so, pass the request onto `HeadlessJsTaskContext`. The only difference here is that we return a `Promise`, so that `AppRegistry`, as above, knows whether the enqueuing failed and thus needs to perform the usual task clean-up. ### HeadlessJsTaskContext.java Before retrying, we need to clean-up any timeout `Runnable`'s posted for the first attempt. Then we need to copy the task config so that if this retry (second attempt) also fails, then on the third attempt (second retry) we do not run into a consumed exception. This is also why in `startTask` we copy the config before putting it in the `Map`, so that the initial attempt does leave the config's in the map as consumed. Then we post a `Runnable` to call `startTask` on the main thread's `Handler`. We use the same `taskId` because the `Service` is keeping track of active task IDs in order to calculate whether it needs to `stopSelf`. This negates the need to inform the `Service` of a new task id and us having to remove the old one. ## Changelog [Android][added] - Allow headless JS tasks to return a promise that will cause the task to be retried again with the specified delay Pull Request resolved: https://github.com/facebook/react-native/pull/23231 Differential Revision: D15646870 fbshipit-source-id: 4440f4b4392f1fa5c69aab7908b51b7007ba2c40
2019-06-06 21:54:08 +03:00
if (!retryPosted) {
NativeHeadlessJsTaskSupport.notifyTaskFinished(taskId);
}
},
);
}
});
},
/**
* Only called from native code. Cancels a headless task.
*
* See https://reactnative.dev/docs/appregistry#cancelheadlesstask
*/
cancelHeadlessTask(taskId: number, taskKey: string): void {
const taskCancelProvider = taskCancelProviders.get(taskKey);
if (!taskCancelProvider) {
throw new Error(`No task canceller registered for key '${taskKey}'`);
}
taskCancelProvider()();
},
};
2015-01-30 04:10:49 +03:00
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);
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
2015-12-09 02:57:34 +03:00
if (__DEV__) {
const LogBoxInspector = require('../LogBox/LogBoxInspectorContainer').default;
AppRegistry.registerComponent('LogBox', () => LogBoxInspector);
} else {
AppRegistry.registerComponent(
'LogBox',
() =>
function NoOp() {
return null;
},
);
}
module.exports = AppRegistry;