32
// Ensure environment variables are read.
33
require('../config/env');
34
35
-const fs = require('fs');
35
+const fs = require('fs').promises;
36
const compress = require('compression');
37
const chalk = require('chalk');
38
const express = require('express');
39
-const app = express();
40
-
39
const http = require('http');
40
41
+const {renderToPipeableStream} = require('react-dom/server');
42
+const {createFromNodeStream} = require('react-server-dom-webpack/client');
43
+
44
+const app = express();
45
+
46
app.use(compress());
47
48
+if (process.env.NODE_ENV === 'development') {
49
+ // In development we host the Webpack server for live bundling.
50
+ const webpack = require('webpack');
51
+ const webpackMiddleware = require('webpack-dev-middleware');
52
+ const webpackHotMiddleware = require('webpack-hot-middleware');
53
+ const paths = require('../config/paths');
54
+ const configFactory = require('../config/webpack.config');
55
+ const getClientEnvironment = require('../config/env');
56
+
57
+ const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
58
+
59
+ const config = configFactory('development');
60
+ const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
61
+ const appName = require(paths.appPackageJson).name;
62
+
63
+ // Create a webpack compiler that is configured with custom messages.
64
+ const compiler = webpack(config);
65
+ app.use(
66
+ webpackMiddleware(compiler, {
67
+ publicPath: paths.publicUrlOrPath.slice(0, -1),
68
+ serverSideRender: true,
69
+ })
70
+ );
71
+ app.use(webpackHotMiddleware(compiler));
72
+}
73
+
74
function request(options, body) {
75
return new Promise((resolve, reject) => {
76
const req = http.request(options, res => {
84
}
85
86
app.all('/', async function (req, res, next) {
58
- if (req.accepts('text/html')) {
59
- // Pass-through to the html file
60
- next();
61
- return;
62
- }
63
-
87
// Proxy the request to the regional server.
88
const proxiedHeaders = {
89
'X-Forwarded-Host': req.hostname,
108
req
109
);
110
88
- try {
89
- const rscResponse = await promiseForData;
90
- res.set('Content-type', 'text/x-component');
91
- rscResponse.pipe(res);
92
- } catch (e) {
93
- console.error(`Failed to proxy request: ${e.stack}`);
94
- res.statusCode = 500;
95
- res.end();
111
+ if (req.accepts('text/html')) {
112
+ try {
113
+ const rscResponse = await promiseForData;
114
+
115
+ let virtualFs;
116
+ let buildPath;
117
+ if (process.env.NODE_ENV === 'development') {
118
+ const {devMiddleware} = res.locals.webpack;
119
+ virtualFs = devMiddleware.outputFileSystem.promises;
120
+ buildPath = devMiddleware.stats.toJson().outputPath;
121
+ } else {
122
+ virtualFs = fs;
123
+ buildPath = path.join(__dirname, '../build/');
124
+ }
125
+ // Read the module map from the virtual file system.
126
+ const moduleMap = JSON.parse(
127
+ await virtualFs.readFile(
128
+ path.join(buildPath, 'react-ssr-manifest.json'),
129
+ 'utf8'
130
+ )
131
+ );
132
+ // Read the entrypoints containing the initial JS to bootstrap everything.
133
+ // For other pages, the chunks in the RSC payload are enough.
134
+ const mainJSChunks = JSON.parse(
135
+ await virtualFs.readFile(
136
+ path.join(buildPath, 'entrypoint-manifest.json'),
137
+ 'utf8'
138
+ )
139
+ ).main.js;
140
+ // For HTML, we're a "client" emulator that runs the client code,
141
+ // so we start by consuming the RSC payload. This needs a module
142
+ // map that reverse engineers the client-side path to the SSR path.
143
+ const root = await createFromNodeStream(rscResponse, moduleMap);
144
+ // Render it into HTML by resolving the client components
145
+ res.set('Content-type', 'text/html');
146
+ const {pipe} = renderToPipeableStream(root, {
147
+ bootstrapScripts: mainJSChunks,
148
+ });
149
+ pipe(res);
150
+ } catch (e) {
151
+ console.error(`Failed to SSR: ${e.stack}`);
152
+ res.statusCode = 500;
153
+ res.end();
154
+ }
155
+ } else {
156
+ try {
157
+ const rscResponse = await promiseForData;
158
+ // For other request, we pass-through the RSC payload.
159
+ res.set('Content-type', 'text/x-component');
160
+ rscResponse.pipe(res);
161
+ } catch (e) {
162
+ console.error(`Failed to proxy request: ${e.stack}`);
163
+ res.statusCode = 500;
164
+ res.end();
165
+ }
166
}
167
});
168
169
if (process.env.NODE_ENV === 'development') {
100
- // In development we host the Webpack server for live bundling.
101
- const webpack = require('webpack');
102
- const webpackMiddleware = require('webpack-dev-middleware');
103
- const webpackHotMiddleware = require('webpack-hot-middleware');
104
- const paths = require('../config/paths');
105
- const configFactory = require('../config/webpack.config');
106
- const getClientEnvironment = require('../config/env');
107
-
108
- const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
109
-
110
- const HOST = '0.0.0.0';
111
- const PORT = 3000;
112
-
113
- const config = configFactory('development');
114
- const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
115
- const appName = require(paths.appPackageJson).name;
116
-
117
- // Create a webpack compiler that is configured with custom messages.
118
- const compiler = webpack(config);
119
- app.use(
120
- webpackMiddleware(compiler, {
121
- writeToDisk: filePath => {
122
- return /(react-client-manifest|react-ssr-manifest)\.json$/.test(
123
- filePath
124
- );
125
- },
126
- publicPath: paths.publicUrlOrPath.slice(0, -1),
127
- })
128
- );
129
- app.use(
130
- webpackHotMiddleware(compiler, {
131
- /* Options */
132
- })
133
- );
170
app.use(express.static('public'));
171
} else {
172
// In production we host the static build output.