'use strict';
const path = require('path');
const url = require('url');
const register = require('react-server-dom-unbundled/node-register');
register();
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'],
plugins: ['@babel/transform-modules-commonjs'],
sourceMaps: process.env.NODE_ENV === 'development' ? 'inline' : false,
});
if (typeof fetch === 'undefined') {
global.fetch = require('undici').fetch;
}
const express = require('express');
const bodyParser = require('body-parser');
const busboy = require('busboy');
const app = express();
const compress = require('compression');
const {Readable} = require('node:stream');
const nodeModule = require('node:module');
app.use(compress());
const {readFile} = require('fs').promises;
const React = require('react');
const activeDebugChannels =
process.env.NODE_ENV === 'development' ? new Map() : null;
function filterStackFrame(sourceURL, functionName) {
return (
sourceURL !== '' &&
!sourceURL.startsWith('node:') &&
!sourceURL.includes('node_modules') &&
!sourceURL.endsWith('library.js') &&
!sourceURL.includes('/server/region.js')
);
}
function getDebugChannel(req) {
if (process.env.NODE_ENV !== 'development') {
return undefined;
}
const requestId = req.get('rsc-request-id');
if (!requestId) {
return undefined;
}
return activeDebugChannels.get(requestId);
}
async function renderApp(res, returnValue, formState, noCache, debugChannel) {
const {renderToPipeableStream} = await import(
'react-server-dom-unbundled/server'
);
const m = await import('../src/App.js');
let moduleMap;
let mainCSSChunks;
if (process.env.NODE_ENV === 'development') {
moduleMap = await (
await fetch('http://localhost:3000/react-client-manifest.json')
).json();
mainCSSChunks = (
await (
await fetch('http://localhost:3000/entrypoint-manifest.json')
).json()
).main.css;
} else {
moduleMap = JSON.parse(
await readFile(
path.resolve(__dirname, `../build/react-client-manifest.json`),
'utf8'
)
);
mainCSSChunks = JSON.parse(
await readFile(
path.resolve(__dirname, `../build/entrypoint-manifest.json`),
'utf8'
)
).main.css;
}
const App = m.default.default || m.default;
const root = React.createElement(
React.Fragment,
null,
mainCSSChunks.map(filename =>
React.createElement('link', {
rel: 'stylesheet',
href: filename,
precedence: 'default',
key: filename,
})
),
React.createElement(App, {noCache})
);
const payload = {root, returnValue, formState};
const {pipe} = renderToPipeableStream(payload, moduleMap, {
debugChannel,
filterStackFrame,
});
pipe(res);
}
async function prerenderApp(res, returnValue, formState, noCache) {
const {prerenderToNodeStream} = await import(
'react-server-dom-unbundled/static'
);
const m = await import('../src/App.js');
let moduleMap;
let mainCSSChunks;
if (process.env.NODE_ENV === 'development') {
moduleMap = await (
await fetch('http://localhost:3000/react-client-manifest.json')
).json();
mainCSSChunks = (
await (
await fetch('http://localhost:3000/entrypoint-manifest.json')
).json()
).main.css;
} else {
moduleMap = JSON.parse(
await readFile(
path.resolve(__dirname, `../build/react-client-manifest.json`),
'utf8'
)
);
mainCSSChunks = JSON.parse(
await readFile(
path.resolve(__dirname, `../build/entrypoint-manifest.json`),
'utf8'
)
).main.css;
}
const App = m.default.default || m.default;
const root = React.createElement(
React.Fragment,
null,
mainCSSChunks.map(filename =>
React.createElement('link', {
rel: 'stylesheet',
href: filename,
precedence: 'default',
key: filename,
})
),
React.createElement(App, {prerender: true, noCache})
);
const payload = {root, returnValue, formState};
const {prelude} = await prerenderToNodeStream(payload, moduleMap, {
filterStackFrame,
});
prelude.pipe(res);
}
app.get('/', async function (req, res) {
const noCache = req.get('cache-control') === 'no-cache';
if ('prerender' in req.query) {
await prerenderApp(res, null, null, noCache);
} else {
await renderApp(res, null, null, noCache, getDebugChannel(req));
}
});
app.post('/', bodyParser.text(), async function (req, res) {
const noCache = req.headers['cache-control'] === 'no-cache';
const {decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState} =
await import('react-server-dom-unbundled/server');
const serverReference = req.get('rsc-action');
if (serverReference) {
const [filepath, name] = serverReference.split('#');
const action = (await import(filepath))[name];
if (action.$$typeof !== Symbol.for('react.server.reference')) {
throw new Error('Invalid action');
}
let args;
if (req.is('multipart/form-data')) {
const bb = busboy({headers: req.headers});
const reply = decodeReplyFromBusboy(bb);
req.pipe(bb);
args = await reply;
} else {
args = await decodeReply(req.body);
}
const result = action.apply(null, args);
try {
await result;
} catch (x) {
}
renderApp(res, result, null, noCache, getDebugChannel(req));
} else {
const UndiciRequest = require('undici').Request;
const fakeRequest = new UndiciRequest('http://localhost', {
method: 'POST',
headers: {'Content-Type': req.headers['content-type']},
body: Readable.toWeb(req),
duplex: 'half',
});
const formData = await fakeRequest.formData();
const action = await decodeAction(formData);
try {
const result = await action();
const formState = decodeFormState(result, formData);
renderApp(res, null, formState, noCache, undefined);
} catch (x) {
const {setServerState} = await import('../src/ServerState.js');
setServerState('Error: ' + x.message);
renderApp(res, null, null, noCache, undefined);
}
}
});
app.get('/todos', function (req, res) {
res.json([
{
id: 1,
text: 'Shave yaks',
},
{
id: 2,
text: 'Eat kale',
},
]);
});
if (process.env.NODE_ENV === 'development') {
const rootDir = path.resolve(__dirname, '../');
app.get('/source-maps', async function (req, res, next) {
try {
res.set('Content-type', 'application/json');
let requestedFilePath = req.query.name;
let isCompiledOutput = false;
if (requestedFilePath.startsWith('file://')) {
isCompiledOutput = true;
requestedFilePath = url.fileURLToPath(requestedFilePath);
}
const relativePath = path.relative(rootDir, requestedFilePath);
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
res.status = 403;
res.write('{}');
res.end();
return;
}
const sourceMap = nodeModule.findSourceMap(requestedFilePath);
let map;
if (requestedFilePath.startsWith('node:')) {
map = {
version: 3,
sources: ['node:///' + requestedFilePath.slice(5)],
sourcesContent: ['// Node Internals'],
mappings: 'AAAA',
ignoreList: [0],
sourceRoot: '',
};
} else if (!sourceMap || !isCompiledOutput) {
const sourceContent = await readFile(requestedFilePath, 'utf8');
const lines = sourceContent.split('\n').length;
const sourceURL = url.pathToFileURL(requestedFilePath);
map = {
version: 3,
sources: [sourceURL],
sourcesContent: [sourceContent],
mappings: 'AAAA' + ';AACA'.repeat(lines - 1),
sourceRoot: '',
ignoreList: requestedFilePath.includes('node_modules')
? [0]
: undefined,
};
} else {
map = sourceMap.payload;
}
res.write(JSON.stringify(map));
res.end();
} catch (x) {
res.status = 500;
res.write('{}');
res.end();
console.error(x);
}
});
}
const httpServer = app.listen(3001, () => {
console.log('Regional Flight Server listening on port 3001...');
});
app.on('error', function (error) {
if (error.syscall !== 'listen') {
throw error;
}
switch (error.code) {
case 'EACCES':
console.error('port 3001 requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error('Port 3001 is already in use');
process.exit(1);
break;
default:
throw error;
}
});
if (process.env.NODE_ENV === 'development') {
const WebSocket = require('ws');
const webSocketServer = new WebSocket.Server({
server: httpServer,
path: '/debug-channel',
});
webSocketServer.on('connection', (ws, req) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const requestId = url.searchParams.get('id');
activeDebugChannels.set(requestId, ws);
ws.on('close', (code, reason) => {
activeDebugChannels.delete(requestId);
});
});
}