Skip to content

Commit

Permalink
[Flight Fixture] Proxy requests through the global server instead of …
Browse files Browse the repository at this point in the history
…directly (#26257)

This proxies requests through the global server instead of requesting
RSC responses from the regional server. This is a bit closer to
idiomatic, and closer to SSR.

This also wires up HMR using the Middleware technique instead of server.
This will be an important part of RSC compatibility because there will
be a `react-refresh` aspect to the integration.

This convention uses `Accept` header to branch a URL between HTML/RSC
but it could be anything really. Special headers, URLs etc. We might be
more opinionated about this in the future but now it's up to the router.

Some fixes for Node 16/17 support in the loader and fetch polyfill.
  • Loading branch information
sebmarkbage committed Mar 1, 2023
1 parent 38509cc commit 40755c0
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 38 deletions.
9 changes: 8 additions & 1 deletion fixtures/flight/config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,13 @@ module.exports = function (webpackEnv) {
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: paths.appIndexJs,
entry: isEnvProduction
? [paths.appIndexJs]
: [
paths.appIndexJs,
// HMR client
'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
],
output: {
// The build folder.
path: paths.appBuild,
Expand Down Expand Up @@ -571,6 +577,7 @@ module.exports = function (webpackEnv) {
].filter(Boolean),
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
Expand Down
4 changes: 3 additions & 1 deletion fixtures/flight/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.2",
"terser-webpack-plugin": "^5.2.5",
"undici": "^5.20.0",
"webpack": "^5.64.4",
"webpack-dev-middleware": "^5.3.1"
"webpack-dev-middleware": "^5.3.1",
"webpack-hot-middleware": "^2.25.3"
},
"scripts": {
"predev": "cp -r ../../build/oss-experimental/* ./node_modules/",
Expand Down
79 changes: 72 additions & 7 deletions fixtures/flight/server/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,69 @@ const chalk = require('chalk');
const express = require('express');
const app = express();

const http = require('http');

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) {
if (req.accepts('text/html')) {
// Pass-through to the html file
next();
return;
}

// 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');
}

const promiseForData = request(
{
host: '127.0.0.1',
port: 3001,
method: req.method,
path: '/',
headers: proxiedHeaders,
},
req
);

try {
const rscResponse = await promiseForData;
res.set('Content-type', 'text/x-component');
rscResponse.pipe(res);
} catch (e) {
console.error(`Failed to proxy request: ${e.stack}`);
res.statusCode = 500;
res.end();
}
});

if (process.env.NODE_ENV === 'development') {
// In development we host the Webpack server for live bundling.
const webpack = require('webpack');
const webpackMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const paths = require('../config/paths');
const configFactory = require('../config/webpack.config');
const getClientEnvironment = require('../config/env');
Expand All @@ -59,13 +116,21 @@ if (process.env.NODE_ENV === 'development') {

// Create a webpack compiler that is configured with custom messages.
const compiler = webpack(config);
const devMiddleware = {
writeToDisk: filePath => {
return /(react-client-manifest|react-ssr-manifest)\.json$/.test(filePath);
},
publicPath: paths.publicUrlOrPath.slice(0, -1),
};
app.use(webpackMiddleware(compiler, devMiddleware));
app.use(
webpackMiddleware(compiler, {
writeToDisk: filePath => {
return /(react-client-manifest|react-ssr-manifest)\.json$/.test(
filePath
);
},
publicPath: paths.publicUrlOrPath.slice(0, -1),
})
);
app.use(
webpackHotMiddleware(compiler, {
/* Options */
})
);
app.use(express.static('public'));
} else {
// In production we host the static build output.
Expand Down
15 changes: 5 additions & 10 deletions fixtures/flight/server/region.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ babelRegister({
plugins: ['@babel/transform-modules-commonjs'],
});

if (typeof fetch === 'undefined') {
// Patch fetch for earlier Node versions.
global.fetch = require('undici').fetch;
}

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
Expand All @@ -51,19 +56,11 @@ app.get('/', async function (req, res) {
'utf8'
);
const App = m.default.default || m.default;
res.setHeader('Access-Control-Allow-Origin', '*');
const moduleMap = JSON.parse(data);
const {pipe} = renderToPipeableStream(React.createElement(App), moduleMap);
pipe(res);
});

app.options('/', function (req, res) {
res.setHeader('Allow', 'Allow: GET,HEAD,POST');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'rsc-action');
res.end();
});

app.post('/', bodyParser.text(), async function (req, res) {
const {renderToPipeableStream} = await import(
'react-server-dom-webpack/server'
Expand All @@ -81,13 +78,11 @@ app.post('/', bodyParser.text(), async function (req, res) {
const args = JSON.parse(req.body);
const result = action.apply(null, args);

res.setHeader('Access-Control-Allow-Origin', '*');
const {pipe} = renderToPipeableStream(result, {});
pipe(res);
});

app.get('/todos', function (req, res) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json([
{
id: 1,
Expand Down
10 changes: 7 additions & 3 deletions fixtures/flight/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import ReactDOM from 'react-dom/client';
import ReactServerDOMReader from 'react-server-dom-webpack/client';

let data = ReactServerDOMReader.createFromFetch(
fetch('http://localhost:3001'),
fetch('/', {
headers: {
Accept: 'text/x-component',
},
}),
{
callServer(id, args) {
const response = fetch('http://localhost:3001', {
const response = fetch('/', {
method: 'POST',
cors: 'cors',
headers: {
Accept: 'text/x-component',
'rsc-action': JSON.stringify({filepath: id.id, name: id.name}),
},
body: JSON.stringify(args),
Expand Down
30 changes: 29 additions & 1 deletion fixtures/flight/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2651,7 +2651,7 @@ ansi-escapes@^4.3.1:
dependencies:
type-fest "^0.11.0"

ansi-html-community@^0.0.8:
ansi-html-community@0.0.8, ansi-html-community@^0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41"
integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==
Expand Down Expand Up @@ -3061,6 +3061,13 @@ buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"

busboy@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
dependencies:
streamsearch "^1.1.0"

bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
Expand Down Expand Up @@ -7903,6 +7910,11 @@ statuses@2.0.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==

streamsearch@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==

string-length@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1"
Expand Down Expand Up @@ -8388,6 +8400,13 @@ undefsafe@^2.0.5:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==

undici@^5.20.0:
version "5.20.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.20.0.tgz#6327462f5ce1d3646bcdac99da7317f455bcc263"
integrity sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==
dependencies:
busboy "^1.6.0"

unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
Expand Down Expand Up @@ -8548,6 +8567,15 @@ webpack-dev-middleware@^5.3.1:
range-parser "^1.2.1"
schema-utils "^4.0.0"

webpack-hot-middleware@^2.25.3:
version "2.25.3"
resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.3.tgz#be343ce2848022cfd854dd82820cd730998c6794"
integrity sha512-IK/0WAHs7MTu1tzLTjio73LjS3Ov+VvBKQmE8WPlJutgG5zT6Urgq/BbAdRrHTRpyzK0dvAvFh1Qg98akxgZpA==
dependencies:
ansi-html-community "0.0.8"
html-entities "^2.1.0"
strip-ansi "^6.0.0"

webpack-sources@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,17 @@ async function parseExportNamesInto(
if (typeof source !== 'string') {
throw new Error('Expected the transformed source to be a string.');
}
const {body: childBody} = acorn.parse(source, {
ecmaVersion: '2019',
sourceType: 'module',
});
let childBody;
try {
childBody = acorn.parse(source, {
ecmaVersion: '2024',
sourceType: 'module',
}).body;
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
continue;
}
await parseExportNamesInto(childBody, names, url, loader);
continue;
}
Expand Down Expand Up @@ -381,10 +388,17 @@ async function transformModuleIfNeeded(
return source;
}

const {body} = acorn.parse(source, {
ecmaVersion: '2019',
sourceType: 'module',
});
let body;
try {
body = acorn.parse(source, {
ecmaVersion: '2024',
sourceType: 'module',
}).body;
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
return source;
}

let useClient = false;
let useServer = false;
Expand Down Expand Up @@ -450,8 +464,8 @@ export async function load(
context: LoadContext,
defaultLoad: LoadFunction,
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
if (context.format === 'module') {
const result = await defaultLoad(url, context, defaultLoad);
const result = await defaultLoad(url, context, defaultLoad);
if (result.format === 'module') {
if (typeof result.source !== 'string') {
throw new Error('Expected source to have been loaded into a string.');
}
Expand All @@ -462,5 +476,5 @@ export async function load(
);
return {format: 'module', source: newSrc};
}
return defaultLoad(url, context, defaultLoad);
return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,17 @@ module.exports = function register() {
return originalCompile.apply(this, arguments);
}

const {body} = acorn.parse(content, {
ecmaVersion: '2019',
sourceType: 'source',
});
let body;
try {
body = acorn.parse(content, {
ecmaVersion: '2024',
sourceType: 'source',
}).body;
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
return originalCompile.apply(this, arguments);
}

let useClient = false;
let useServer = false;
Expand Down

0 comments on commit 40755c0

Please sign in to comment.