Files
react/fixtures/flight-esm/server/global.js
Josh StoryandGitHub 701ac2e572 [Flight][Float] Preinitialize module imports during SSR (#27314)
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).
2023-09-27 09:53:31 -07:00

156 lines
4.1 KiB
JavaScript

'use strict';
// This is a server to host CDN distributed resources like module source files and SSR
const path = require('path');
const url = require('url');
const fs = require('fs').promises;
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');
const moduleBasePath = new URL('../src', url.pathToFileURL(__filename)).href;
const app = express();
app.use(compress());
function request(options, body) {
return new Promise((resolve, reject) => {
const req = http.request(options, res => {
resolve(res);
});
req.on('error', e => {
reject(e);
});
body.pipe(req);
});
}
app.all('/', async function (req, res, next) {
// Proxy the request to the regional server.
const proxiedHeaders = {
'X-Forwarded-Host': req.hostname,
'X-Forwarded-For': req.ips,
'X-Forwarded-Port': 3000,
'X-Forwarded-Proto': req.protocol,
};
// Proxy other headers as desired.
if (req.get('rsc-action')) {
proxiedHeaders['Content-type'] = req.get('Content-type');
proxiedHeaders['rsc-action'] = req.get('rsc-action');
} else if (req.get('Content-type')) {
proxiedHeaders['Content-type'] = req.get('Content-type');
}
const promiseForData = request(
{
host: '127.0.0.1',
port: 3001,
method: req.method,
path: '/',
headers: proxiedHeaders,
},
req
);
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.
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(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) {
console.error(`Failed to SSR: ${e.stack}`);
res.statusCode = 500;
res.end();
}
} 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 => {
res.write(data);
res.flush();
});
rscResponse.on('end', data => {
res.end();
});
} catch (e) {
console.error(`Failed to proxy request: ${e.stack}`);
res.statusCode = 500;
res.end();
}
}
});
app.use(express.static('public'));
app.use('/src', express.static('src'));
app.use(
'/node_modules/react-server-dom-esm/esm',
express.static('node_modules/react-server-dom-esm/esm')
);
app.listen(3000, () => {
console.log('Global Fizz/Webpack Server listening on port 3000...');
});
app.on('error', function (error) {
if (error.syscall !== 'listen') {
throw error;
}
switch (error.code) {
case 'EACCES':
console.error('port 3000 requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error('Port 3000 is already in use');
process.exit(1);
break;
default:
throw error;
}
});