mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[Flight Fixture] Proxy requests through the global server instead of 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.
This commit is contained in:
committed by
GitHub
parent
1175c77cf2
commit
e1afa557e7
@@ -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,
|
||||
@@ -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(
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -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');
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
@@ -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'
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -2651,7 +2651,7 @@ ansi-escapes@^4.3.1:
|
||||
dependencies:
|
||||
type-fest "^0.11.0"
|
||||
|
||||
ansi-html-community@^0.0.8:
|
||||
[email protected], 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==
|
||||
@@ -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"
|
||||
|
||||
[email protected]:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
@@ -7903,6 +7910,11 @@ [email protected]:
|
||||
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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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.');
|
||||
}
|
||||
@@ -462,5 +476,5 @@ export async function load(
|
||||
);
|
||||
return {format: 'module', source: newSrc};
|
||||
}
|
||||
return defaultLoad(url, context, defaultLoad);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user