Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flight][Float] Preinitialize module imports during SSR #27314

Merged
merged 2 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fixtures/flight-esm/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v18
38 changes: 28 additions & 10 deletions fixtures/flight-esm/server/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,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-esm/client');
Expand Down Expand Up @@ -62,23 +63,39 @@ app.all('/', async function (req, res, next) {
if (req.accepts('text/html')) {
try {
const rscResponse = await promiseForData;

const moduleBaseURL = '/src';

// For HTML, we're a "client" emulator that runs the client code,
// so we start by consuming the RSC payload. This needs the local file path
// to load the source files from as well as the URL path for preloads.
const root = await createFromNodeStream(
rscResponse,
moduleBasePath,
moduleBaseURL
);

let root;
let Root = () => {
if (root) {
return React.use(root);
}

return React.use(
(root = createFromNodeStream(
rscResponse,
moduleBasePath,
moduleBaseURL
))
);
};
// Render it into HTML by resolving the client components
res.set('Content-type', 'text/html');
const {pipe} = renderToPipeableStream(root, {
// TODO: bootstrapModules inserts a preload before the importmap which causes
// the import map to be invalid. We need to fix that in Float somehow.
// bootstrapModules: ['/src/index.js'],
const {pipe} = renderToPipeableStream(React.createElement(Root), {
importMap: {
imports: {
react: 'https://esm.sh/react@experimental?pin=v124&dev',
'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev',
'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/',
'react-server-dom-esm/client':
'/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js',
},
},
bootstrapModules: ['/src/index.js'],
});
pipe(res);
} catch (e) {
Expand All @@ -89,6 +106,7 @@ app.all('/', async function (req, res, next) {
} else {
try {
const rscResponse = await promiseForData;

// For other request, we pass-through the RSC payload.
res.set('Content-type', 'text/x-component');
rscResponse.on('data', data => {
Expand Down
20 changes: 1 addition & 19 deletions fixtures/flight-esm/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@ import {getServerState} from './ServerState.js';

const h = React.createElement;

const importMap = {
imports: {
react: 'https://esm.sh/react@experimental?pin=v124&dev',
'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev',
'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/',
'react-server-dom-esm/client':
'/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js',
},
};

export default async function App() {
const res = await fetch('http://localhost:3001/todos');
const todos = await res.json();
Expand All @@ -42,12 +32,6 @@ export default async function App() {
rel: 'stylesheet',
href: '/src/style.css',
precedence: 'default',
}),
h('script', {
type: 'importmap',
dangerouslySetInnerHTML: {
__html: JSON.stringify(importMap),
},
})
),
h(
Expand Down Expand Up @@ -84,9 +68,7 @@ export default async function App() {
'Like'
)
)
),
// TODO: Move this to bootstrapModules.
h('script', {type: 'module', src: '/src/index.js'})
)
)
);
}
22 changes: 11 additions & 11 deletions fixtures/flight-esm/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -540,17 +540,17 @@ raw-body@2.5.2:
unpipe "1.0.0"

react-dom@experimental:
version "0.0.0-experimental-018c58c9c-20230601"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-018c58c9c-20230601.tgz#2cc0ac824b83bab2ac1c6187f241dbd5dcd5201b"
integrity sha512-hwRsyoG1R3Tub0nUa72YvNcqPvU+pTcr9dadOnUCKKfSiYVbBCy7LxmkqLauCD8OjNJMlwtMgG4UAgtidclYGQ==
version "0.0.0-experimental-b9be4537c-20230905"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-b9be4537c-20230905.tgz#b078d6d06041e0c98ce5a2f5e9ff26a2e308eb41"
integrity sha512-veAFNVj81lUYhYlucYm3kbj2BhakG57XYkWC/QHVEZDk4Hm2qxM9RUk7gn8dWs9Eq7KR6Q+JWiSH3ZbObQTV9g==
dependencies:
loose-envify "^1.1.0"
scheduler "0.0.0-experimental-018c58c9c-20230601"
scheduler "0.0.0-experimental-b9be4537c-20230905"

react@experimental:
version "0.0.0-experimental-018c58c9c-20230601"
resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-018c58c9c-20230601.tgz#ab04d1243c8f83b0166ed342056fa6b38ab2cd23"
integrity sha512-nSQIBsZ26Ii899pZ9cRt/6uQLbIUEAcDIivvAQyaHp4pWm289aB+7AK7VCWojAJIf4OStCuWs2berZsk4mzLVg==
version "0.0.0-experimental-b9be4537c-20230905"
resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-b9be4537c-20230905.tgz#3c2352b42b8024544a12dcd96f2700313cebcb6b"
integrity sha512-QNeK74S7AU94j4vCxet2S76HqxpF6CJo1pG3XcgY2NravyXdWYszrRDNHrfu86gGNwAQvSU+YpStYn/i0b9tLA==
dependencies:
loose-envify "^1.1.0"

Expand Down Expand Up @@ -588,10 +588,10 @@ safe-buffer@5.1.2:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==

scheduler@0.0.0-experimental-018c58c9c-20230601:
version "0.0.0-experimental-018c58c9c-20230601"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-018c58c9c-20230601.tgz#4f083614f8e857bab63dd90b4b37b03783dafe6b"
integrity sha512-otUM7AAAnCoJ5/0jTQwUQ7NhxjgcPEdrfzW7NfkpocrDoTUbql1kIGIhj9L9POMVFDI/wcZzRNK/oIEWsB4DPw==
scheduler@0.0.0-experimental-b9be4537c-20230905:
version "0.0.0-experimental-b9be4537c-20230905"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-b9be4537c-20230905.tgz#f0fe5a710ce15a9d637c28e9f019a4100e1f3f34"
integrity sha512-V5P9LOS+c5CG7qaCJu+Qgcz9eh/dP4nBszj3w1MCgZnMtAna6+J8ZuuUnRDMeY86F8KH+cY8Q5beIvAL2noMzA==
dependencies:
loose-envify "^1.1.0"

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
41 changes: 34 additions & 7 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,12 +127,13 @@ 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 ssrManifest = JSON.parse(
await virtualFs.readFile(
path.join(buildPath, 'react-ssr-manifest.json'),
'utf8'
)
);

// Read the entrypoints containing the initial JS to bootstrap everything.
// For other pages, the chunks in the RSC payload are enough.
const mainJSChunks = JSON.parse(
Expand All @@ -138,15 +145,35 @@ 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, formState} = await createFromNodeStream(
rscResponse,
moduleMap
);

// This is a bad hack to set the form state after SSR has started. It works
// because we block the root component until we have the form state and
// any form that reads it necessarily will come later. It also only works
// because the formstate type is an object which may change in the future
const lazyFormState = [];

let cachedResult = null;
async function getRootAndFormState() {
const {root, formState} = await createFromNodeStream(
rscResponse,
ssrManifest
);
// We shouldn't be assuming formState is an object type but at the moment
// we have no way of setting the form state from within the render
Object.assign(lazyFormState, formState);
return root;
}
let Root = () => {
if (!cachedResult) {
cachedResult = getRootAndFormState();
}
return React.use(cachedResult);
};
// 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,
experimental_formState: formState,
experimental_formState: lazyFormState,
});
pipe(res);
} catch (e) {
Expand Down
20 changes: 17 additions & 3 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import type {LazyComponent} from 'react/src/ReactLazy';
import type {
ClientReference,
ClientReferenceMetadata,
SSRManifest,
SSRModuleMap,
StringDecoder,
ModuleLoading,
} from './ReactFlightClientConfig';

import type {
Expand All @@ -36,6 +37,7 @@ import {
readPartialStringChunk,
readFinalStringChunk,
createStringDecoder,
prepareDestinationForModule,
} from './ReactFlightClientConfig';

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

export type Response = {
_bundlerConfig: SSRManifest,
_bundlerConfig: SSRModuleMap,
_moduleLoading: ModuleLoading,
_callServer: CallServerCallback,
_nonce: ?string,
_chunks: Map<number, SomeChunk<any>>,
_fromJSON: (key: string, value: JSONValue) => any,
_stringDecoder: StringDecoder,
Expand Down Expand Up @@ -706,13 +710,17 @@ function missingCall() {
}

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

prepareDestinationForModule(
response._moduleLoading,
response._nonce,
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 @@ -25,7 +25,8 @@

declare var $$$config: any;

export opaque type SSRManifest = mixed;
export opaque type ModuleLoading = mixed;
export opaque type SSRModuleMap = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
export opaque type ClientReferenceMetadata = mixed;
Expand All @@ -35,6 +36,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,7 @@
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigESMBundler';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigBundlerESM';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigTargetESMBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = false;
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,7 +11,8 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

export type Response = any;
export opaque type SSRManifest = mixed;
export opaque type ModuleLoading = mixed;
export opaque type SSRModuleMap = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
export opaque type ClientReferenceMetadata = mixed;
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,7 +11,8 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

export type Response = any;
export opaque type SSRManifest = mixed;
export opaque type ModuleLoading = mixed;
export opaque type SSRModuleMap = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
export opaque type ClientReferenceMetadata = mixed;
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 @@ -7,7 +7,8 @@
* @flow
*/

export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigESMBundler';
export * from 'react-client/src/ReactFlightClientConfigNode';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigBundlerESM';
export * from 'react-server-dom-esm/src/ReactFlightClientConfigTargetESMServer';
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,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;
Loading