Skip to content

Commit 244d1aa

Browse files
committed
Updates flight client and associated webpack plugin to preinitialize imports when resolving client references on the server (SSR). The result is that the SSR stream will end up streaming in async scripts for the chunks needed to hydrate the SSR'd content instead of waiting for the flight payload to start processing rows on the client to discover imports there.
On the client however we need to be able to load the required chunks for a given import. We can't just use webpack's chunk loading because we don't have the chunkIds and are only transmitting the filepath. We implement our own chunk loading implementation which mimics webpack's with some differences. Namely there is no explicit timeout, we wait until the network fails if an earlier load or error even does not happen first. One consequence of this approach is we may insert the same script twice for a chunk, once during SSR, and again when the flight client starts processing the flight payload for hydration. Since chunks register modules the operation is idempotent and as long as there is some cache-control in place for the resource the network requests should not be duplicated. This does mean however that it is important that if a chunk contains the webpack runtime it is not ever loaded using this custom loader implementation.
1 parent 493f72b commit 244d1aa

33 files changed

Lines changed: 462 additions & 111 deletions

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ module.exports = {
426426
files: ['packages/react-server-dom-webpack/**/*.js'],
427427
globals: {
428428
__webpack_chunk_load__: 'readonly',
429-
__webpack_require__: 'readonly',
429+
__webpack_require__: true,
430430
},
431431
},
432432
{

fixtures/flight/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v18

fixtures/flight/config/webpack.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ module.exports = function (webpackEnv) {
248248
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
249249
fs.existsSync(f)
250250
),
251+
react: [
252+
'react/',
253+
'react-dom/',
254+
'react-server-dom-webpack/',
255+
'scheduler/',
256+
],
251257
},
252258
},
253259
infrastructureLogging: {

fixtures/flight/server/global.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const compress = require('compression');
3333
const chalk = require('chalk');
3434
const express = require('express');
3535
const http = require('http');
36+
const React = require('react');
3637

3738
const {renderToPipeableStream} = require('react-dom/server');
3839
const {createFromNodeStream} = require('react-server-dom-webpack/client');
@@ -62,6 +63,11 @@ if (process.env.NODE_ENV === 'development') {
6263
webpackMiddleware(compiler, {
6364
publicPath: paths.publicUrlOrPath.slice(0, -1),
6465
serverSideRender: true,
66+
headers: () => {
67+
return {
68+
'Cache-Control': 'no-store, must-revalidate',
69+
};
70+
},
6571
})
6672
);
6773
app.use(webpackHotMiddleware(compiler));
@@ -121,9 +127,9 @@ app.all('/', async function (req, res, next) {
121127
buildPath = path.join(__dirname, '../build/');
122128
}
123129
// Read the module map from the virtual file system.
124-
const moduleMap = JSON.parse(
130+
const ssrBundleConfig = JSON.parse(
125131
await virtualFs.readFile(
126-
path.join(buildPath, 'react-ssr-manifest.json'),
132+
path.join(buildPath, 'react-ssr-bundle-config.json'),
127133
'utf8'
128134
)
129135
);
@@ -138,10 +144,21 @@ app.all('/', async function (req, res, next) {
138144
// For HTML, we're a "client" emulator that runs the client code,
139145
// so we start by consuming the RSC payload. This needs a module
140146
// map that reverse engineers the client-side path to the SSR path.
141-
const root = await createFromNodeStream(rscResponse, moduleMap);
147+
let root;
148+
let Root = () => {
149+
if (root) {
150+
return root;
151+
}
152+
root = createFromNodeStream(
153+
rscResponse,
154+
ssrBundleConfig.chunkLoading,
155+
ssrBundleConfig.ssrManifest
156+
);
157+
return root;
158+
};
142159
// Render it into HTML by resolving the client components
143160
res.set('Content-type', 'text/html');
144-
const {pipe} = renderToPipeableStream(root, {
161+
const {pipe} = renderToPipeableStream(React.createElement(Root), {
145162
bootstrapScripts: mainJSChunks,
146163
});
147164
pipe(res);
@@ -173,7 +190,6 @@ app.all('/', async function (req, res, next) {
173190
if (process.env.NODE_ENV === 'development') {
174191
app.use(express.static('public'));
175192
} else {
176-
// In production we host the static build output.
177193
app.use(express.static('build'));
178194
}
179195

packages/react-client/src/ReactFlightClient.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {LazyComponent} from 'react/src/ReactLazy';
1313
import type {
1414
ClientReference,
1515
ClientReferenceMetadata,
16+
ChunkLoading,
1617
SSRManifest,
1718
StringDecoder,
1819
} from './ReactFlightClientConfig';
@@ -32,6 +33,7 @@ import {
3233
readFinalStringChunk,
3334
createStringDecoder,
3435
usedWithSSR,
36+
prepareDestinationForModule,
3537
} from './ReactFlightClientConfig';
3638

3739
import {
@@ -174,6 +176,7 @@ Chunk.prototype.then = function <T>(
174176

175177
export type Response = {
176178
_bundlerConfig: SSRManifest,
179+
_chunkLoading: ChunkLoading,
177180
_callServer: CallServerCallback,
178181
_chunks: Map<number, SomeChunk<any>>,
179182
_fromJSON: (key: string, value: JSONValue) => any,
@@ -707,11 +710,13 @@ function missingCall() {
707710

708711
export function createResponse(
709712
bundlerConfig: SSRManifest,
713+
chunkLoading: ChunkLoading,
710714
callServer: void | CallServerCallback,
711715
): Response {
712716
const chunks: Map<number, SomeChunk<any>> = new Map();
713717
const response: Response = {
714718
_bundlerConfig: bundlerConfig,
719+
_chunkLoading: chunkLoading,
715720
_callServer: callServer !== undefined ? callServer : missingCall,
716721
_chunks: chunks,
717722
_stringDecoder: createStringDecoder(),
@@ -769,6 +774,9 @@ function resolveModule(
769774
response,
770775
model,
771776
);
777+
778+
prepareDestinationForModule(response._chunkLoading, clientReferenceMetadata);
779+
772780
const clientReference = resolveClientReference<$FlowFixMe>(
773781
response._bundlerConfig,
774782
clientReferenceMetadata,

packages/react-client/src/forks/ReactFlightClientConfig.custom.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
declare var $$$config: any;
2727

28+
export opaque type ChunkLoading = mixed;
2829
export opaque type SSRManifest = mixed;
2930
export opaque type ServerManifest = mixed;
3031
export opaque type ServerReferenceId = string;
@@ -36,6 +37,8 @@ export const preloadModule = $$$config.preloadModule;
3637
export const requireModule = $$$config.requireModule;
3738
export const dispatchHint = $$$config.dispatchHint;
3839
export const usedWithSSR = true;
40+
export const prepareDestinationForModule =
41+
$$$config.prepareDestinationForModule;
3942

4043
export opaque type Source = mixed;
4144

packages/react-client/src/forks/ReactFlightClientConfig.dom-browser.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*/
99

1010
export * from 'react-client/src/ReactFlightClientConfigBrowser';
11-
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
11+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
12+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser';
13+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackDestinationClient';
1214
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
1315
export const usedWithSSR = false;

packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export * from 'react-client/src/ReactFlightClientConfigBrowser';
1111
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
1212

1313
export type Response = any;
14+
export opaque type ChunkLoading = mixed;
1415
export opaque type SSRManifest = mixed;
1516
export opaque type ServerManifest = mixed;
1617
export opaque type ServerReferenceId = string;

packages/react-client/src/forks/ReactFlightClientConfig.dom-edge-webpack.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*/
99

1010
export * from 'react-client/src/ReactFlightClientConfigBrowser';
11-
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
11+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
12+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackEdge';
13+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackDestinationServer';
1214
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
1315
export const usedWithSSR = true;

packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*/
99

1010
export * from 'react-client/src/ReactFlightClientConfigBrowser';
11-
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
11+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpack';
12+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigBundlerWebpackBrowser';
13+
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackDestinationClient';
1214
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
1315
export const usedWithSSR = true;

0 commit comments

Comments
 (0)