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.   HostDispatcher,
    
  12.   CrossOriginEnum,
    
  13.   PreloadImplOptions,
    
  14.   PreloadModuleImplOptions,
    
  15.   PreinitStyleOptions,
    
  16.   PreinitScriptOptions,
    
  17.   PreinitModuleScriptOptions,
    
  18. } from 'react-dom/src/shared/ReactDOMTypes';
    
  19. 
    
  20. import {enableFloat} from 'shared/ReactFeatureFlags';
    
  21. 
    
  22. import {
    
  23.   emitHint,
    
  24.   getHints,
    
  25.   resolveRequest,
    
  26. } from 'react-server/src/ReactFlightServer';
    
  27. 
    
  28. export const ReactDOMFlightServerDispatcher: HostDispatcher = {
    
  29.   prefetchDNS,
    
  30.   preconnect,
    
  31.   preload,
    
  32.   preloadModule,
    
  33.   preinitStyle,
    
  34.   preinitScript,
    
  35.   preinitModuleScript,
    
  36. };
    
  37. 
    
  38. function prefetchDNS(href: string) {
    
  39.   if (enableFloat) {
    
  40.     if (typeof href === 'string' && href) {
    
  41.       const request = resolveRequest();
    
  42.       if (request) {
    
  43.         const hints = getHints(request);
    
  44.         const key = 'D|' + href;
    
  45.         if (hints.has(key)) {
    
  46.           // duplicate hint
    
  47.           return;
    
  48.         }
    
  49.         hints.add(key);
    
  50.         emitHint(request, 'D', href);
    
  51.       }
    
  52.     }
    
  53.   }
    
  54. }
    
  55. 
    
  56. function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
    
  57.   if (enableFloat) {
    
  58.     if (typeof href === 'string') {
    
  59.       const request = resolveRequest();
    
  60.       if (request) {
    
  61.         const hints = getHints(request);
    
  62. 
    
  63.         const key = `C|${crossOrigin == null ? 'null' : crossOrigin}|${href}`;
    
  64.         if (hints.has(key)) {
    
  65.           // duplicate hint
    
  66.           return;
    
  67.         }
    
  68.         hints.add(key);
    
  69.         if (typeof crossOrigin === 'string') {
    
  70.           emitHint(request, 'C', [href, crossOrigin]);
    
  71.         } else {
    
  72.           emitHint(request, 'C', href);
    
  73.         }
    
  74.       }
    
  75.     }
    
  76.   }
    
  77. }
    
  78. 
    
  79. function preload(href: string, as: string, options?: ?PreloadImplOptions) {
    
  80.   if (enableFloat) {
    
  81.     if (typeof href === 'string') {
    
  82.       const request = resolveRequest();
    
  83.       if (request) {
    
  84.         const hints = getHints(request);
    
  85.         let key = 'L';
    
  86.         if (as === 'image' && options) {
    
  87.           key += getImagePreloadKey(
    
  88.             href,
    
  89.             options.imageSrcSet,
    
  90.             options.imageSizes,
    
  91.           );
    
  92.         } else {
    
  93.           key += `[${as}]${href}`;
    
  94.         }
    
  95.         if (hints.has(key)) {
    
  96.           // duplicate hint
    
  97.           return;
    
  98.         }
    
  99.         hints.add(key);
    
  100. 
    
  101.         const trimmed = trimOptions(options);
    
  102.         if (trimmed) {
    
  103.           emitHint(request, 'L', [href, as, trimmed]);
    
  104.         } else {
    
  105.           emitHint(request, 'L', [href, as]);
    
  106.         }
    
  107.       }
    
  108.     }
    
  109.   }
    
  110. }
    
  111. 
    
  112. function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
    
  113.   if (enableFloat) {
    
  114.     if (typeof href === 'string') {
    
  115.       const request = resolveRequest();
    
  116.       if (request) {
    
  117.         const hints = getHints(request);
    
  118.         const key = 'm|' + href;
    
  119.         if (hints.has(key)) {
    
  120.           // duplicate hint
    
  121.           return;
    
  122.         }
    
  123.         hints.add(key);
    
  124. 
    
  125.         const trimmed = trimOptions(options);
    
  126.         if (trimmed) {
    
  127.           return emitHint(request, 'm', [href, trimmed]);
    
  128.         } else {
    
  129.           return emitHint(request, 'm', href);
    
  130.         }
    
  131.       }
    
  132.     }
    
  133.   }
    
  134. }
    
  135. 
    
  136. function preinitStyle(
    
  137.   href: string,
    
  138.   precedence: ?string,
    
  139.   options?: ?PreinitStyleOptions,
    
  140. ) {
    
  141.   if (enableFloat) {
    
  142.     if (typeof href === 'string') {
    
  143.       const request = resolveRequest();
    
  144.       if (request) {
    
  145.         const hints = getHints(request);
    
  146.         const key = 'S|' + href;
    
  147.         if (hints.has(key)) {
    
  148.           // duplicate hint
    
  149.           return;
    
  150.         }
    
  151.         hints.add(key);
    
  152. 
    
  153.         const trimmed = trimOptions(options);
    
  154.         if (trimmed) {
    
  155.           return emitHint(request, 'S', [
    
  156.             href,
    
  157.             typeof precedence === 'string' ? precedence : 0,
    
  158.             trimmed,
    
  159.           ]);
    
  160.         } else if (typeof precedence === 'string') {
    
  161.           return emitHint(request, 'S', [href, precedence]);
    
  162.         } else {
    
  163.           return emitHint(request, 'S', href);
    
  164.         }
    
  165.       }
    
  166.     }
    
  167.   }
    
  168. }
    
  169. 
    
  170. function preinitScript(href: string, options?: ?PreinitScriptOptions) {
    
  171.   if (enableFloat) {
    
  172.     if (typeof href === 'string') {
    
  173.       const request = resolveRequest();
    
  174.       if (request) {
    
  175.         const hints = getHints(request);
    
  176.         const key = 'X|' + href;
    
  177.         if (hints.has(key)) {
    
  178.           // duplicate hint
    
  179.           return;
    
  180.         }
    
  181.         hints.add(key);
    
  182. 
    
  183.         const trimmed = trimOptions(options);
    
  184.         if (trimmed) {
    
  185.           return emitHint(request, 'X', [href, trimmed]);
    
  186.         } else {
    
  187.           return emitHint(request, 'X', href);
    
  188.         }
    
  189.       }
    
  190.     }
    
  191.   }
    
  192. }
    
  193. 
    
  194. function preinitModuleScript(
    
  195.   href: string,
    
  196.   options?: ?PreinitModuleScriptOptions,
    
  197. ) {
    
  198.   if (enableFloat) {
    
  199.     if (typeof href === 'string') {
    
  200.       const request = resolveRequest();
    
  201.       if (request) {
    
  202.         const hints = getHints(request);
    
  203.         const key = 'M|' + href;
    
  204.         if (hints.has(key)) {
    
  205.           // duplicate hint
    
  206.           return;
    
  207.         }
    
  208.         hints.add(key);
    
  209. 
    
  210.         const trimmed = trimOptions(options);
    
  211.         if (trimmed) {
    
  212.           return emitHint(request, 'M', [href, trimmed]);
    
  213.         } else {
    
  214.           return emitHint(request, 'M', href);
    
  215.         }
    
  216.       }
    
  217.     }
    
  218.   }
    
  219. }
    
  220. 
    
  221. // Flight normally encodes undefined as a special character however for directive option
    
  222. // arguments we don't want to send unnecessary keys and bloat the payload so we create a
    
  223. // trimmed object which omits any keys with null or undefined values.
    
  224. // This is only typesafe because these option objects have entirely optional fields where
    
  225. // null and undefined represent the same thing as no property.
    
  226. function trimOptions<
    
  227.   T:
    
  228.     | PreloadImplOptions
    
  229.     | PreloadModuleImplOptions
    
  230.     | PreinitStyleOptions
    
  231.     | PreinitScriptOptions
    
  232.     | PreinitModuleScriptOptions,
    
  233. >(options: ?T): ?T {
    
  234.   if (options == null) return null;
    
  235.   let hasProperties = false;
    
  236.   const trimmed: T = ({}: any);
    
  237.   for (const key in options) {
    
  238.     if (options[key] != null) {
    
  239.       hasProperties = true;
    
  240.       (trimmed: any)[key] = options[key];
    
  241.     }
    
  242.   }
    
  243.   return hasProperties ? trimmed : null;
    
  244. }
    
  245. 
    
  246. function getImagePreloadKey(
    
  247.   href: string,
    
  248.   imageSrcSet: ?string,
    
  249.   imageSizes: ?string,
    
  250. ) {
    
  251.   let uniquePart = '';
    
  252.   if (typeof imageSrcSet === 'string' && imageSrcSet !== '') {
    
  253.     uniquePart += '[' + imageSrcSet + ']';
    
  254.     if (typeof imageSizes === 'string') {
    
  255.       uniquePart += '[' + imageSizes + ']';
    
  256.     }
    
  257.   } else {
    
  258.     uniquePart += '[][]' + href;
    
  259.   }
    
  260.   return `[image]${uniquePart}`;
    
  261. }