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. 
    
  16. import type {ImportMetadata} from './shared/ReactFlightImportMetadata';
    
  17. import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
    
  18. 
    
  19. import {
    
  20.   ID,
    
  21.   CHUNKS,
    
  22.   NAME,
    
  23.   isAsyncImport,
    
  24. } from './shared/ReactFlightImportMetadata';
    
  25. import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
    
  26. 
    
  27. export type SSRModuleMap = {
    
  28.   [clientId: string]: {
    
  29.     [clientExportName: string]: ClientReference<any>,
    
  30.   },
    
  31. };
    
  32. 
    
  33. export type ServerManifest = void;
    
  34. 
    
  35. export type ServerReferenceId = string;
    
  36. 
    
  37. export opaque type ClientReferenceMetadata = ImportMetadata;
    
  38. 
    
  39. // eslint-disable-next-line no-unused-vars
    
  40. export opaque type ClientReference<T> = {
    
  41.   specifier: string,
    
  42.   name: string,
    
  43.   async?: boolean,
    
  44. };
    
  45. 
    
  46. // The reason this function needs to defined here in this file instead of just
    
  47. // being exported directly from the WebpackDestination... file is because the
    
  48. // ClientReferenceMetadata is opaque and we can't unwrap it there.
    
  49. // This should get inlined and we could also just implement an unwrapping function
    
  50. // though that risks it getting used in places it shouldn't be. This is unfortunate
    
  51. // but currently it seems to be the best option we have.
    
  52. export function prepareDestinationForModule(
    
  53.   moduleLoading: ModuleLoading,
    
  54.   nonce: ?string,
    
  55.   metadata: ClientReferenceMetadata,
    
  56. ) {
    
  57.   prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce);
    
  58. }
    
  59. 
    
  60. export function resolveClientReference<T>(
    
  61.   bundlerConfig: SSRModuleMap,
    
  62.   metadata: ClientReferenceMetadata,
    
  63. ): ClientReference<T> {
    
  64.   const moduleExports = bundlerConfig[metadata[ID]];
    
  65.   let resolvedModuleData = moduleExports[metadata[NAME]];
    
  66.   let name;
    
  67.   if (resolvedModuleData) {
    
  68.     // The potentially aliased name.
    
  69.     name = resolvedModuleData.name;
    
  70.   } else {
    
  71.     // If we don't have this specific name, we might have the full module.
    
  72.     resolvedModuleData = moduleExports['*'];
    
  73.     if (!resolvedModuleData) {
    
  74.       throw new Error(
    
  75.         'Could not find the module "' +
    
  76.           metadata[ID] +
    
  77.           '" in the React SSR Manifest. ' +
    
  78.           'This is probably a bug in the React Server Components bundler.',
    
  79.       );
    
  80.     }
    
  81.     name = metadata[NAME];
    
  82.   }
    
  83.   return {
    
  84.     specifier: resolvedModuleData.specifier,
    
  85.     name: name,
    
  86.     async: isAsyncImport(metadata),
    
  87.   };
    
  88. }
    
  89. 
    
  90. export function resolveServerReference<T>(
    
  91.   bundlerConfig: ServerManifest,
    
  92.   id: ServerReferenceId,
    
  93. ): ClientReference<T> {
    
  94.   const idx = id.lastIndexOf('#');
    
  95.   const specifier = id.slice(0, idx);
    
  96.   const name = id.slice(idx + 1);
    
  97.   return {specifier, name};
    
  98. }
    
  99. 
    
  100. const asyncModuleCache: Map<string, Thenable<any>> = new Map();
    
  101. 
    
  102. export function preloadModule<T>(
    
  103.   metadata: ClientReference<T>,
    
  104. ): null | Thenable<any> {
    
  105.   const existingPromise = asyncModuleCache.get(metadata.specifier);
    
  106.   if (existingPromise) {
    
  107.     if (existingPromise.status === 'fulfilled') {
    
  108.       return null;
    
  109.     }
    
  110.     return existingPromise;
    
  111.   } else {
    
  112.     // $FlowFixMe[unsupported-syntax]
    
  113.     let modulePromise: Promise<T> = import(metadata.specifier);
    
  114.     if (metadata.async) {
    
  115.       // If the module is async, it must have been a CJS module.
    
  116.       // CJS modules are accessed through the default export in
    
  117.       // Node.js so we have to get the default export to get the
    
  118.       // full module exports.
    
  119.       modulePromise = modulePromise.then(function (value) {
    
  120.         return (value: any).default;
    
  121.       });
    
  122.     }
    
  123.     modulePromise.then(
    
  124.       value => {
    
  125.         const fulfilledThenable: FulfilledThenable<mixed> =
    
  126.           (modulePromise: any);
    
  127.         fulfilledThenable.status = 'fulfilled';
    
  128.         fulfilledThenable.value = value;
    
  129.       },
    
  130.       reason => {
    
  131.         const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any);
    
  132.         rejectedThenable.status = 'rejected';
    
  133.         rejectedThenable.reason = reason;
    
  134.       },
    
  135.     );
    
  136.     asyncModuleCache.set(metadata.specifier, modulePromise);
    
  137.     return modulePromise;
    
  138.   }
    
  139. }
    
  140. 
    
  141. export function requireModule<T>(metadata: ClientReference<T>): T {
    
  142.   let moduleExports;
    
  143.   // We assume that preloadModule has been called before, which
    
  144.   // should have added something to the module cache.
    
  145.   const promise: any = asyncModuleCache.get(metadata.specifier);
    
  146.   if (promise.status === 'fulfilled') {
    
  147.     moduleExports = promise.value;
    
  148.   } else {
    
  149.     throw promise.reason;
    
  150.   }
    
  151.   if (metadata.name === '*') {
    
  152.     // This is a placeholder value that represents that the caller imported this
    
  153.     // as a CommonJS module as is.
    
  154.     return moduleExports;
    
  155.   }
    
  156.   if (metadata.name === '') {
    
  157.     // This is a placeholder value that represents that the caller accessed the
    
  158.     // default property of this if it was an ESM interop module.
    
  159.     return moduleExports.default;
    
  160.   }
    
  161.   return moduleExports[metadata.name];
    
  162. }