'use strict';
const path = require('path');
process.env.BABEL_ENV = process.env.NODE_ENV;
const babelRegister = require('@babel/register');
babelRegister({
babelrc: false,
ignore: [
/\/(build|node_modules)\//,
function (file) {
if ((path.dirname(file) + '/').startsWith(__dirname + '/')) {
return true;
}
return false;
},
],
presets: ['@babel/preset-react'],
});
require('../config/env');
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-unbundled/client');
const {PassThrough} = require('stream');
const app = express();
app.use(compress());
if (process.env.NODE_ENV === 'development') {
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');
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
const config = configFactory('development');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const compiler = webpack(config);
app.use(
webpackMiddleware(compiler, {
publicPath: paths.publicUrlOrPath.slice(0, -1),
serverSideRender: true,
headers: () => {
return {
'Cache-Control': 'no-store, must-revalidate',
};
},
})
);
app.use(webpackHotMiddleware(compiler));
}
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);
});
}
async function renderApp(req, res, next) {
const proxiedHeaders = {
'X-Forwarded-Host': req.hostname,
'X-Forwarded-For': req.ips,
'X-Forwarded-Port': 3000,
'X-Forwarded-Proto': req.protocol,
};
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');
}
if (req.headers['cache-control']) {
proxiedHeaders['Cache-Control'] = req.get('cache-control');
}
if (req.get('rsc-request-id')) {
proxiedHeaders['rsc-request-id'] = req.get('rsc-request-id');
}
const requestsPrerender = req.path === '/prerender';
const promiseForData = request(
{
host: '127.0.0.1',
port: 3001,
method: req.method,
path: requestsPrerender ? '/?prerender=1' : '/',
headers: proxiedHeaders,
},
req
);
if (req.accepts('text/html')) {
try {
const rscResponse = await promiseForData;
let virtualFs;
let buildPath;
if (process.env.NODE_ENV === 'development') {
const {devMiddleware} = res.locals.webpack;
virtualFs = devMiddleware.outputFileSystem.promises;
buildPath = devMiddleware.stats.toJson().outputPath;
} else {
virtualFs = fs;
buildPath = path.join(__dirname, '../build/');
}
const serverConsumerManifest = JSON.parse(
await virtualFs.readFile(
path.join(buildPath, 'react-ssr-manifest.json'),
'utf8'
)
);
const mainJSChunks = JSON.parse(
await virtualFs.readFile(
path.join(buildPath, 'entrypoint-manifest.json'),
'utf8'
)
).main.js;
const rscResponse1 = new PassThrough();
const rscResponse2 = new PassThrough();
rscResponse.pipe(rscResponse1);
rscResponse.pipe(rscResponse2);
const {formState} = await createFromNodeStream(
rscResponse1,
serverConsumerManifest
);
rscResponse1.end();
let cachedResult;
let Root = () => {
if (!cachedResult) {
cachedResult = createFromNodeStream(
rscResponse2,
serverConsumerManifest
);
}
return React.use(cachedResult).root;
};
res.set('Content-type', 'text/html');
const {pipe} = renderToPipeableStream(React.createElement(Root), {
bootstrapScripts: mainJSChunks,
formState: formState,
onShellReady() {
pipe(res);
},
onShellError(error) {
const {pipe: pipeError} = renderToPipeableStream(
React.createElement('html', null, React.createElement('body')),
{
bootstrapScripts: mainJSChunks,
}
);
pipeError(res);
},
});
} catch (e) {
console.error(`Failed to SSR: ${e.stack}`);
res.statusCode = 500;
res.end();
}
} else {
try {
const rscResponse = await promiseForData;
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.all('/', renderApp);
app.all('/prerender', renderApp);
if (process.env.NODE_ENV === 'development') {
app.use(express.static('public'));
app.get('/source-maps', async function (req, res, next) {
const proxiedHeaders = {
'X-Forwarded-Host': req.hostname,
'X-Forwarded-For': req.ips,
'X-Forwarded-Port': 3000,
'X-Forwarded-Proto': req.protocol,
};
const promiseForData = request(
{
host: '127.0.0.1',
port: 3001,
method: req.method,
path: req.originalUrl,
headers: proxiedHeaders,
},
req
);
try {
const rscResponse = await promiseForData;
res.set('Content-type', 'application/json');
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();
}
});
} else {
app.use(express.static('build'));
}
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;
}
});