/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
HostDispatcher,
CrossOriginEnum,
PreloadImplOptions,
PreloadModuleImplOptions,
PreinitStyleOptions,
PreinitScriptOptions,
PreinitModuleScriptOptions,
} from 'react-dom/src/shared/ReactDOMTypes';
import {enableFloat} from 'shared/ReactFeatureFlags';
import {
emitHint,
getHints,
resolveRequest,
} from 'react-server/src/ReactFlightServer';
export const ReactDOMFlightServerDispatcher: HostDispatcher = {
prefetchDNS,
preconnect,
preload,
preloadModule,
preinitStyle,
preinitScript,
preinitModuleScript,
};
function prefetchDNS(href: string) {
if (enableFloat) {
if (typeof href === 'string' && href) {
const request = resolveRequest();
if (request) {
const hints = getHints(request);
const key = 'D|' + href;
if (hints.has(key)) {
// duplicate hint
return;
}
hints.add(key);
emitHint(request, 'D', href);
}
}
}
}
function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
if (enableFloat) {
if (typeof href === 'string') {
const request = resolveRequest();
if (request) {
const hints = getHints(request);
const key = `C|${crossOrigin == null ? 'null' : crossOrigin}|${href}`;
if (hints.has(key)) {
// duplicate hint
return;
}
hints.add(key);
if (typeof crossOrigin === 'string') {
emitHint(request, 'C', [href, crossOrigin]);
} else {
emitHint(request, 'C', href);
}
}
}
}
}
function preload(href: string, as: string, options?: ?PreloadImplOptions) {
if (enableFloat) {
if (typeof href === 'string') {
const request = resolveRequest();
if (request) {
const hints = getHints(request);
let key = 'L';
if (as === 'image' && options) {
key += getImagePreloadKey(
href,
options.imageSrcSet,
options.imageSizes,
);
} else {
key += `[${as}]${href}`;
}
if (hints.has(key)) {
// duplicate hint
return;
}
hints.add(key);
const trimmed = trimOptions(options);
if (trimmed) {
emitHint(request, 'L', [href, as, trimmed]);
} else {
emitHint(request, 'L', [href, as]);
}
}
}
}
}
function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
if (enableFloat) {
if (typeof href === 'string') {
const request = resolveRequest();
if (request) {
const hints = getHints(request);
const key = 'm|' + href;
if (hints.has(key)) {
// duplicate hint
return;
}
hints.add(key);
const trimmed = trimOptions(options);
if (trimmed) {
return emitHint(request, 'm', [href, trimmed]);
} else {
return emitHint(request, 'm', href);
}
}
}
}
}
function preinitStyle(
href: string,
precedence: ?string,
options?: ?PreinitStyleOptions,
) {
if (enableFloat) {
if (typeof href === 'string') {
const request = resolveRequest();
if (request) {
const hints = getHints(request);
const key = 'S|' + href;
if (hints.has(key)) {
// duplicate hint
return;
}
hints.add(key);
const trimmed = trimOptions(options);
if (trimmed) {
return emitHint(request, 'S', [
href,
typeof precedence === 'string' ? precedence : 0,
trimmed,
]);
} else if (typeof precedence === 'string') {
return emitHint(request, 'S', [href, precedence]);
} else {
return emitHint(request, 'S', href);
}
}
}
}
}
function preinitScript(href: string, options?: ?PreinitScriptOptions) {
if (enableFloat) {
if (typeof href === 'string') {
const request = resolveRequest();
if (request) {
const hints = getHints(request);
const key = 'X|' + href;
if (hints.has(key)) {
// duplicate hint
return;
}
hints.add(key);
const trimmed = trimOptions(options);
if (trimmed) {
return emitHint(request, 'X', [href, trimmed]);
} else {
return emitHint(request, 'X', href);
}
}
}
}
}
function preinitModuleScript(
href: string,
options?: ?PreinitModuleScriptOptions,
) {
if (enableFloat) {
if (typeof href === 'string') {
const request = resolveRequest();
if (request) {
const hints = getHints(request);
const key = 'M|' + href;
if (hints.has(key)) {
// duplicate hint
return;
}
hints.add(key);
const trimmed = trimOptions(options);
if (trimmed) {
return emitHint(request, 'M', [href, trimmed]);
} else {
return emitHint(request, 'M', href);
}
}
}
}
}
// Flight normally encodes undefined as a special character however for directive option
// arguments we don't want to send unnecessary keys and bloat the payload so we create a
// trimmed object which omits any keys with null or undefined values.
// This is only typesafe because these option objects have entirely optional fields where
// null and undefined represent the same thing as no property.
function trimOptions<
T:
| PreloadImplOptions
| PreloadModuleImplOptions
| PreinitStyleOptions
| PreinitScriptOptions
| PreinitModuleScriptOptions,
>(options: ?T): ?T {
if (options == null) return null;
let hasProperties = false;
const trimmed: T = ({}: any);
for (const key in options) {
if (options[key] != null) {
hasProperties = true;
(trimmed: any)[key] = options[key];
}
}
return hasProperties ? trimmed : null;
}
function getImagePreloadKey(
href: string,
imageSrcSet: ?string,
imageSizes: ?string,
) {
let uniquePart = '';
if (typeof imageSrcSet === 'string' && imageSrcSet !== '') {
uniquePart += '[' + imageSrcSet + ']';
if (typeof imageSizes === 'string') {
uniquePart += '[' + imageSizes + ']';
}
} else {
uniquePart += '[][]' + href;
}
return `[image]${uniquePart}`;
}