1. 'use strict';
    
  2. 
    
  3. // This is a server to host data-local resources like databases and RSC
    
  4. 
    
  5. const path = require('path');
    
  6. 
    
  7. const register = require('react-server-dom-webpack/node-register');
    
  8. register();
    
  9. 
    
  10. const babelRegister = require('@babel/register');
    
  11. babelRegister({
    
  12.   babelrc: false,
    
  13.   ignore: [
    
  14.     /\/(build|node_modules)\//,
    
  15.     function (file) {
    
  16.       if ((path.dirname(file) + '/').startsWith(__dirname + '/')) {
    
  17.         // Ignore everything in this folder
    
  18.         // because it's a mix of CJS and ESM
    
  19.         // and working with raw code is easier.
    
  20.         return true;
    
  21.       }
    
  22.       return false;
    
  23.     },
    
  24.   ],
    
  25.   presets: ['@babel/preset-react'],
    
  26.   plugins: ['@babel/transform-modules-commonjs'],
    
  27. });
    
  28. 
    
  29. if (typeof fetch === 'undefined') {
    
  30.   // Patch fetch for earlier Node versions.
    
  31.   global.fetch = require('undici').fetch;
    
  32. }
    
  33. 
    
  34. const express = require('express');
    
  35. const bodyParser = require('body-parser');
    
  36. const busboy = require('busboy');
    
  37. const app = express();
    
  38. const compress = require('compression');
    
  39. const {Readable} = require('node:stream');
    
  40. 
    
  41. app.use(compress());
    
  42. 
    
  43. // Application
    
  44. 
    
  45. const {readFile} = require('fs').promises;
    
  46. 
    
  47. const React = require('react');
    
  48. 
    
  49. async function renderApp(res, returnValue, formState) {
    
  50.   const {renderToPipeableStream} = await import(
    
  51.     'react-server-dom-webpack/server'
    
  52.   );
    
  53.   // const m = require('../src/App.js');
    
  54.   const m = await import('../src/App.js');
    
  55. 
    
  56.   let moduleMap;
    
  57.   let mainCSSChunks;
    
  58.   if (process.env.NODE_ENV === 'development') {
    
  59.     // Read the module map from the HMR server in development.
    
  60.     moduleMap = await (
    
  61.       await fetch('http://localhost:3000/react-client-manifest.json')
    
  62.     ).json();
    
  63.     mainCSSChunks = (
    
  64.       await (
    
  65.         await fetch('http://localhost:3000/entrypoint-manifest.json')
    
  66.       ).json()
    
  67.     ).main.css;
    
  68.   } else {
    
  69.     // Read the module map from the static build in production.
    
  70.     moduleMap = JSON.parse(
    
  71.       await readFile(
    
  72.         path.resolve(__dirname, `../build/react-client-manifest.json`),
    
  73.         'utf8'
    
  74.       )
    
  75.     );
    
  76.     mainCSSChunks = JSON.parse(
    
  77.       await readFile(
    
  78.         path.resolve(__dirname, `../build/entrypoint-manifest.json`),
    
  79.         'utf8'
    
  80.       )
    
  81.     ).main.css;
    
  82.   }
    
  83.   const App = m.default.default || m.default;
    
  84.   const root = [
    
  85.     // Prepend the App's tree with stylesheets required for this entrypoint.
    
  86.     mainCSSChunks.map(filename =>
    
  87.       React.createElement('link', {
    
  88.         rel: 'stylesheet',
    
  89.         href: filename,
    
  90.         precedence: 'default',
    
  91.       })
    
  92.     ),
    
  93.     React.createElement(App),
    
  94.   ];
    
  95.   // For client-invoked server actions we refresh the tree and return a return value.
    
  96.   const payload = {root, returnValue, formState};
    
  97.   const {pipe} = renderToPipeableStream(payload, moduleMap);
    
  98.   pipe(res);
    
  99. }
    
  100. 
    
  101. app.get('/', async function (req, res) {
    
  102.   await renderApp(res, null, null);
    
  103. });
    
  104. 
    
  105. app.post('/', bodyParser.text(), async function (req, res) {
    
  106.   const {
    
  107.     renderToPipeableStream,
    
  108.     decodeReply,
    
  109.     decodeReplyFromBusboy,
    
  110.     decodeAction,
    
  111.     decodeFormState,
    
  112.   } = await import('react-server-dom-webpack/server');
    
  113.   const serverReference = req.get('rsc-action');
    
  114.   if (serverReference) {
    
  115.     // This is the client-side case
    
  116.     const [filepath, name] = serverReference.split('#');
    
  117.     const action = (await import(filepath))[name];
    
  118.     // Validate that this is actually a function we intended to expose and
    
  119.     // not the client trying to invoke arbitrary functions. In a real app,
    
  120.     // you'd have a manifest verifying this before even importing it.
    
  121.     if (action.$$typeof !== Symbol.for('react.server.reference')) {
    
  122.       throw new Error('Invalid action');
    
  123.     }
    
  124. 
    
  125.     let args;
    
  126.     if (req.is('multipart/form-data')) {
    
  127.       // Use busboy to streamingly parse the reply from form-data.
    
  128.       const bb = busboy({headers: req.headers});
    
  129.       const reply = decodeReplyFromBusboy(bb);
    
  130.       req.pipe(bb);
    
  131.       args = await reply;
    
  132.     } else {
    
  133.       args = await decodeReply(req.body);
    
  134.     }
    
  135.     const result = action.apply(null, args);
    
  136.     try {
    
  137.       // Wait for any mutations
    
  138.       await result;
    
  139.     } catch (x) {
    
  140.       // We handle the error on the client
    
  141.     }
    
  142.     // Refresh the client and return the value
    
  143.     renderApp(res, result, null);
    
  144.   } else {
    
  145.     // This is the progressive enhancement case
    
  146.     const UndiciRequest = require('undici').Request;
    
  147.     const fakeRequest = new UndiciRequest('http://localhost', {
    
  148.       method: 'POST',
    
  149.       headers: {'Content-Type': req.headers['content-type']},
    
  150.       body: Readable.toWeb(req),
    
  151.       duplex: 'half',
    
  152.     });
    
  153.     const formData = await fakeRequest.formData();
    
  154.     const action = await decodeAction(formData);
    
  155.     try {
    
  156.       // Wait for any mutations
    
  157.       const result = await action();
    
  158.       const formState = decodeFormState(result, formData);
    
  159.       renderApp(res, null, formState);
    
  160.     } catch (x) {
    
  161.       const {setServerState} = await import('../src/ServerState.js');
    
  162.       setServerState('Error: ' + x.message);
    
  163.       renderApp(res, null, null);
    
  164.     }
    
  165.   }
    
  166. });
    
  167. 
    
  168. app.get('/todos', function (req, res) {
    
  169.   res.json([
    
  170.     {
    
  171.       id: 1,
    
  172.       text: 'Shave yaks',
    
  173.     },
    
  174.     {
    
  175.       id: 2,
    
  176.       text: 'Eat kale',
    
  177.     },
    
  178.   ]);
    
  179. });
    
  180. 
    
  181. app.listen(3001, () => {
    
  182.   console.log('Regional Flight Server listening on port 3001...');
    
  183. });
    
  184. 
    
  185. app.on('error', function (error) {
    
  186.   if (error.syscall !== 'listen') {
    
  187.     throw error;
    
  188.   }
    
  189. 
    
  190.   switch (error.code) {
    
  191.     case 'EACCES':
    
  192.       console.error('port 3001 requires elevated privileges');
    
  193.       process.exit(1);
    
  194.       break;
    
  195.     case 'EADDRINUSE':
    
  196.       console.error('Port 3001 is already in use');
    
  197.       process.exit(1);
    
  198.       break;
    
  199.     default:
    
  200.       throw error;
    
  201.   }
    
  202. });