1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and 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.  */
    
  9. 
    
  10. import {__DEBUG__} from 'react-devtools-shared/src/constants';
    
  11. 
    
  12. import type {Thenable, Wakeable} from 'shared/ReactTypes';
    
  13. 
    
  14. const TIMEOUT = 30000;
    
  15. 
    
  16. const Pending = 0;
    
  17. const Resolved = 1;
    
  18. const Rejected = 2;
    
  19. 
    
  20. type PendingRecord = {
    
  21.   status: 0,
    
  22.   value: Wakeable,
    
  23. };
    
  24. 
    
  25. type ResolvedRecord<T> = {
    
  26.   status: 1,
    
  27.   value: T,
    
  28. };
    
  29. 
    
  30. type RejectedRecord = {
    
  31.   status: 2,
    
  32.   value: null,
    
  33. };
    
  34. 
    
  35. type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
    
  36. 
    
  37. type Module = any;
    
  38. type ModuleLoaderFunction = () => Thenable<Module>;
    
  39. 
    
  40. // This is intentionally a module-level Map, rather than a React-managed one.
    
  41. // Otherwise, refreshing the inspected element cache would also clear this cache.
    
  42. // Modules are static anyway.
    
  43. const moduleLoaderFunctionToModuleMap: Map<ModuleLoaderFunction, Module> =
    
  44.   new Map();
    
  45. 
    
  46. function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
    
  47.   if (record.status === Resolved) {
    
  48.     // This is just a type refinement.
    
  49.     return record;
    
  50.   } else if (record.status === Rejected) {
    
  51.     // This is just a type refinement.
    
  52.     return record;
    
  53.   } else {
    
  54.     throw record.value;
    
  55.   }
    
  56. }
    
  57. 
    
  58. // TODO Flow type
    
  59. export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
    
  60.   let record = moduleLoaderFunctionToModuleMap.get(moduleLoaderFunction);
    
  61. 
    
  62.   if (__DEBUG__) {
    
  63.     console.log(
    
  64.       `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}")`,
    
  65.     );
    
  66.   }
    
  67. 
    
  68.   if (!record) {
    
  69.     const callbacks = new Set<() => mixed>();
    
  70.     const wakeable: Wakeable = {
    
  71.       then(callback: () => mixed) {
    
  72.         callbacks.add(callback);
    
  73.       },
    
  74. 
    
  75.       // Optional property used by Timeline:
    
  76.       displayName: `Loading module "${moduleLoaderFunction.name}"`,
    
  77.     };
    
  78. 
    
  79.     const wake = () => {
    
  80.       if (timeoutID) {
    
  81.         clearTimeout(timeoutID);
    
  82.         timeoutID = null;
    
  83.       }
    
  84. 
    
  85.       // This assumes they won't throw.
    
  86.       callbacks.forEach(callback => callback());
    
  87.       callbacks.clear();
    
  88.     };
    
  89. 
    
  90.     const newRecord: Record<Module> = (record = {
    
  91.       status: Pending,
    
  92.       value: wakeable,
    
  93.     });
    
  94. 
    
  95.     let didTimeout = false;
    
  96. 
    
  97.     moduleLoaderFunction().then(
    
  98.       module => {
    
  99.         if (__DEBUG__) {
    
  100.           console.log(
    
  101.             `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}") then()`,
    
  102.           );
    
  103.         }
    
  104. 
    
  105.         if (didTimeout) {
    
  106.           return;
    
  107.         }
    
  108. 
    
  109.         const resolvedRecord = ((newRecord: any): ResolvedRecord<Module>);
    
  110.         resolvedRecord.status = Resolved;
    
  111.         resolvedRecord.value = module;
    
  112. 
    
  113.         wake();
    
  114.       },
    
  115.       error => {
    
  116.         if (__DEBUG__) {
    
  117.           console.log(
    
  118.             `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}") catch()`,
    
  119.           );
    
  120.         }
    
  121. 
    
  122.         if (didTimeout) {
    
  123.           return;
    
  124.         }
    
  125. 
    
  126.         console.log(error);
    
  127. 
    
  128.         const thrownRecord = ((newRecord: any): RejectedRecord);
    
  129.         thrownRecord.status = Rejected;
    
  130.         thrownRecord.value = null;
    
  131. 
    
  132.         wake();
    
  133.       },
    
  134.     );
    
  135. 
    
  136.     // Eventually timeout and stop trying to load the module.
    
  137.     let timeoutID: null | TimeoutID = setTimeout(function onTimeout() {
    
  138.       if (__DEBUG__) {
    
  139.         console.log(
    
  140.           `[dynamicImportCache] loadModule("${moduleLoaderFunction.name}") onTimeout()`,
    
  141.         );
    
  142.       }
    
  143. 
    
  144.       timeoutID = null;
    
  145. 
    
  146.       didTimeout = true;
    
  147. 
    
  148.       const timedoutRecord = ((newRecord: any): RejectedRecord);
    
  149.       timedoutRecord.status = Rejected;
    
  150.       timedoutRecord.value = null;
    
  151. 
    
  152.       wake();
    
  153.     }, TIMEOUT);
    
  154. 
    
  155.     moduleLoaderFunctionToModuleMap.set(moduleLoaderFunction, record);
    
  156.   }
    
  157. 
    
  158.   // $FlowFixMe[underconstrained-implicit-instantiation]
    
  159.   const response = readRecord(record).value;
    
  160.   return response;
    
  161. }