AppRegistry.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @flow
  8. * @format
  9. */
  10. 'use strict';
  11. const BatchedBridge = require('../BatchedBridge/BatchedBridge');
  12. const BugReporting = require('../BugReporting/BugReporting');
  13. const ReactNative = require('../Renderer/shims/ReactNative');
  14. const SceneTracker = require('../Utilities/SceneTracker');
  15. const infoLog = require('../Utilities/infoLog');
  16. const invariant = require('invariant');
  17. const renderApplication = require('./renderApplication');
  18. const createPerformanceLogger = require('../Utilities/createPerformanceLogger');
  19. import type {IPerformanceLogger} from '../Utilities/createPerformanceLogger';
  20. import NativeHeadlessJsTaskSupport from './NativeHeadlessJsTaskSupport';
  21. import HeadlessJsTaskError from './HeadlessJsTaskError';
  22. type Task = (taskData: any) => Promise<void>;
  23. export type TaskProvider = () => Task;
  24. type TaskCanceller = () => void;
  25. type TaskCancelProvider = () => TaskCanceller;
  26. export type ComponentProvider = () => React$ComponentType<any>;
  27. export type ComponentProviderInstrumentationHook = (
  28. component: ComponentProvider,
  29. scopedPerformanceLogger: IPerformanceLogger,
  30. ) => React$ComponentType<any>;
  31. export type AppConfig = {
  32. appKey: string,
  33. component?: ComponentProvider,
  34. run?: Function,
  35. section?: boolean,
  36. ...
  37. };
  38. export type Runnable = {
  39. component?: ComponentProvider,
  40. run: Function,
  41. ...
  42. };
  43. export type Runnables = {[appKey: string]: Runnable, ...};
  44. export type Registry = {
  45. sections: Array<string>,
  46. runnables: Runnables,
  47. ...
  48. };
  49. export type WrapperComponentProvider = any => React$ComponentType<*>;
  50. const runnables: Runnables = {};
  51. let runCount = 1;
  52. const sections: Runnables = {};
  53. const taskProviders: Map<string, TaskProvider> = new Map();
  54. const taskCancelProviders: Map<string, TaskCancelProvider> = new Map();
  55. let componentProviderInstrumentationHook: ComponentProviderInstrumentationHook = (
  56. component: ComponentProvider,
  57. ) => component();
  58. let wrapperComponentProvider: ?WrapperComponentProvider;
  59. let showArchitectureIndicator = false;
  60. /**
  61. * `AppRegistry` is the JavaScript entry point to running all React Native apps.
  62. *
  63. * See https://reactnative.dev/docs/appregistry.html
  64. */
  65. const AppRegistry = {
  66. setWrapperComponentProvider(provider: WrapperComponentProvider) {
  67. wrapperComponentProvider = provider;
  68. },
  69. enableArchitectureIndicator(enabled: boolean): void {
  70. showArchitectureIndicator = enabled;
  71. },
  72. registerConfig(config: Array<AppConfig>): void {
  73. config.forEach(appConfig => {
  74. if (appConfig.run) {
  75. AppRegistry.registerRunnable(appConfig.appKey, appConfig.run);
  76. } else {
  77. invariant(
  78. appConfig.component != null,
  79. 'AppRegistry.registerConfig(...): Every config is expected to set ' +
  80. 'either `run` or `component`, but `%s` has neither.',
  81. appConfig.appKey,
  82. );
  83. AppRegistry.registerComponent(
  84. appConfig.appKey,
  85. appConfig.component,
  86. appConfig.section,
  87. );
  88. }
  89. });
  90. },
  91. /**
  92. * Registers an app's root component.
  93. *
  94. * See https://reactnative.dev/docs/appregistry.html#registercomponent
  95. */
  96. registerComponent(
  97. appKey: string,
  98. componentProvider: ComponentProvider,
  99. section?: boolean,
  100. ): string {
  101. let scopedPerformanceLogger = createPerformanceLogger();
  102. runnables[appKey] = {
  103. componentProvider,
  104. run: appParameters => {
  105. renderApplication(
  106. componentProviderInstrumentationHook(
  107. componentProvider,
  108. scopedPerformanceLogger,
  109. ),
  110. appParameters.initialProps,
  111. appParameters.rootTag,
  112. wrapperComponentProvider && wrapperComponentProvider(appParameters),
  113. appParameters.fabric,
  114. showArchitectureIndicator,
  115. scopedPerformanceLogger,
  116. appKey === 'LogBox',
  117. );
  118. },
  119. };
  120. if (section) {
  121. sections[appKey] = runnables[appKey];
  122. }
  123. return appKey;
  124. },
  125. registerRunnable(appKey: string, run: Function): string {
  126. runnables[appKey] = {run};
  127. return appKey;
  128. },
  129. registerSection(appKey: string, component: ComponentProvider): void {
  130. AppRegistry.registerComponent(appKey, component, true);
  131. },
  132. getAppKeys(): Array<string> {
  133. return Object.keys(runnables);
  134. },
  135. getSectionKeys(): Array<string> {
  136. return Object.keys(sections);
  137. },
  138. getSections(): Runnables {
  139. return {
  140. ...sections,
  141. };
  142. },
  143. getRunnable(appKey: string): ?Runnable {
  144. return runnables[appKey];
  145. },
  146. getRegistry(): Registry {
  147. return {
  148. sections: AppRegistry.getSectionKeys(),
  149. runnables: {...runnables},
  150. };
  151. },
  152. setComponentProviderInstrumentationHook(
  153. hook: ComponentProviderInstrumentationHook,
  154. ) {
  155. componentProviderInstrumentationHook = hook;
  156. },
  157. /**
  158. * Loads the JavaScript bundle and runs the app.
  159. *
  160. * See https://reactnative.dev/docs/appregistry.html#runapplication
  161. */
  162. runApplication(appKey: string, appParameters: any): void {
  163. if (appKey !== 'LogBox') {
  164. const msg =
  165. 'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
  166. infoLog(msg);
  167. BugReporting.addSource(
  168. 'AppRegistry.runApplication' + runCount++,
  169. () => msg,
  170. );
  171. }
  172. invariant(
  173. runnables[appKey] && runnables[appKey].run,
  174. `"${appKey}" has not been registered. This can happen if:\n` +
  175. '* Metro (the local dev server) is run from the wrong folder. ' +
  176. 'Check if Metro is running, stop it and restart it in the current project.\n' +
  177. "* A module failed to load due to an error and `AppRegistry.registerComponent` wasn't called.",
  178. );
  179. SceneTracker.setActiveScene({name: appKey});
  180. runnables[appKey].run(appParameters);
  181. },
  182. /**
  183. * Stops an application when a view should be destroyed.
  184. *
  185. * See https://reactnative.dev/docs/appregistry.html#unmountapplicationcomponentatroottag
  186. */
  187. unmountApplicationComponentAtRootTag(rootTag: number): void {
  188. ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag);
  189. },
  190. /**
  191. * Register a headless task. A headless task is a bit of code that runs without a UI.
  192. *
  193. * See https://reactnative.dev/docs/appregistry.html#registerheadlesstask
  194. */
  195. registerHeadlessTask(taskKey: string, taskProvider: TaskProvider): void {
  196. this.registerCancellableHeadlessTask(taskKey, taskProvider, () => () => {
  197. /* Cancel is no-op */
  198. });
  199. },
  200. /**
  201. * Register a cancellable headless task. A headless task is a bit of code that runs without a UI.
  202. *
  203. * See https://reactnative.dev/docs/appregistry.html#registercancellableheadlesstask
  204. */
  205. registerCancellableHeadlessTask(
  206. taskKey: string,
  207. taskProvider: TaskProvider,
  208. taskCancelProvider: TaskCancelProvider,
  209. ): void {
  210. if (taskProviders.has(taskKey)) {
  211. console.warn(
  212. `registerHeadlessTask or registerCancellableHeadlessTask called multiple times for same key '${taskKey}'`,
  213. );
  214. }
  215. taskProviders.set(taskKey, taskProvider);
  216. taskCancelProviders.set(taskKey, taskCancelProvider);
  217. },
  218. /**
  219. * Only called from native code. Starts a headless task.
  220. *
  221. * See https://reactnative.dev/docs/appregistry.html#startheadlesstask
  222. */
  223. startHeadlessTask(taskId: number, taskKey: string, data: any): void {
  224. const taskProvider = taskProviders.get(taskKey);
  225. if (!taskProvider) {
  226. console.warn(`No task registered for key ${taskKey}`);
  227. if (NativeHeadlessJsTaskSupport) {
  228. NativeHeadlessJsTaskSupport.notifyTaskFinished(taskId);
  229. }
  230. return;
  231. }
  232. taskProvider()(data)
  233. .then(() => {
  234. if (NativeHeadlessJsTaskSupport) {
  235. NativeHeadlessJsTaskSupport.notifyTaskFinished(taskId);
  236. }
  237. })
  238. .catch(reason => {
  239. console.error(reason);
  240. if (
  241. NativeHeadlessJsTaskSupport &&
  242. reason instanceof HeadlessJsTaskError
  243. ) {
  244. NativeHeadlessJsTaskSupport.notifyTaskRetry(taskId).then(
  245. retryPosted => {
  246. if (!retryPosted) {
  247. NativeHeadlessJsTaskSupport.notifyTaskFinished(taskId);
  248. }
  249. },
  250. );
  251. }
  252. });
  253. },
  254. /**
  255. * Only called from native code. Cancels a headless task.
  256. *
  257. * See https://reactnative.dev/docs/appregistry.html#cancelheadlesstask
  258. */
  259. cancelHeadlessTask(taskId: number, taskKey: string): void {
  260. const taskCancelProvider = taskCancelProviders.get(taskKey);
  261. if (!taskCancelProvider) {
  262. throw new Error(`No task canceller registered for key '${taskKey}'`);
  263. }
  264. taskCancelProvider()();
  265. },
  266. };
  267. BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);
  268. if (__DEV__) {
  269. const LogBoxInspector = require('../LogBox/LogBoxInspectorContainer').default;
  270. AppRegistry.registerComponent('LogBox', () => LogBoxInspector);
  271. } else {
  272. AppRegistry.registerComponent(
  273. 'LogBox',
  274. () =>
  275. function NoOp() {
  276. return null;
  277. },
  278. );
  279. }
  280. module.exports = AppRegistry;