1. 'use strict';
    
  2. 
    
  3. // This is a server to host CDN distributed resources like module source files and SSR
    
  4. 
    
  5. const path = require('path');
    
  6. const url = require('url');
    
  7. 
    
  8. const fs = require('fs').promises;
    
  9. const compress = require('compression');
    
  10. const chalk = require('chalk');
    
  11. const express = require('express');
    
  12. const http = require('http');
    
  13. const React = require('react');
    
  14. 
    
  15. const {renderToPipeableStream} = require('react-dom/server');
    
  16. const {createFromNodeStream} = require('react-server-dom-esm/client');
    
  17. 
    
  18. const moduleBasePath = new URL('../src', url.pathToFileURL(__filename)).href;
    
  19. 
    
  20. const app = express();
    
  21. 
    
  22. app.use(compress());
    
  23. 
    
  24. function request(options, body) {
    
  25.   return new Promise((resolve, reject) => {
    
  26.     const req = http.request(options, res => {
    
  27.       resolve(res);
    
  28.     });
    
  29.     req.on('error', e => {
    
  30.       reject(e);
    
  31.     });
    
  32.     body.pipe(req);
    
  33.   });
    
  34. }
    
  35. 
    
  36. app.all('/', async function (req, res, next) {
    
  37.   // Proxy the request to the regional server.
    
  38.   const proxiedHeaders = {
    
  39.     'X-Forwarded-Host': req.hostname,
    
  40.     'X-Forwarded-For': req.ips,
    
  41.     'X-Forwarded-Port': 3000,
    
  42.     'X-Forwarded-Proto': req.protocol,
    
  43.   };
    
  44.   // Proxy other headers as desired.
    
  45.   if (req.get('rsc-action')) {
    
  46.     proxiedHeaders['Content-type'] = req.get('Content-type');
    
  47.     proxiedHeaders['rsc-action'] = req.get('rsc-action');
    
  48.   } else if (req.get('Content-type')) {
    
  49.     proxiedHeaders['Content-type'] = req.get('Content-type');
    
  50.   }
    
  51. 
    
  52.   const promiseForData = request(
    
  53.     {
    
  54.       host: '127.0.0.1',
    
  55.       port: 3001,
    
  56.       method: req.method,
    
  57.       path: '/',
    
  58.       headers: proxiedHeaders,
    
  59.     },
    
  60.     req
    
  61.   );
    
  62. 
    
  63.   if (req.accepts('text/html')) {
    
  64.     try {
    
  65.       const rscResponse = await promiseForData;
    
  66.       const moduleBaseURL = '/src';
    
  67. 
    
  68.       // For HTML, we're a "client" emulator that runs the client code,
    
  69.       // so we start by consuming the RSC payload. This needs the local file path
    
  70.       // to load the source files from as well as the URL path for preloads.
    
  71. 
    
  72.       let root;
    
  73.       let Root = () => {
    
  74.         if (root) {
    
  75.           return React.use(root);
    
  76.         }
    
  77. 
    
  78.         return React.use(
    
  79.           (root = createFromNodeStream(
    
  80.             rscResponse,
    
  81.             moduleBasePath,
    
  82.             moduleBaseURL
    
  83.           ))
    
  84.         );
    
  85.       };
    
  86.       // Render it into HTML by resolving the client components
    
  87.       res.set('Content-type', 'text/html');
    
  88.       const {pipe} = renderToPipeableStream(React.createElement(Root), {
    
  89.         importMap: {
    
  90.           imports: {
    
  91.             react: 'https://esm.sh/react@experimental?pin=v124&dev',
    
  92.             'react-dom': 'https://esm.sh/react-dom@experimental?pin=v124&dev',
    
  93.             'react-dom/': 'https://esm.sh/react-dom@experimental&pin=v124&dev/',
    
  94.             'react-server-dom-esm/client':
    
  95.               '/node_modules/react-server-dom-esm/esm/react-server-dom-esm-client.browser.development.js',
    
  96.           },
    
  97.         },
    
  98.         bootstrapModules: ['/src/index.js'],
    
  99.       });
    
  100.       pipe(res);
    
  101.     } catch (e) {
    
  102.       console.error(`Failed to SSR: ${e.stack}`);
    
  103.       res.statusCode = 500;
    
  104.       res.end();
    
  105.     }
    
  106.   } else {
    
  107.     try {
    
  108.       const rscResponse = await promiseForData;
    
  109. 
    
  110.       // For other request, we pass-through the RSC payload.
    
  111.       res.set('Content-type', 'text/x-component');
    
  112.       rscResponse.on('data', data => {
    
  113.         res.write(data);
    
  114.         res.flush();
    
  115.       });
    
  116.       rscResponse.on('end', data => {
    
  117.         res.end();
    
  118.       });
    
  119.     } catch (e) {
    
  120.       console.error(`Failed to proxy request: ${e.stack}`);
    
  121.       res.statusCode = 500;
    
  122.       res.end();
    
  123.     }
    
  124.   }
    
  125. });
    
  126. 
    
  127. app.use(express.static('public'));
    
  128. app.use('/src', express.static('src'));
    
  129. app.use(
    
  130.   '/node_modules/react-server-dom-esm/esm',
    
  131.   express.static('node_modules/react-server-dom-esm/esm')
    
  132. );
    
  133. 
    
  134. app.listen(3000, () => {
    
  135.   console.log('Global Fizz/Webpack Server listening on port 3000...');
    
  136. });
    
  137. 
    
  138. app.on('error', function (error) {
    
  139.   if (error.syscall !== 'listen') {
    
  140.     throw error;
    
  141.   }
    
  142. 
    
  143.   switch (error.code) {
    
  144.     case 'EACCES':
    
  145.       console.error('port 3000 requires elevated privileges');
    
  146.       process.exit(1);
    
  147.       break;
    
  148.     case 'EADDRINUSE':
    
  149.       console.error('Port 3000 is already in use');
    
  150.       process.exit(1);
    
  151.       break;
    
  152.     default:
    
  153.       throw error;
    
  154.   }
    
  155. });