Skip to content

Commit fccfc24

Browse files
authored
[Fizz] Add another fixture (facebook#21627)
1 parent cc4d24a commit fccfc24

File tree

17 files changed

+13331
-0
lines changed

17 files changed

+13331
-0
lines changed

fixtures/ssr2/package-lock.json

Lines changed: 12684 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fixtures/ssr2/package.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "react-ssr",
3+
"version": "0.1.0",
4+
"private": true,
5+
"engines": {
6+
"node": ">=14.9.0"
7+
},
8+
"license": "MIT",
9+
"dependencies": {
10+
"@babel/core": "7.14.3",
11+
"@babel/register": "7.13.16",
12+
"babel-loader": "8.1.0",
13+
"babel-preset-react-app": "10.0.0",
14+
"compression": "^1.7.4",
15+
"concurrently": "^5.3.0",
16+
"express": "^4.17.1",
17+
"nodemon": "^2.0.6",
18+
"react": "0.0.0-cc4d24ab0",
19+
"react-dom": "0.0.0-cc4d24ab0",
20+
"react-error-boundary": "^3.1.3",
21+
"resolve": "1.12.0",
22+
"rimraf": "^3.0.2",
23+
"webpack": "4.44.2",
24+
"webpack-cli": "^4.2.0"
25+
},
26+
"devDependencies": {
27+
"cross-env": "^7.0.3",
28+
"prettier": "1.19.1"
29+
},
30+
"scripts": {
31+
"start": "concurrently \"npm run server:dev\" \"npm run bundler:dev\"",
32+
"start:prod": "concurrently \"npm run server:prod\" \"npm run bundler:prod\"",
33+
"server:dev": "cross-env NODE_ENV=development nodemon -- server/server.js",
34+
"server:prod": "cross-env NODE_ENV=production nodemon -- server/server.js",
35+
"bundler:dev": "cross-env NODE_ENV=development nodemon -- scripts/build.js",
36+
"bundler:prod": "cross-env NODE_ENV=production nodemon -- scripts/build.js"
37+
},
38+
"babel": {
39+
"presets": [
40+
[
41+
"react-app",
42+
{
43+
"runtime": "automatic"
44+
}
45+
]
46+
]
47+
},
48+
"nodemonConfig": {
49+
"ignore": [
50+
"build/*"
51+
]
52+
}
53+
}

fixtures/ssr2/public/main.css

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
body {
2+
font-family: system-ui, sans-serif;
3+
}
4+
5+
nav {
6+
padding: 20px;
7+
}
8+
9+
.sidebar {
10+
padding: 10px;
11+
height: 500px;
12+
float: left;
13+
width: 30%;
14+
}
15+
16+
.post {
17+
padding: 20px;
18+
float: left;
19+
width: 60%;
20+
}
21+
22+
h1, h2 {
23+
padding: 0;
24+
}
25+
26+
ul, li {
27+
margin: 0;
28+
}
29+
30+
.post p {
31+
font-size: larger;
32+
font-family: Georgia, serif;
33+
}
34+
35+
.comments {
36+
margin-top: 40px;
37+
}
38+
39+
.comment {
40+
border: 2px solid #aaa;
41+
border-radius: 4px;
42+
padding: 20px;
43+
}
44+
45+
/* https://codepen.io/mandelid/pen/vwKoe */
46+
.spinner {
47+
display: inline-block;
48+
transition: opacity linear 0.1s;
49+
width: 20px;
50+
height: 20px;
51+
border: 3px solid rgba(80, 80, 80, 0.5);
52+
border-radius: 50%;
53+
border-top-color: #fff;
54+
animation: spin 1s ease-in-out infinite;
55+
opacity: 0;
56+
}
57+
.spinner--active {
58+
opacity: 1;
59+
}
60+
61+
@keyframes spin {
62+
to {
63+
transform: rotate(360deg);
64+
}
65+
}
66+
@keyframes spin {
67+
to {
68+
transform: rotate(360deg);
69+
}
70+
}

fixtures/ssr2/scripts/build.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
'use strict';
10+
11+
const path = require('path');
12+
const rimraf = require('rimraf');
13+
const webpack = require('webpack');
14+
15+
const isProduction = process.env.NODE_ENV === 'production';
16+
rimraf.sync(path.resolve(__dirname, '../build'));
17+
webpack(
18+
{
19+
mode: isProduction ? 'production' : 'development',
20+
devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
21+
entry: [path.resolve(__dirname, '../src/index.js')],
22+
output: {
23+
path: path.resolve(__dirname, '../build'),
24+
filename: 'main.js',
25+
},
26+
module: {
27+
rules: [
28+
{
29+
test: /\.js$/,
30+
use: 'babel-loader',
31+
exclude: /node_modules/,
32+
},
33+
],
34+
},
35+
},
36+
(err, stats) => {
37+
if (err) {
38+
console.error(err.stack || err);
39+
if (err.details) {
40+
console.error(err.details);
41+
}
42+
process.exit(1);
43+
return;
44+
}
45+
const info = stats.toJson();
46+
if (stats.hasErrors()) {
47+
console.log('Finished running webpack with errors.');
48+
info.errors.forEach(e => console.error(e));
49+
process.exit(1);
50+
} else {
51+
console.log('Finished running webpack.');
52+
}
53+
}
54+
);

fixtures/ssr2/server/delays.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
// Tweak these to play with different kinds of latency.
10+
11+
// How long the data fetches on the server.
12+
exports.API_DELAY = 2000;
13+
14+
// How long the server waits for data before giving up.
15+
exports.ABORT_DELAY = 10000;
16+
17+
// How long serving the JS bundles is delayed.
18+
exports.JS_BUNDLE_DELAY = 5000;

fixtures/ssr2/server/render.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
import * as React from 'react';
10+
// import {renderToString} from 'react-dom/server';
11+
import {pipeToNodeWritable} from 'react-dom/unstable-fizz';
12+
import App from '../src/App';
13+
import {DataProvider} from '../src/data';
14+
import {API_DELAY, ABORT_DELAY} from './delays';
15+
16+
// In a real setup, you'd read it from webpack build stats.
17+
let assets = {
18+
'main.js': '/main.js',
19+
'main.css': '/main.css',
20+
};
21+
22+
module.exports = function render(url, res) {
23+
// This is how you would wire it up previously:
24+
//
25+
// res.send(
26+
// '<!DOCTYPE html>' +
27+
// renderToString(
28+
// <DataProvider data={data}>
29+
// <App assets={assets} />
30+
// </DataProvider>,
31+
// )
32+
// );
33+
34+
// The new wiring is a bit more involved.
35+
res.socket.on('error', error => {
36+
console.error('Fatal', error);
37+
});
38+
let didError = false;
39+
const data = createServerData();
40+
const {startWriting, abort} = pipeToNodeWritable(
41+
<DataProvider data={data}>
42+
<App assets={assets} />
43+
</DataProvider>,
44+
res,
45+
{
46+
onReadyToStream() {
47+
// If something errored before we started streaming, we set the error code appropriately.
48+
res.statusCode = didError ? 500 : 200;
49+
res.setHeader('Content-type', 'text/html');
50+
res.write('<!DOCTYPE html>');
51+
startWriting();
52+
},
53+
onError(x) {
54+
didError = true;
55+
console.error(x);
56+
},
57+
}
58+
);
59+
// Abandon and switch to client rendering if enough time passes.
60+
// Try lowering this to see the client recover.
61+
setTimeout(abort, ABORT_DELAY);
62+
};
63+
64+
// Simulate a delay caused by data fetching.
65+
// We fake this because the streaming HTML renderer
66+
// is not yet integrated with real data fetching strategies.
67+
function createServerData() {
68+
let done = false;
69+
let promise = null;
70+
return {
71+
read() {
72+
if (done) {
73+
return;
74+
}
75+
if (promise) {
76+
throw promise;
77+
}
78+
promise = new Promise(resolve => {
79+
setTimeout(() => {
80+
done = true;
81+
promise = null;
82+
resolve();
83+
}, API_DELAY);
84+
});
85+
throw promise;
86+
},
87+
};
88+
}

fixtures/ssr2/server/server.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
'use strict';
10+
11+
const babelRegister = require('@babel/register');
12+
babelRegister({
13+
ignore: [/[\\\/](build|server\/server|node_modules)[\\\/]/],
14+
presets: [['react-app', {runtime: 'automatic'}]],
15+
plugins: ['@babel/transform-modules-commonjs'],
16+
});
17+
18+
const express = require('express');
19+
const compress = require('compression');
20+
const {readFileSync} = require('fs');
21+
const path = require('path');
22+
const React = require('react');
23+
const render = require('./render');
24+
const {JS_BUNDLE_DELAY} = require('./delays');
25+
26+
const PORT = process.env.PORT || 4000;
27+
const app = express();
28+
29+
app.use((req, res, next) => {
30+
if (req.url.endsWith('.js')) {
31+
// Artificially delay serving JS
32+
// to demonstrate streaming HTML.
33+
setTimeout(next, JS_BUNDLE_DELAY);
34+
} else {
35+
next();
36+
}
37+
});
38+
39+
app.use(compress());
40+
app.get(
41+
'/',
42+
handleErrors(async function(req, res) {
43+
await waitForWebpack();
44+
render(req.url, res);
45+
})
46+
);
47+
app.use(express.static('build'));
48+
app.use(express.static('public'));
49+
50+
app
51+
.listen(PORT, () => {
52+
console.log(`Listening at ${PORT}...`);
53+
})
54+
.on('error', function(error) {
55+
if (error.syscall !== 'listen') {
56+
throw error;
57+
}
58+
const isPipe = portOrPipe => Number.isNaN(portOrPipe);
59+
const bind = isPipe(PORT) ? 'Pipe ' + PORT : 'Port ' + PORT;
60+
switch (error.code) {
61+
case 'EACCES':
62+
console.error(bind + ' requires elevated privileges');
63+
process.exit(1);
64+
break;
65+
case 'EADDRINUSE':
66+
console.error(bind + ' is already in use');
67+
process.exit(1);
68+
break;
69+
default:
70+
throw error;
71+
}
72+
});
73+
74+
function handleErrors(fn) {
75+
return async function(req, res, next) {
76+
try {
77+
return await fn(req, res);
78+
} catch (x) {
79+
next(x);
80+
}
81+
};
82+
}
83+
84+
async function waitForWebpack() {
85+
while (true) {
86+
try {
87+
readFileSync(path.resolve(__dirname, '../build/main.js'));
88+
return;
89+
} catch (err) {
90+
console.log(
91+
'Could not find webpack build output. Will retry in a second...'
92+
);
93+
await new Promise(resolve => setTimeout(resolve, 1000));
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)