mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[Flight Fixture] Show SSR Support with CSS (#26263)
Builds on #26257. To do this we need access to a manifest for which scripts and CSS are used for each "page" (entrypoint). The initial script to bootstrap the app is inserted with `bootstrapScripts`. Subsequent content are loaded using the chunks mechanism built-in. The stylesheets for each pages are prepended to each RSC payload and rendered using Float. This doesn't yet support styles imported in components that are also SSR:ed nor imported through Server Components. That's more complex and not implemented in the node loader. HMR doesn't work after reloads right now because the SSR renderer isn't hot reloaded because there's no idiomatic way to hot reload ESM modules in Node.js yet. Without killing the HMR server. This leads to hydration mismatches when reloading the page after a hot reload. Notably this doesn't show serializing the stream through the HTML like real implementations do. This will lead to possible hydration mismatches based on the data. However, manually serializing the stream as a string isn't exactly correct due to binary data. It's not the idiomatic way this is supposed to work. This will all be built-in which will make this automatic in the future.
This commit is contained in:
committed by
GitHub
parent
40755c01a6
commit
67a61d5bd7
1
fixtures/flight/.gitignore
vendored
1
fixtures/flight/.gitignore
vendored
@@ -10,7 +10,6 @@
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
.eslintcache
|
||||
|
||||
# misc
|
||||
|
||||
@@ -56,7 +56,6 @@ module.exports = {
|
||||
appPath: resolveApp('.'),
|
||||
appBuild: resolveApp(buildPath),
|
||||
appPublic: resolveApp('public'),
|
||||
appHtml: resolveApp('public/index.html'),
|
||||
appIndexJs: resolveModule(resolveApp, 'src/index'),
|
||||
appPackageJson: resolveApp('package.json'),
|
||||
appSrc: resolveApp('src'),
|
||||
|
||||
@@ -9,13 +9,10 @@ const {createHash} = require('crypto');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const resolve = require('resolve');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
|
||||
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
|
||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||
@@ -28,6 +25,7 @@ const ForkTsCheckerWebpackPlugin =
|
||||
? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin')
|
||||
: require('react-dev-utils/ForkTsCheckerWebpackPlugin');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
const {WebpackManifestPlugin} = require('webpack-manifest-plugin');
|
||||
|
||||
function createEnvironmentHash(env) {
|
||||
const hash = createHash('md5');
|
||||
@@ -116,7 +114,7 @@ module.exports = function (webpackEnv) {
|
||||
const getStyleLoaders = (cssOptions, preProcessor) => {
|
||||
const loaders = [
|
||||
isEnvDevelopment && require.resolve('style-loader'),
|
||||
isEnvProduction && {
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
// css is located in `static/css`, use '../../' to locate index.html folder
|
||||
// in production `paths.publicUrlOrPath` can be a relative path
|
||||
@@ -578,44 +576,6 @@ module.exports = function (webpackEnv) {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin(
|
||||
Object.assign(
|
||||
{},
|
||||
{
|
||||
inject: true,
|
||||
template: paths.appHtml,
|
||||
},
|
||||
isEnvProduction
|
||||
? {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
),
|
||||
// Inlines the webpack runtime script. This script is too small to warrant
|
||||
// a network request.
|
||||
// https://github.com/facebook/create-react-app/issues/5358
|
||||
isEnvProduction &&
|
||||
shouldInlineRuntimeChunk &&
|
||||
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// It will be an empty string unless you specify "homepage"
|
||||
// in `package.json`, in which case it will be the pathname of that URL.
|
||||
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
|
||||
// This gives some necessary context to module not found errors, such as
|
||||
// the requesting resource.
|
||||
new ModuleNotFoundPlugin(paths.appPath),
|
||||
@@ -636,13 +596,40 @@ module.exports = function (webpackEnv) {
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebook/create-react-app/issues/240
|
||||
isEnvDevelopment && new CaseSensitivePathsPlugin(),
|
||||
isEnvProduction &&
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: 'static/css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: 'static/css/[name].[contenthash:8].css',
|
||||
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
|
||||
}),
|
||||
// Generate a manifest containing the required script / css for each entry.
|
||||
new WebpackManifestPlugin({
|
||||
fileName: 'entrypoint-manifest.json',
|
||||
publicPath: paths.publicUrlOrPath,
|
||||
generate: (seed, files, entrypoints) => {
|
||||
const entrypointFiles = entrypoints.main.filter(
|
||||
fileName => !fileName.endsWith('.map')
|
||||
);
|
||||
|
||||
const processedEntrypoints = {};
|
||||
for (let key in entrypoints) {
|
||||
processedEntrypoints[key] = {
|
||||
js: entrypoints[key].filter(
|
||||
filename =>
|
||||
// Include JS assets but ignore hot updates because they're not
|
||||
// safe to include as async script tags.
|
||||
filename.endsWith('.js') &&
|
||||
!filename.endsWith('.hot-update.js')
|
||||
),
|
||||
css: entrypoints[key].filter(filename =>
|
||||
filename.endsWith('.css')
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return processedEntrypoints;
|
||||
},
|
||||
}),
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
|
||||
51
fixtures/flight/loader/global.js
Normal file
51
fixtures/flight/loader/global.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import babel from '@babel/core';
|
||||
|
||||
const babelOptions = {
|
||||
babelrc: false,
|
||||
ignore: [/\/(build|node_modules)\//],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-import-meta',
|
||||
'@babel/plugin-transform-react-jsx',
|
||||
],
|
||||
};
|
||||
|
||||
export async function load(url, context, defaultLoad) {
|
||||
const {format} = context;
|
||||
const result = await defaultLoad(url, context, defaultLoad);
|
||||
if (result.format === 'module') {
|
||||
const opt = Object.assign({filename: url}, babelOptions);
|
||||
const newResult = await babel.transformAsync(result.source, opt);
|
||||
if (!newResult) {
|
||||
if (typeof result.source === 'string') {
|
||||
return result;
|
||||
}
|
||||
return {
|
||||
source: Buffer.from(result.source).toString('utf8'),
|
||||
format: 'module',
|
||||
};
|
||||
}
|
||||
return {source: newResult.code, format: 'module'};
|
||||
}
|
||||
return defaultLoad(url, context, defaultLoad);
|
||||
}
|
||||
|
||||
async function babelTransformSource(source, context, defaultTransformSource) {
|
||||
const {format} = context;
|
||||
if (format === 'module') {
|
||||
const opt = Object.assign({filename: context.url}, babelOptions);
|
||||
const newResult = await babel.transformAsync(source, opt);
|
||||
if (!newResult) {
|
||||
if (typeof source === 'string') {
|
||||
return {source};
|
||||
}
|
||||
return {
|
||||
source: Buffer.from(source).toString('utf8'),
|
||||
};
|
||||
}
|
||||
return {source: newResult.code};
|
||||
}
|
||||
return defaultTransformSource(source, context, defaultTransformSource);
|
||||
}
|
||||
|
||||
export const transformSource =
|
||||
process.version < 'v16' ? babelTransformSource : undefined;
|
||||
@@ -43,10 +43,10 @@
|
||||
"postcss-normalize": "^10.0.1",
|
||||
"postcss-preset-env": "^7.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react": "experimental",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "experimental",
|
||||
"react-refresh": "^0.11.0",
|
||||
"resolve": "^1.20.0",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
@@ -59,17 +59,18 @@
|
||||
"undici": "^5.20.0",
|
||||
"webpack": "^5.64.4",
|
||||
"webpack-dev-middleware": "^5.3.1",
|
||||
"webpack-hot-middleware": "^2.25.3"
|
||||
"webpack-hot-middleware": "^2.25.3",
|
||||
"webpack-manifest-plugin": "^4.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"predev": "cp -r ../../build/oss-experimental/* ./node_modules/",
|
||||
"prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/",
|
||||
"dev": "concurrently \"npm run dev:region\" \"npm run dev:global\"",
|
||||
"dev:global": "NODE_ENV=development BUILD_PATH=dist node server/global",
|
||||
"dev:region": "NODE_ENV=development BUILD_PATH=dist nodemon --watch src --watch dist -- --experimental-loader ./loader/index.js --conditions=react-server server/region",
|
||||
"dev:global": "NODE_ENV=development BUILD_PATH=dist node --experimental-loader ./loader/global.js server/global",
|
||||
"dev:region": "NODE_ENV=development BUILD_PATH=dist nodemon --watch src --watch dist -- --experimental-loader ./loader/region.js --conditions=react-server server/region",
|
||||
"start": "node scripts/build.js && concurrently \"npm run start:region\" \"npm run start:global\"",
|
||||
"start:global": "NODE_ENV=production node server/global",
|
||||
"start:region": "NODE_ENV=production node --experimental-loader ./loader/index.js --conditions=react-server server/region",
|
||||
"start:global": "NODE_ENV=production node --experimental-loader ./loader/global.js server/global",
|
||||
"start:region": "NODE_ENV=production node --experimental-loader ./loader/region.js --conditions=react-server server/region",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "node scripts/test.js --env=jsdom"
|
||||
},
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Flight</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -38,7 +38,7 @@ const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
if (!checkRequiredFiles([paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -204,6 +204,5 @@ function build(previousFileSizes) {
|
||||
function copyPublicFolder() {
|
||||
fs.copySync('public', 'build', {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,16 +32,45 @@ babelRegister({
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const fs = require('fs');
|
||||
const fs = require('fs').promises;
|
||||
const compress = require('compression');
|
||||
const chalk = require('chalk');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
const http = require('http');
|
||||
|
||||
const {renderToPipeableStream} = require('react-dom/server');
|
||||
const {createFromNodeStream} = require('react-server-dom-webpack/client');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(compress());
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// In development we host the Webpack server for live bundling.
|
||||
const webpack = require('webpack');
|
||||
const webpackMiddleware = require('webpack-dev-middleware');
|
||||
const webpackHotMiddleware = require('webpack-hot-middleware');
|
||||
const paths = require('../config/paths');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const getClientEnvironment = require('../config/env');
|
||||
|
||||
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
|
||||
|
||||
const config = configFactory('development');
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const appName = require(paths.appPackageJson).name;
|
||||
|
||||
// Create a webpack compiler that is configured with custom messages.
|
||||
const compiler = webpack(config);
|
||||
app.use(
|
||||
webpackMiddleware(compiler, {
|
||||
publicPath: paths.publicUrlOrPath.slice(0, -1),
|
||||
serverSideRender: true,
|
||||
})
|
||||
);
|
||||
app.use(webpackHotMiddleware(compiler));
|
||||
}
|
||||
|
||||
function request(options, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.request(options, res => {
|
||||
@@ -55,12 +84,6 @@ function request(options, body) {
|
||||
}
|
||||
|
||||
app.all('/', async function (req, res, next) {
|
||||
if (req.accepts('text/html')) {
|
||||
// Pass-through to the html file
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// Proxy the request to the regional server.
|
||||
const proxiedHeaders = {
|
||||
'X-Forwarded-Host': req.hostname,
|
||||
@@ -85,52 +108,65 @@ app.all('/', async function (req, res, next) {
|
||||
req
|
||||
);
|
||||
|
||||
try {
|
||||
const rscResponse = await promiseForData;
|
||||
res.set('Content-type', 'text/x-component');
|
||||
rscResponse.pipe(res);
|
||||
} catch (e) {
|
||||
console.error(`Failed to proxy request: ${e.stack}`);
|
||||
res.statusCode = 500;
|
||||
res.end();
|
||||
if (req.accepts('text/html')) {
|
||||
try {
|
||||
const rscResponse = await promiseForData;
|
||||
|
||||
let virtualFs;
|
||||
let buildPath;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const {devMiddleware} = res.locals.webpack;
|
||||
virtualFs = devMiddleware.outputFileSystem.promises;
|
||||
buildPath = devMiddleware.stats.toJson().outputPath;
|
||||
} else {
|
||||
virtualFs = fs;
|
||||
buildPath = path.join(__dirname, '../build/');
|
||||
}
|
||||
// Read the module map from the virtual file system.
|
||||
const moduleMap = JSON.parse(
|
||||
await virtualFs.readFile(
|
||||
path.join(buildPath, 'react-ssr-manifest.json'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
// Read the entrypoints containing the initial JS to bootstrap everything.
|
||||
// For other pages, the chunks in the RSC payload are enough.
|
||||
const mainJSChunks = JSON.parse(
|
||||
await virtualFs.readFile(
|
||||
path.join(buildPath, 'entrypoint-manifest.json'),
|
||||
'utf8'
|
||||
)
|
||||
).main.js;
|
||||
// For HTML, we're a "client" emulator that runs the client code,
|
||||
// so we start by consuming the RSC payload. This needs a module
|
||||
// map that reverse engineers the client-side path to the SSR path.
|
||||
const root = await createFromNodeStream(rscResponse, moduleMap);
|
||||
// Render it into HTML by resolving the client components
|
||||
res.set('Content-type', 'text/html');
|
||||
const {pipe} = renderToPipeableStream(root, {
|
||||
bootstrapScripts: mainJSChunks,
|
||||
});
|
||||
pipe(res);
|
||||
} catch (e) {
|
||||
console.error(`Failed to SSR: ${e.stack}`);
|
||||
res.statusCode = 500;
|
||||
res.end();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const rscResponse = await promiseForData;
|
||||
// For other request, we pass-through the RSC payload.
|
||||
res.set('Content-type', 'text/x-component');
|
||||
rscResponse.pipe(res);
|
||||
} catch (e) {
|
||||
console.error(`Failed to proxy request: ${e.stack}`);
|
||||
res.statusCode = 500;
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// In development we host the Webpack server for live bundling.
|
||||
const webpack = require('webpack');
|
||||
const webpackMiddleware = require('webpack-dev-middleware');
|
||||
const webpackHotMiddleware = require('webpack-hot-middleware');
|
||||
const paths = require('../config/paths');
|
||||
const configFactory = require('../config/webpack.config');
|
||||
const getClientEnvironment = require('../config/env');
|
||||
|
||||
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
|
||||
|
||||
const HOST = '0.0.0.0';
|
||||
const PORT = 3000;
|
||||
|
||||
const config = configFactory('development');
|
||||
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
const appName = require(paths.appPackageJson).name;
|
||||
|
||||
// Create a webpack compiler that is configured with custom messages.
|
||||
const compiler = webpack(config);
|
||||
app.use(
|
||||
webpackMiddleware(compiler, {
|
||||
writeToDisk: filePath => {
|
||||
return /(react-client-manifest|react-ssr-manifest)\.json$/.test(
|
||||
filePath
|
||||
);
|
||||
},
|
||||
publicPath: paths.publicUrlOrPath.slice(0, -1),
|
||||
})
|
||||
);
|
||||
app.use(
|
||||
webpackHotMiddleware(compiler, {
|
||||
/* Options */
|
||||
})
|
||||
);
|
||||
app.use(express.static('public'));
|
||||
} else {
|
||||
// In production we host the static build output.
|
||||
|
||||
@@ -50,14 +50,47 @@ app.get('/', async function (req, res) {
|
||||
);
|
||||
// const m = require('../src/App.js');
|
||||
const m = await import('../src/App.js');
|
||||
const dist = process.env.NODE_ENV === 'development' ? 'dist' : 'build';
|
||||
const data = await readFile(
|
||||
path.resolve(__dirname, `../${dist}/react-client-manifest.json`),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
let moduleMap;
|
||||
let mainCSSChunks;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Read the module map from the HMR server in development.
|
||||
moduleMap = await (
|
||||
await fetch('http://localhost:3000/react-client-manifest.json')
|
||||
).json();
|
||||
mainCSSChunks = (
|
||||
await (
|
||||
await fetch('http://localhost:3000/entrypoint-manifest.json')
|
||||
).json()
|
||||
).main.css;
|
||||
} else {
|
||||
// Read the module map from the static build in production.
|
||||
moduleMap = JSON.parse(
|
||||
await readFile(
|
||||
path.resolve(__dirname, `../build/react-client-manifest.json`),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
mainCSSChunks = JSON.parse(
|
||||
await readFile(
|
||||
path.resolve(__dirname, `../build/entrypoint-manifest.json`),
|
||||
'utf8'
|
||||
)
|
||||
).main.css;
|
||||
}
|
||||
const App = m.default.default || m.default;
|
||||
const moduleMap = JSON.parse(data);
|
||||
const {pipe} = renderToPipeableStream(React.createElement(App), moduleMap);
|
||||
const root = [
|
||||
// Prepend the App's tree with stylesheets required for this entrypoint.
|
||||
mainCSSChunks.map(filename =>
|
||||
React.createElement('link', {
|
||||
rel: 'stylesheet',
|
||||
href: filename,
|
||||
precedence: 'default',
|
||||
})
|
||||
),
|
||||
React.createElement(App),
|
||||
];
|
||||
const {pipe} = renderToPipeableStream(root, moduleMap);
|
||||
pipe(res);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,21 +14,30 @@ export default async function App() {
|
||||
const res = await fetch('http://localhost:3001/todos');
|
||||
const todos = await res.json();
|
||||
return (
|
||||
<Container>
|
||||
<h1>Hello, world</h1>
|
||||
<Counter />
|
||||
<Counter2 />
|
||||
<ul>
|
||||
{todos.map(todo => (
|
||||
<li key={todo.id}>{todo.text}</li>
|
||||
))}
|
||||
</ul>
|
||||
<ShowMore>
|
||||
<p>Lorem ipsum</p>
|
||||
</ShowMore>
|
||||
<div>
|
||||
<Button action={like}>Like</Button>
|
||||
</div>
|
||||
</Container>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Flight</title>
|
||||
</head>
|
||||
<body>
|
||||
<Container>
|
||||
<h1>Hello, world</h1>
|
||||
<Counter />
|
||||
<Counter2 />
|
||||
<ul>
|
||||
{todos.map(todo => (
|
||||
<li key={todo.id}>{todo.text}</li>
|
||||
))}
|
||||
</ul>
|
||||
<ShowMore>
|
||||
<p>Lorem ipsum</p>
|
||||
</ShowMore>
|
||||
<div>
|
||||
<Button action={like}>Like</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ import {Suspense} from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import ReactServerDOMReader from 'react-server-dom-webpack/client';
|
||||
|
||||
// TODO: This should be a dependency of the App but we haven't implemented CSS in Node yet.
|
||||
import './style.css';
|
||||
|
||||
let data = ReactServerDOMReader.createFromFetch(
|
||||
fetch('/', {
|
||||
headers: {
|
||||
@@ -24,12 +27,12 @@ let data = ReactServerDOMReader.createFromFetch(
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Once not needed once children can be promises.
|
||||
function Content() {
|
||||
return React.use(data);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<Suspense fallback={<h1>Loading...</h1>}>
|
||||
<Content />
|
||||
</Suspense>
|
||||
);
|
||||
// TODO: This transition shouldn't really be necessary but it is for now.
|
||||
React.startTransition(() => {
|
||||
ReactDOM.hydrateRoot(document, <Content />);
|
||||
});
|
||||
|
||||
3
fixtures/flight/src/style.css
Normal file
3
fixtures/flight/src/style.css
Normal file
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
font-family: Helvetica;
|
||||
}
|
||||
@@ -7364,13 +7364,13 @@ react-dev-utils@^12.0.1:
|
||||
strip-ansi "^6.0.1"
|
||||
text-table "^0.2.0"
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||
react-dom@experimental:
|
||||
version "0.0.0-experimental-6ff1733e6-20230225"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-0.0.0-experimental-6ff1733e6-20230225.tgz#47c1e80f21230e6c650ba9e43d15fccd616441be"
|
||||
integrity sha512-1vGCQDhmSOwBIb8QbTaSjBUysebPhl7WCcGuT6dW+HdlxFDvClL0M47K1e8kZWiuCkMUDjJaTlC4J32PgznbFw==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
scheduler "0.0.0-experimental-6ff1733e6-20230225"
|
||||
|
||||
react-error-overlay@^6.0.11:
|
||||
version "6.0.11"
|
||||
@@ -7397,10 +7397,10 @@ react-refresh@^0.11.0:
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
|
||||
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||
react@experimental:
|
||||
version "0.0.0-experimental-6ff1733e6-20230225"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-6ff1733e6-20230225.tgz#24bca9d60c6b3597f389e8bcd07e7b853dddc917"
|
||||
integrity sha512-5syUtEwwbWzDHN84xNu9C9cciLW9sY4c19fG27EU5ApS1s+i7fFtS+KtyPMHU9S4eK0u65uQU3hWMqFvqzLHhw==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
@@ -7681,10 +7681,10 @@ saxes@^5.0.1:
|
||||
dependencies:
|
||||
xmlchars "^2.2.0"
|
||||
|
||||
scheduler@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
|
||||
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
|
||||
scheduler@0.0.0-experimental-6ff1733e6-20230225:
|
||||
version "0.0.0-experimental-6ff1733e6-20230225"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.0.0-experimental-6ff1733e6-20230225.tgz#9de04947f82afa784de799aa09aaad92ee67cb19"
|
||||
integrity sha512-YDyRM4ohU6NmzsbiJ/UUdRB4ulz27n+3Su8LZ8cENrQDuu2us3sPN+y8CMGeqaNYJlS0GQTHNcLa2LZIK9BP5Q==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
@@ -7838,6 +7838,11 @@ slash@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
|
||||
integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
|
||||
|
||||
source-list-map@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
|
||||
|
||||
source-map-js@^1.0.1, source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
@@ -8576,6 +8581,22 @@ webpack-hot-middleware@^2.25.3:
|
||||
html-entities "^2.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
webpack-manifest-plugin@^4.0.2:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz#10f8dbf4714ff93a215d5a45bcc416d80506f94f"
|
||||
integrity sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==
|
||||
dependencies:
|
||||
tapable "^2.0.0"
|
||||
webpack-sources "^2.2.0"
|
||||
|
||||
webpack-sources@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd"
|
||||
integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==
|
||||
dependencies:
|
||||
source-list-map "^2.0.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
webpack-sources@^3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||
|
||||
@@ -256,30 +256,29 @@ export default class ReactFlightWebpackPlugin {
|
||||
[string]: {specifier: string, name: string},
|
||||
} = {};
|
||||
|
||||
ssrManifest[id] = ssrExports;
|
||||
|
||||
['', '*']
|
||||
.concat(
|
||||
Array.isArray(moduleProvidedExports)
|
||||
? moduleProvidedExports
|
||||
: [],
|
||||
)
|
||||
.forEach(function (name) {
|
||||
clientExports[name] = {
|
||||
id,
|
||||
chunks: chunkIds,
|
||||
name: name,
|
||||
};
|
||||
ssrExports[name] = {
|
||||
specifier: module.resource,
|
||||
name: name,
|
||||
};
|
||||
});
|
||||
const href = pathToFileURL(module.resource).href;
|
||||
|
||||
if (href !== undefined) {
|
||||
['', '*']
|
||||
.concat(
|
||||
Array.isArray(moduleProvidedExports)
|
||||
? moduleProvidedExports
|
||||
: [],
|
||||
)
|
||||
.forEach(function (name) {
|
||||
clientExports[name] = {
|
||||
id,
|
||||
chunks: chunkIds,
|
||||
name: name,
|
||||
};
|
||||
ssrExports[name] = {
|
||||
specifier: href,
|
||||
name: name,
|
||||
};
|
||||
});
|
||||
|
||||
clientManifest[href] = clientExports;
|
||||
ssrManifest[href] = ssrExports;
|
||||
ssrManifest[id] = ssrExports;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user