mirror of
https://github.com/facebook/react.git
synced 2026-02-24 20:53:03 +00:00
Currently when we SSR a Flight response we do not emit any resources for module imports. This means that when the client hydrates it won't have already loaded the necessary scripts to satisfy the Imports defined in the Flight payload which will lead to a delay in hydration completing. This change updates `react-server-dom-webpack` and `react-server-dom-esm` to emit async script tags in the head when we encounter a modules in the flight response. To support this we need some additional server configuration. We need to know the path prefix for chunk loading and whether the chunks will load with CORS or not (and if so with what configuration).
121 lines
3.6 KiB
JavaScript
121 lines
3.6 KiB
JavaScript
/**
|
|
* 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 {
|
|
Thenable,
|
|
FulfilledThenable,
|
|
RejectedThenable,
|
|
} from 'shared/ReactTypes';
|
|
import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
|
|
|
|
export type SSRModuleMap = string; // Module root path
|
|
|
|
export type ServerManifest = string; // Module root path
|
|
|
|
export type ServerReferenceId = string;
|
|
|
|
import {prepareDestinationForModuleImpl} from 'react-client/src/ReactFlightClientConfig';
|
|
|
|
export opaque type ClientReferenceMetadata = [
|
|
string, // module path
|
|
string, // export name
|
|
];
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
export opaque type ClientReference<T> = {
|
|
specifier: string,
|
|
name: string,
|
|
};
|
|
|
|
// The reason this function needs to defined here in this file instead of just
|
|
// being exported directly from the WebpackDestination... file is because the
|
|
// ClientReferenceMetadata is opaque and we can't unwrap it there.
|
|
// This should get inlined and we could also just implement an unwrapping function
|
|
// though that risks it getting used in places it shouldn't be. This is unfortunate
|
|
// but currently it seems to be the best option we have.
|
|
export function prepareDestinationForModule(
|
|
moduleLoading: ModuleLoading,
|
|
nonce: ?string,
|
|
metadata: ClientReferenceMetadata,
|
|
) {
|
|
prepareDestinationForModuleImpl(moduleLoading, metadata[0], nonce);
|
|
}
|
|
|
|
export function resolveClientReference<T>(
|
|
bundlerConfig: SSRModuleMap,
|
|
metadata: ClientReferenceMetadata,
|
|
): ClientReference<T> {
|
|
const baseURL = bundlerConfig;
|
|
return {
|
|
specifier: baseURL + metadata[0],
|
|
name: metadata[1],
|
|
};
|
|
}
|
|
|
|
export function resolveServerReference<T>(
|
|
config: ServerManifest,
|
|
id: ServerReferenceId,
|
|
): ClientReference<T> {
|
|
const baseURL: string = config;
|
|
const idx = id.lastIndexOf('#');
|
|
const exportName = id.slice(idx + 1);
|
|
const fullURL = id.slice(0, idx);
|
|
if (!fullURL.startsWith(baseURL)) {
|
|
throw new Error(
|
|
'Attempted to load a Server Reference outside the hosted root.',
|
|
);
|
|
}
|
|
return {specifier: fullURL, name: exportName};
|
|
}
|
|
|
|
const asyncModuleCache: Map<string, Thenable<any>> = new Map();
|
|
|
|
export function preloadModule<T>(
|
|
metadata: ClientReference<T>,
|
|
): null | Thenable<any> {
|
|
const existingPromise = asyncModuleCache.get(metadata.specifier);
|
|
if (existingPromise) {
|
|
if (existingPromise.status === 'fulfilled') {
|
|
return null;
|
|
}
|
|
return existingPromise;
|
|
} else {
|
|
// $FlowFixMe[unsupported-syntax]
|
|
const modulePromise: Thenable<T> = import(metadata.specifier);
|
|
modulePromise.then(
|
|
value => {
|
|
const fulfilledThenable: FulfilledThenable<mixed> =
|
|
(modulePromise: any);
|
|
fulfilledThenable.status = 'fulfilled';
|
|
fulfilledThenable.value = value;
|
|
},
|
|
reason => {
|
|
const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any);
|
|
rejectedThenable.status = 'rejected';
|
|
rejectedThenable.reason = reason;
|
|
},
|
|
);
|
|
asyncModuleCache.set(metadata.specifier, modulePromise);
|
|
return modulePromise;
|
|
}
|
|
}
|
|
|
|
export function requireModule<T>(metadata: ClientReference<T>): T {
|
|
let moduleExports;
|
|
// We assume that preloadModule has been called before, which
|
|
// should have added something to the module cache.
|
|
const promise: any = asyncModuleCache.get(metadata.specifier);
|
|
if (promise.status === 'fulfilled') {
|
|
moduleExports = promise.value;
|
|
} else {
|
|
throw promise.reason;
|
|
}
|
|
return moduleExports[metadata.name];
|
|
}
|