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 {
    
  17.   ImportMetadata,
    
  18.   ImportManifestEntry,
    
  19. } from './shared/ReactFlightImportMetadata';
    
  20. import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
    
  21. 
    
  22. import {
    
  23.   ID,
    
  24.   CHUNKS,
    
  25.   NAME,
    
  26.   isAsyncImport,
    
  27. } from './shared/ReactFlightImportMetadata';
    
  28. 
    
  29. import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';
    
  30. 
    
  31. import {loadChunk} from 'react-client/src/ReactFlightClientConfig';
    
  32. 
    
  33. export type SSRModuleMap = null | {
    
  34.   [clientId: string]: {
    
  35.     [clientExportName: string]: ClientReferenceManifestEntry,
    
  36.   },
    
  37. };
    
  38. 
    
  39. export type ServerManifest = {
    
  40.   [id: string]: ImportManifestEntry,
    
  41. };
    
  42. 
    
  43. export type ServerReferenceId = string;
    
  44. 
    
  45. export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
    
  46. export opaque type ClientReferenceMetadata = ImportMetadata;
    
  47. 
    
  48. // eslint-disable-next-line no-unused-vars
    
  49. export opaque type ClientReference<T> = ClientReferenceMetadata;
    
  50. 
    
  51. // The reason this function needs to defined here in this file instead of just
    
  52. // being exported directly from the WebpackDestination... file is because the
    
  53. // ClientReferenceMetadata is opaque and we can't unwrap it there.
    
  54. // This should get inlined and we could also just implement an unwrapping function
    
  55. // though that risks it getting used in places it shouldn't be. This is unfortunate
    
  56. // but currently it seems to be the best option we have.
    
  57. export function prepareDestinationForModule(
    
  58.   moduleLoading: ModuleLoading,
    
  59.   nonce: ?string,
    
  60.   metadata: ClientReferenceMetadata,
    
  61. ) {
    
  62.   prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS], nonce);
    
  63. }
    
  64. 
    
  65. export function resolveClientReference<T>(
    
  66.   bundlerConfig: SSRModuleMap,
    
  67.   metadata: ClientReferenceMetadata,
    
  68. ): ClientReference<T> {
    
  69.   if (bundlerConfig) {
    
  70.     const moduleExports = bundlerConfig[metadata[ID]];
    
  71.     let resolvedModuleData = moduleExports[metadata[NAME]];
    
  72.     let name;
    
  73.     if (resolvedModuleData) {
    
  74.       // The potentially aliased name.
    
  75.       name = resolvedModuleData.name;
    
  76.     } else {
    
  77.       // If we don't have this specific name, we might have the full module.
    
  78.       resolvedModuleData = moduleExports['*'];
    
  79.       if (!resolvedModuleData) {
    
  80.         throw new Error(
    
  81.           'Could not find the module "' +
    
  82.             metadata[ID] +
    
  83.             '" in the React SSR Manifest. ' +
    
  84.             'This is probably a bug in the React Server Components bundler.',
    
  85.         );
    
  86.       }
    
  87.       name = metadata[NAME];
    
  88.     }
    
  89.     if (isAsyncImport(metadata)) {
    
  90.       return [
    
  91.         resolvedModuleData.id,
    
  92.         resolvedModuleData.chunks,
    
  93.         name,
    
  94.         1 /* async */,
    
  95.       ];
    
  96.     } else {
    
  97.       return [resolvedModuleData.id, resolvedModuleData.chunks, name];
    
  98.     }
    
  99.   }
    
  100.   return metadata;
    
  101. }
    
  102. 
    
  103. export function resolveServerReference<T>(
    
  104.   bundlerConfig: ServerManifest,
    
  105.   id: ServerReferenceId,
    
  106. ): ClientReference<T> {
    
  107.   let name = '';
    
  108.   let resolvedModuleData = bundlerConfig[id];
    
  109.   if (resolvedModuleData) {
    
  110.     // The potentially aliased name.
    
  111.     name = resolvedModuleData.name;
    
  112.   } else {
    
  113.     // We didn't find this specific export name but we might have the * export
    
  114.     // which contains this name as well.
    
  115.     // TODO: It's unfortunate that we now have to parse this string. We should
    
  116.     // probably go back to encoding path and name separately on the client reference.
    
  117.     const idx = id.lastIndexOf('#');
    
  118.     if (idx !== -1) {
    
  119.       name = id.slice(idx + 1);
    
  120.       resolvedModuleData = bundlerConfig[id.slice(0, idx)];
    
  121.     }
    
  122.     if (!resolvedModuleData) {
    
  123.       throw new Error(
    
  124.         'Could not find the module "' +
    
  125.           id +
    
  126.           '" in the React Server Manifest. ' +
    
  127.           'This is probably a bug in the React Server Components bundler.',
    
  128.       );
    
  129.     }
    
  130.   }
    
  131.   // TODO: This needs to return async: true if it's an async module.
    
  132.   return [resolvedModuleData.id, resolvedModuleData.chunks, name];
    
  133. }
    
  134. 
    
  135. // The chunk cache contains all the chunks we've preloaded so far.
    
  136. // If they're still pending they're a thenable. This map also exists
    
  137. // in Webpack but unfortunately it's not exposed so we have to
    
  138. // replicate it in user space. null means that it has already loaded.
    
  139. const chunkCache: Map<string, null | Promise<any>> = new Map();
    
  140. 
    
  141. function requireAsyncModule(id: string): null | Thenable<any> {
    
  142.   // We've already loaded all the chunks. We can require the module.
    
  143.   const promise = __webpack_require__(id);
    
  144.   if (typeof promise.then !== 'function') {
    
  145.     // This wasn't a promise after all.
    
  146.     return null;
    
  147.   } else if (promise.status === 'fulfilled') {
    
  148.     // This module was already resolved earlier.
    
  149.     return null;
    
  150.   } else {
    
  151.     // Instrument the Promise to stash the result.
    
  152.     promise.then(
    
  153.       value => {
    
  154.         const fulfilledThenable: FulfilledThenable<mixed> = (promise: any);
    
  155.         fulfilledThenable.status = 'fulfilled';
    
  156.         fulfilledThenable.value = value;
    
  157.       },
    
  158.       reason => {
    
  159.         const rejectedThenable: RejectedThenable<mixed> = (promise: any);
    
  160.         rejectedThenable.status = 'rejected';
    
  161.         rejectedThenable.reason = reason;
    
  162.       },
    
  163.     );
    
  164.     return promise;
    
  165.   }
    
  166. }
    
  167. 
    
  168. function ignoreReject() {
    
  169.   // We rely on rejected promises to be handled by another listener.
    
  170. }
    
  171. // Start preloading the modules since we might need them soon.
    
  172. // This function doesn't suspend.
    
  173. export function preloadModule<T>(
    
  174.   metadata: ClientReference<T>,
    
  175. ): null | Thenable<any> {
    
  176.   const chunks = metadata[CHUNKS];
    
  177.   const promises = [];
    
  178.   let i = 0;
    
  179.   while (i < chunks.length) {
    
  180.     const chunkId = chunks[i++];
    
  181.     const chunkFilename = chunks[i++];
    
  182.     const entry = chunkCache.get(chunkId);
    
  183.     if (entry === undefined) {
    
  184.       const thenable = loadChunk(chunkId, chunkFilename);
    
  185.       promises.push(thenable);
    
  186.       // $FlowFixMe[method-unbinding]
    
  187.       const resolve = chunkCache.set.bind(chunkCache, chunkId, null);
    
  188.       thenable.then(resolve, ignoreReject);
    
  189.       chunkCache.set(chunkId, thenable);
    
  190.     } else if (entry !== null) {
    
  191.       promises.push(entry);
    
  192.     }
    
  193.   }
    
  194.   if (isAsyncImport(metadata)) {
    
  195.     if (promises.length === 0) {
    
  196.       return requireAsyncModule(metadata[ID]);
    
  197.     } else {
    
  198.       return Promise.all(promises).then(() => {
    
  199.         return requireAsyncModule(metadata[ID]);
    
  200.       });
    
  201.     }
    
  202.   } else if (promises.length > 0) {
    
  203.     return Promise.all(promises);
    
  204.   } else {
    
  205.     return null;
    
  206.   }
    
  207. }
    
  208. 
    
  209. // Actually require the module or suspend if it's not yet ready.
    
  210. // Increase priority if necessary.
    
  211. export function requireModule<T>(metadata: ClientReference<T>): T {
    
  212.   let moduleExports = __webpack_require__(metadata[ID]);
    
  213.   if (isAsyncImport(metadata)) {
    
  214.     if (typeof moduleExports.then !== 'function') {
    
  215.       // This wasn't a promise after all.
    
  216.     } else if (moduleExports.status === 'fulfilled') {
    
  217.       // This Promise should've been instrumented by preloadModule.
    
  218.       moduleExports = moduleExports.value;
    
  219.     } else {
    
  220.       throw moduleExports.reason;
    
  221.     }
    
  222.   }
    
  223.   if (metadata[NAME] === '*') {
    
  224.     // This is a placeholder value that represents that the caller imported this
    
  225.     // as a CommonJS module as is.
    
  226.     return moduleExports;
    
  227.   }
    
  228.   if (metadata[NAME] === '') {
    
  229.     // This is a placeholder value that represents that the caller accessed the
    
  230.     // default property of this if it was an ESM interop module.
    
  231.     return moduleExports.__esModule ? moduleExports.default : moduleExports;
    
  232.   }
    
  233.   return moduleExports[metadata[NAME]];
    
  234. }