Skip to content

Commit

Permalink
During SSR we should preinitialize imports so they can begin to be fe…
Browse files Browse the repository at this point in the history
…tched before bootstrap in the browser
  • Loading branch information
gnoff committed Aug 30, 2023
1 parent d23b8b5 commit bee5c3e
Show file tree
Hide file tree
Showing 31 changed files with 472 additions and 151 deletions.
6 changes: 2 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,7 @@ module.exports = {
},
},
{
files: [
'packages/react-native-renderer/**/*.js',
],
files: ['packages/react-native-renderer/**/*.js'],
globals: {
nativeFabricUIManager: 'readonly',
},
Expand All @@ -426,7 +424,7 @@ module.exports = {
files: ['packages/react-server-dom-webpack/**/*.js'],
globals: {
__webpack_chunk_load__: 'readonly',
__webpack_require__: 'readonly',
__webpack_require__: true,
},
},
{
Expand Down
1 change: 1 addition & 0 deletions fixtures/flight/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18
6 changes: 6 additions & 0 deletions fixtures/flight/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ module.exports = function (webpackEnv) {
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
fs.existsSync(f)
),
react: [
'react/',
'react-dom/',
'react-server-dom-webpack/',
'scheduler/',
],
},
},
infrastructureLogging: {
Expand Down
23 changes: 19 additions & 4 deletions fixtures/flight/server/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const compress = require('compression');
const chalk = require('chalk');
const express = require('express');
const http = require('http');
const React = require('react');

const {renderToPipeableStream} = require('react-dom/server');
const {createFromNodeStream} = require('react-server-dom-webpack/client');
Expand Down Expand Up @@ -62,6 +63,11 @@ if (process.env.NODE_ENV === 'development') {
webpackMiddleware(compiler, {
publicPath: paths.publicUrlOrPath.slice(0, -1),
serverSideRender: true,
headers: () => {
return {
'Cache-Control': 'no-store, must-revalidate',
};
},
})
);
app.use(webpackHotMiddleware(compiler));
Expand Down Expand Up @@ -121,9 +127,9 @@ app.all('/', async function (req, res, next) {
buildPath = path.join(__dirname, '../build/');
}
// Read the module map from the virtual file system.
const moduleMap = JSON.parse(
const ssrBundleConfig = JSON.parse(
await virtualFs.readFile(
path.join(buildPath, 'react-ssr-manifest.json'),
path.join(buildPath, 'react-ssr-bundle-config.json'),
'utf8'
)
);
Expand All @@ -138,10 +144,19 @@ app.all('/', async function (req, res, next) {
// For HTML, we're a "client" emulator that runs the client code,
// so we start by consuming the RSC payload. This needs a module
// map that reverse engineers the client-side path to the SSR path.
const root = await createFromNodeStream(rscResponse, moduleMap);
let root;
let Root = () => {
if (root) {
return root;
}
root = createFromNodeStream(rscResponse, ssrBundleConfig.ssrManifest, {
moduleLoading: ssrBundleConfig.moduleLoading,
});
return root;
};
// Render it into HTML by resolving the client components
res.set('Content-type', 'text/html');
const {pipe} = renderToPipeableStream(root, {
const {pipe} = renderToPipeableStream(React.createElement(Root), {
bootstrapScripts: mainJSChunks,
});
pipe(res);
Expand Down
7 changes: 7 additions & 0 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ClientReferenceMetadata,
SSRManifest,
StringDecoder,
ModuleLoading,
} from './ReactFlightClientConfig';

import type {HintModel} from 'react-server/src/ReactFlightServerConfig';
Expand All @@ -33,6 +34,7 @@ import {
readPartialStringChunk,
readFinalStringChunk,
createStringDecoder,
prepareDestinationForModule,
} from './ReactFlightClientConfig';

import {registerServerReference} from './ReactFlightReplyClient';
Expand Down Expand Up @@ -176,6 +178,7 @@ Chunk.prototype.then = function <T>(

export type Response = {
_bundlerConfig: SSRManifest,
_moduleLoading: ModuleLoading,
_callServer: CallServerCallback,
_chunks: Map<number, SomeChunk<any>>,
_fromJSON: (key: string, value: JSONValue) => any,
Expand Down Expand Up @@ -704,11 +707,13 @@ function missingCall() {

export function createResponse(
bundlerConfig: SSRManifest,
moduleLoading: ModuleLoading,
callServer: void | CallServerCallback,
): Response {
const chunks: Map<number, SomeChunk<any>> = new Map();
const response: Response = {
_bundlerConfig: bundlerConfig,
_moduleLoading: moduleLoading,
_callServer: callServer !== undefined ? callServer : missingCall,
_chunks: chunks,
_stringDecoder: createStringDecoder(),
Expand Down Expand Up @@ -771,6 +776,8 @@ function resolveModule(
clientReferenceMetadata,
);

prepareDestinationForModule(response._moduleLoading, clientReferenceMetadata);

// TODO: Add an option to encode modules that are lazy loaded.
// For now we preload all modules as early as possible since it's likely
// that we'll need them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export const resolveServerReference = $$$config.resolveServerReference;
export const preloadModule = $$$config.preloadModule;
export const requireModule = $$$config.requireModule;
export const dispatchHint = $$$config.dispatchHint;
export const prepareDestinationForModule =
$$$config.prepareDestinationForModule;
export const usedWithSSR = true;

export opaque type Source = mixed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = false;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

export type Response = any;
export opaque type ModuleLoading = mixed;
export opaque type SSRManifest = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
Expand All @@ -20,4 +21,5 @@ export const resolveClientReference: any = null;
export const resolveServerReference: any = null;
export const preloadModule: any = null;
export const requireModule: any = null;
export const prepareDestinationForModule: any = null;
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

export type Response = any;
export opaque type ModuleLoading = mixed;
export opaque type SSRManifest = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
Expand All @@ -20,4 +21,5 @@ export const resolveClientReference: any = null;
export const resolveServerReference: any = null;
export const preloadModule: any = null;
export const requireModule: any = null;
export const prepareDestinationForModule: any = null;
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

export * from 'react-client/src/ReactFlightClientConfigNode';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackServer';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

export * from 'react-client/src/ReactFlightClientConfigNode';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigNodeBundler';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerNode';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigTargetWebpackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,14 @@ export function dispatchHint(code: string, model: HintModel): void {
}
}
}

export function preinitModulesForSSR(href: string, crossOrigin: ?string) {
const dispatcher = ReactDOMCurrentDispatcher.current;
if (dispatcher) {
if (typeof crossOrigin === 'string') {
dispatcher.preinit(href, {as: 'script', crossOrigin});
} else {
dispatcher.preinit(href, {as: 'script'});
}
}
}
1 change: 1 addition & 0 deletions packages/react-noop-renderer/src/ReactNoopFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const {createResponse, processBinaryChunk, getRoot, close} = ReactFlightClient({
resolveClientReference(bundlerConfig: null, idx: string) {
return idx;
},
prepareDestinationForModule(moduleLoading: null, metadata: string) {},
preloadModule(idx: string) {},
requireModule(idx: string) {
return readModule(idx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ import type {
RejectedThenable,
} from 'shared/ReactTypes';

import type {ClientReferenceMetadata as SharedClientReferenceMetadata} from './shared/ReactFlightClientReference';
import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';

import {
ID,
CHUNKS,
NAME,
isAsyncClientReference,
} from './shared/ReactFlightClientReference';
import {prepareDestinationWithChunks} from 'react-client/src/ReactFlightClientConfig';

export type SSRManifest = {
[clientId: string]: {
[clientExportName: string]: ClientReference<any>,
Expand All @@ -23,12 +34,7 @@ export type ServerManifest = void;

export type ServerReferenceId = string;

export opaque type ClientReferenceMetadata = {
id: string,
chunks: Array<string>,
name: string,
async?: boolean,
};
export opaque type ClientReferenceMetadata = SharedClientReferenceMetadata;

// eslint-disable-next-line no-unused-vars
export opaque type ClientReference<T> = {
Expand All @@ -37,12 +43,25 @@ export opaque type ClientReference<T> = {
async?: boolean,
};

// 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,
metadata: ClientReferenceMetadata,
) {
prepareDestinationWithChunks(moduleLoading, metadata[CHUNKS]);
}

export function resolveClientReference<T>(
bundlerConfig: SSRManifest,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
const moduleExports = bundlerConfig[metadata.id];
let resolvedModuleData = moduleExports[metadata.name];
const moduleExports = bundlerConfig[metadata[ID]];
let resolvedModuleData = moduleExports[metadata[NAME]];
let name;
if (resolvedModuleData) {
// The potentially aliased name.
Expand All @@ -53,17 +72,17 @@ export function resolveClientReference<T>(
if (!resolvedModuleData) {
throw new Error(
'Could not find the module "' +
metadata.id +
metadata[ID] +
'" in the React SSR Manifest. ' +
'This is probably a bug in the React Server Components bundler.',
);
}
name = metadata.name;
name = metadata[NAME];
}
return {
specifier: resolvedModuleData.specifier,
name: name,
async: metadata.async,
async: isAsyncClientReference(metadata),
};
}

Expand Down
Loading

0 comments on commit bee5c3e

Please sign in to comment.