mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Fizz] Add another fixture (#21627)
This commit is contained in:
Generated
+12684
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "react-ssr",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=14.9.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.14.3",
|
||||
"@babel/register": "7.13.16",
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-preset-react-app": "10.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"concurrently": "^5.3.0",
|
||||
"express": "^4.17.1",
|
||||
"nodemon": "^2.0.6",
|
||||
"react": "0.0.0-cc4d24ab0",
|
||||
"react-dom": "0.0.0-cc4d24ab0",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"resolve": "1.12.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"webpack": "4.44.2",
|
||||
"webpack-cli": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
"prettier": "1.19.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "concurrently \"npm run server:dev\" \"npm run bundler:dev\"",
|
||||
"start:prod": "concurrently \"npm run server:prod\" \"npm run bundler:prod\"",
|
||||
"server:dev": "cross-env NODE_ENV=development nodemon -- server/server.js",
|
||||
"server:prod": "cross-env NODE_ENV=production nodemon -- server/server.js",
|
||||
"bundler:dev": "cross-env NODE_ENV=development nodemon -- scripts/build.js",
|
||||
"bundler:prod": "cross-env NODE_ENV=production nodemon -- scripts/build.js"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
[
|
||||
"react-app",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
"build/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 10px;
|
||||
height: 500px;
|
||||
float: left;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.post {
|
||||
padding: 20px;
|
||||
float: left;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.post p {
|
||||
font-size: larger;
|
||||
font-family: Georgia, serif;
|
||||
}
|
||||
|
||||
.comments {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.comment {
|
||||
border: 2px solid #aaa;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* https://codepen.io/mandelid/pen/vwKoe */
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
transition: opacity linear 0.1s;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(80, 80, 80, 0.5);
|
||||
border-radius: 50%;
|
||||
border-top-color: #fff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
opacity: 0;
|
||||
}
|
||||
.spinner--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const rimraf = require('rimraf');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
rimraf.sync(path.resolve(__dirname, '../build'));
|
||||
webpack(
|
||||
{
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
|
||||
entry: [path.resolve(__dirname, '../src/index.js')],
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../build'),
|
||||
filename: 'main.js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
(err, stats) => {
|
||||
if (err) {
|
||||
console.error(err.stack || err);
|
||||
if (err.details) {
|
||||
console.error(err.details);
|
||||
}
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
const info = stats.toJson();
|
||||
if (stats.hasErrors()) {
|
||||
console.log('Finished running webpack with errors.');
|
||||
info.errors.forEach(e => console.error(e));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('Finished running webpack.');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
// Tweak these to play with different kinds of latency.
|
||||
|
||||
// How long the data fetches on the server.
|
||||
exports.API_DELAY = 2000;
|
||||
|
||||
// How long the server waits for data before giving up.
|
||||
exports.ABORT_DELAY = 10000;
|
||||
|
||||
// How long serving the JS bundles is delayed.
|
||||
exports.JS_BUNDLE_DELAY = 5000;
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
// import {renderToString} from 'react-dom/server';
|
||||
import {pipeToNodeWritable} from 'react-dom/unstable-fizz';
|
||||
import App from '../src/App';
|
||||
import {DataProvider} from '../src/data';
|
||||
import {API_DELAY, ABORT_DELAY} from './delays';
|
||||
|
||||
// In a real setup, you'd read it from webpack build stats.
|
||||
let assets = {
|
||||
'main.js': '/main.js',
|
||||
'main.css': '/main.css',
|
||||
};
|
||||
|
||||
module.exports = function render(url, res) {
|
||||
// This is how you would wire it up previously:
|
||||
//
|
||||
// res.send(
|
||||
// '<!DOCTYPE html>' +
|
||||
// renderToString(
|
||||
// <DataProvider data={data}>
|
||||
// <App assets={assets} />
|
||||
// </DataProvider>,
|
||||
// )
|
||||
// );
|
||||
|
||||
// The new wiring is a bit more involved.
|
||||
res.socket.on('error', error => {
|
||||
console.error('Fatal', error);
|
||||
});
|
||||
let didError = false;
|
||||
const data = createServerData();
|
||||
const {startWriting, abort} = pipeToNodeWritable(
|
||||
<DataProvider data={data}>
|
||||
<App assets={assets} />
|
||||
</DataProvider>,
|
||||
res,
|
||||
{
|
||||
onReadyToStream() {
|
||||
// If something errored before we started streaming, we set the error code appropriately.
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
res.setHeader('Content-type', 'text/html');
|
||||
res.write('<!DOCTYPE html>');
|
||||
startWriting();
|
||||
},
|
||||
onError(x) {
|
||||
didError = true;
|
||||
console.error(x);
|
||||
},
|
||||
}
|
||||
);
|
||||
// Abandon and switch to client rendering if enough time passes.
|
||||
// Try lowering this to see the client recover.
|
||||
setTimeout(abort, ABORT_DELAY);
|
||||
};
|
||||
|
||||
// Simulate a delay caused by data fetching.
|
||||
// We fake this because the streaming HTML renderer
|
||||
// is not yet integrated with real data fetching strategies.
|
||||
function createServerData() {
|
||||
let done = false;
|
||||
let promise = null;
|
||||
return {
|
||||
read() {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
if (promise) {
|
||||
throw promise;
|
||||
}
|
||||
promise = new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
done = true;
|
||||
promise = null;
|
||||
resolve();
|
||||
}, API_DELAY);
|
||||
});
|
||||
throw promise;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const babelRegister = require('@babel/register');
|
||||
babelRegister({
|
||||
ignore: [/[\\\/](build|server\/server|node_modules)[\\\/]/],
|
||||
presets: [['react-app', {runtime: 'automatic'}]],
|
||||
plugins: ['@babel/transform-modules-commonjs'],
|
||||
});
|
||||
|
||||
const express = require('express');
|
||||
const compress = require('compression');
|
||||
const {readFileSync} = require('fs');
|
||||
const path = require('path');
|
||||
const React = require('react');
|
||||
const render = require('./render');
|
||||
const {JS_BUNDLE_DELAY} = require('./delays');
|
||||
|
||||
const PORT = process.env.PORT || 4000;
|
||||
const app = express();
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.url.endsWith('.js')) {
|
||||
// Artificially delay serving JS
|
||||
// to demonstrate streaming HTML.
|
||||
setTimeout(next, JS_BUNDLE_DELAY);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.use(compress());
|
||||
app.get(
|
||||
'/',
|
||||
handleErrors(async function(req, res) {
|
||||
await waitForWebpack();
|
||||
render(req.url, res);
|
||||
})
|
||||
);
|
||||
app.use(express.static('build'));
|
||||
app.use(express.static('public'));
|
||||
|
||||
app
|
||||
.listen(PORT, () => {
|
||||
console.log(`Listening at ${PORT}...`);
|
||||
})
|
||||
.on('error', function(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
const isPipe = portOrPipe => Number.isNaN(portOrPipe);
|
||||
const bind = isPipe(PORT) ? 'Pipe ' + PORT : 'Port ' + PORT;
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
function handleErrors(fn) {
|
||||
return async function(req, res, next) {
|
||||
try {
|
||||
return await fn(req, res);
|
||||
} catch (x) {
|
||||
next(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForWebpack() {
|
||||
while (true) {
|
||||
try {
|
||||
readFileSync(path.resolve(__dirname, '../build/main.js'));
|
||||
return;
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Could not find webpack build output. Will retry in a second...'
|
||||
);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import {useState, Suspense, lazy} from 'react';
|
||||
import {ErrorBoundary} from 'react-error-boundary';
|
||||
import Html from './Html';
|
||||
import Spinner from './Spinner';
|
||||
import Layout from './Layout';
|
||||
import NavBar from './NavBar';
|
||||
|
||||
const Comments = lazy(() => import('./Comments' /* webpackPrefetch: true */));
|
||||
const Sidebar = lazy(() => import('./Sidebar' /* webpackPrefetch: true */));
|
||||
const Post = lazy(() => import('./Post' /* webpackPrefetch: true */));
|
||||
|
||||
export default function App({assets}) {
|
||||
return (
|
||||
<Html assets={assets} title="Hello">
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<ErrorBoundary FallbackComponent={Error}>
|
||||
<Content />
|
||||
</ErrorBoundary>
|
||||
</Suspense>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
|
||||
function Content() {
|
||||
return (
|
||||
<Layout>
|
||||
<NavBar />
|
||||
<aside className="sidebar">
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Sidebar />
|
||||
</Suspense>
|
||||
</aside>
|
||||
<article className="post">
|
||||
<Post />
|
||||
<section className="comments">
|
||||
<h2>Comments</h2>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<Comments />
|
||||
</Suspense>
|
||||
</section>
|
||||
<h2>Thanks for reading!</h2>
|
||||
</article>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
function Error({error}) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Application Error</h1>
|
||||
<pre style={{whiteSpace: 'pre-wrap'}}>{error.stack}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import {useData} from './data';
|
||||
|
||||
export default function Comments() {
|
||||
const comments = useData();
|
||||
return (
|
||||
<>
|
||||
{comments.map((comment, i) => (
|
||||
<p className="comment" key={i}>
|
||||
{comment}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export default function Html({assets, children, title}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
<link rel="stylesheet" href={assets['main.css']} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `<b>Enable JavaScript to run this app.</b>`,
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `assetManifest = ${JSON.stringify(assets)};`,
|
||||
}}
|
||||
/>
|
||||
<script async src={assets['main.js']} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export default function Layout({children}) {
|
||||
return <main>{children}</main>;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export default function NavBar() {
|
||||
return (
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export default function Post() {
|
||||
return (
|
||||
<>
|
||||
<h1>Hello world</h1>
|
||||
<p>
|
||||
This demo is <b>artificially slowed down</b>. Open{' '}
|
||||
<code>server/delays.js</code> to adjust how much different things are
|
||||
slowed down.
|
||||
</p>
|
||||
<p>
|
||||
Notice how HTML for comments "streams in" before the JS (or React) has
|
||||
loaded on the page.
|
||||
</p>
|
||||
<p>
|
||||
Also notice that the JS for comments and sidebar has been code-split,
|
||||
but HTML for it is still included in the server output.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export default function Sidebar() {
|
||||
return (
|
||||
<>
|
||||
<h1>Archive</h1>
|
||||
<ul>
|
||||
<li>May 2021</li>
|
||||
<li>April 2021</li>
|
||||
<li>March 2021</li>
|
||||
<li>February 2021</li>
|
||||
<li>January 2021</li>
|
||||
<li>December 2020</li>
|
||||
<li>November 2020</li>
|
||||
<li>October 2020</li>
|
||||
<li>September 2020</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
export default function Spinner({active = true}) {
|
||||
return (
|
||||
<div
|
||||
className={['spinner', active && 'spinner--active'].join(' ')}
|
||||
role="progressbar"
|
||||
aria-busy={active ? 'true' : 'false'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import {createContext, useContext} from 'react';
|
||||
|
||||
// Note: this file does not demonstrate a real data fetching strategy.
|
||||
// We only use this to simulate data fetching happening on the server
|
||||
// while the cache is populated on the client. In a real app, you would
|
||||
// instead use a data fetching library or Server Components for this.
|
||||
|
||||
const DataContext = createContext(null);
|
||||
|
||||
export function DataProvider({children, data}) {
|
||||
return <DataContext.Provider value={data}>{children}</DataContext.Provider>;
|
||||
}
|
||||
|
||||
// In a real implementation the data would be streamed with the HTML.
|
||||
// We haven't integrated this part yet, so we'll just use fake data.
|
||||
const fakeData = [
|
||||
"Wait, it doesn't wait for React to load?",
|
||||
'How does this even work?',
|
||||
'I like marshmallows',
|
||||
];
|
||||
|
||||
export function useData() {
|
||||
const ctx = useContext(DataContext);
|
||||
if (ctx !== null) {
|
||||
// This context is only provided on the server.
|
||||
// It is here to simulate a suspending data fetch.
|
||||
ctx.read();
|
||||
}
|
||||
return fakeData;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
import {createRoot} from 'react-dom';
|
||||
import App from './App';
|
||||
import {DataProvider} from './data';
|
||||
|
||||
const root = createRoot(document, {hydrate: true});
|
||||
root.render(<App assets={window.assetManifest} />);
|
||||
Reference in New Issue
Block a user