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 type {
    
  11.   Thenable,
    
  12.   FulfilledThenable,
    
  13.   RejectedThenable,
    
  14. } from 'shared/ReactTypes';
    
  15. import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
    
  16. 
    
  17. export type SSRModuleMap = string; // Module root path
    
  18. 
    
  19. export type ServerManifest = string; // Module root path
    
  20. 
    
  21. export type ServerReferenceId = string;
    
  22. 
    
  23. import {prepareDestinationForModuleImpl} from 'react-client/src/ReactFlightClientConfig';
    
  24. 
    
  25. export opaque type ClientReferenceMetadata = [
    
  26.   string, // module path
    
  27.   string, // export name
    
  28. ];
    
  29. 
    
  30. // eslint-disable-next-line no-unused-vars
    
  31. export opaque type ClientReference<T> = {
    
  32.   specifier: string,
    
  33.   name: string,
    
  34. };
    
  35. 
    
  36. // The reason this function needs to defined here in this file instead of just
    
  37. // being exported directly from the WebpackDestination... file is because the
    
  38. // ClientReferenceMetadata is opaque and we can't unwrap it there.
    
  39. // This should get inlined and we could also just implement an unwrapping function
    
  40. // though that risks it getting used in places it shouldn't be. This is unfortunate
    
  41. // but currently it seems to be the best option we have.
    
  42. export function prepareDestinationForModule(
    
  43.   moduleLoading: ModuleLoading,
    
  44.   nonce: ?string,
    
  45.   metadata: ClientReferenceMetadata,
    
  46. ) {
    
  47.   prepareDestinationForModuleImpl(moduleLoading, metadata[0], nonce);
    
  48. }
    
  49. 
    
  50. export function resolveClientReference<T>(
    
  51.   bundlerConfig: SSRModuleMap,
    
  52.   metadata: ClientReferenceMetadata,
    
  53. ): ClientReference<T> {
    
  54.   const baseURL = bundlerConfig;
    
  55.   return {
    
  56.     specifier: baseURL + metadata[0],
    
  57.     name: metadata[1],
    
  58.   };
    
  59. }
    
  60. 
    
  61. export function resolveServerReference<T>(
    
  62.   config: ServerManifest,
    
  63.   id: ServerReferenceId,
    
  64. ): ClientReference<T> {
    
  65.   const baseURL: string = config;
    
  66.   const idx = id.lastIndexOf('#');
    
  67.   const exportName = id.slice(idx + 1);
    
  68.   const fullURL = id.slice(0, idx);
    
  69.   if (!fullURL.startsWith(baseURL)) {
    
  70.     throw new Error(
    
  71.       'Attempted to load a Server Reference outside the hosted root.',
    
  72.     );
    
  73.   }
    
  74.   return {specifier: fullURL, name: exportName};
    
  75. }
    
  76. 
    
  77. const asyncModuleCache: Map<string, Thenable<any>> = new Map();
    
  78. 
    
  79. export function preloadModule<T>(
    
  80.   metadata: ClientReference<T>,
    
  81. ): null | Thenable<any> {
    
  82.   const existingPromise = asyncModuleCache.get(metadata.specifier);
    
  83.   if (existingPromise) {
    
  84.     if (existingPromise.status === 'fulfilled') {
    
  85.       return null;
    
  86.     }
    
  87.     return existingPromise;
    
  88.   } else {
    
  89.     // $FlowFixMe[unsupported-syntax]
    
  90.     const modulePromise: Thenable<T> = import(metadata.specifier);
    
  91.     modulePromise.then(
    
  92.       value => {
    
  93.         const fulfilledThenable: FulfilledThenable<mixed> =
    
  94.           (modulePromise: any);
    
  95.         fulfilledThenable.status = 'fulfilled';
    
  96.         fulfilledThenable.value = value;
    
  97.       },
    
  98.       reason => {
    
  99.         const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any);
    
  100.         rejectedThenable.status = 'rejected';
    
  101.         rejectedThenable.reason = reason;
    
  102.       },
    
  103.     );
    
  104.     asyncModuleCache.set(metadata.specifier, modulePromise);
    
  105.     return modulePromise;
    
  106.   }
    
  107. }
    
  108. 
    
  109. export function requireModule<T>(metadata: ClientReference<T>): T {
    
  110.   let moduleExports;
    
  111.   // We assume that preloadModule has been called before, which
    
  112.   // should have added something to the module cache.
    
  113.   const promise: any = asyncModuleCache.get(metadata.specifier);
    
  114.   if (promise.status === 'fulfilled') {
    
  115.     moduleExports = promise.value;
    
  116.   } else {
    
  117.     throw promise.reason;
    
  118.   }
    
  119.   return moduleExports[metadata.name];
    
  120. }