main
js 194 lines 5.06 KB
Raw
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 if (process.env.NODE_ENV === 'development') {
135 app.get('/source-maps', async function (req, res, next) {
136 // Proxy the request to the regional server.
137 const proxiedHeaders = {
138 'X-Forwarded-Host': req.hostname,
139 'X-Forwarded-For': req.ips,
140 'X-Forwarded-Port': 3000,
141 'X-Forwarded-Proto': req.protocol,
142 };
143
144 const promiseForData = request(
145 {
146 host: '127.0.0.1',
147 port: 3001,
148 method: req.method,
149 path: req.originalUrl,
150 headers: proxiedHeaders,
151 },
152 req
153 );
154
155 try {
156 const rscResponse = await promiseForData;
157 res.set('Content-type', 'application/json');
158 rscResponse.on('data', data => {
159 res.write(data);
160 res.flush();
161 });
162 rscResponse.on('end', data => {
163 res.end();
164 });
165 } catch (e) {
166 console.error(`Failed to proxy request: ${e.stack}`);
167 res.statusCode = 500;
168 res.end();
169 }
170 });
171 }
172
173 app.listen(3000, () => {
174 console.log('Global Fizz/Webpack Server listening on port 3000...');
175 });
176
177 app.on('error', function (error) {
178 if (error.syscall !== 'listen') {
179 throw error;
180 }
181
182 switch (error.code) {
183 case 'EACCES':
184 console.error('port 3000 requires elevated privileges');
185 process.exit(1);
186 break;
187 case 'EADDRINUSE':
188 console.error('Port 3000 is already in use');
189 process.exit(1);
190 break;
191 default:
192 throw error;
193 }
194 });