diff --git a/fixtures/view-transition/README.md b/fixtures/view-transition/README.md new file mode 100644 index 0000000000000..7f5642ee9c4c3 --- /dev/null +++ b/fixtures/view-transition/README.md @@ -0,0 +1,30 @@ +# View Transition + +A test case for View Transitions. + +## Setup + +To reference a local build of React, first run `npm run build` at the root +of the React project. Then: + +``` +cd fixtures/view-transition +yarn +yarn start +``` + +The `start` command runs a webpack dev server and a server-side rendering server in development mode with hot reloading. + +**Note: whenever you make changes to React and rebuild it, you need to re-run `yarn` in this folder:** + +``` +yarn +``` + +If you want to try the production mode instead run: + +``` +yarn start:prod +``` + +This will pre-build all static resources and then start a server-side rendering HTTP server that hosts the React app and service the static resources (without hot reloading). diff --git a/fixtures/view-transition/package.json b/fixtures/view-transition/package.json new file mode 100644 index 0000000000000..c6226c6f78958 --- /dev/null +++ b/fixtures/view-transition/package.json @@ -0,0 +1,48 @@ +{ + "name": "react-fixtures-view-transition", + "version": "0.1.0", + "private": true, + "devDependencies": { + "concurrently": "3.1.0", + "http-proxy-middleware": "3.0.3", + "react-scripts": "5.0.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.11" + }, + "dependencies": { + "@babel/register": "^7.25.9", + "express": "^4.14.0", + "ignore-styles": "^5.0.1", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "scripts": { + "predev": "cp -r ../../build/oss-experimental/* ./node_modules/", + "prestart": "cp -r ../../build/oss-experimental/* ./node_modules/", + "prebuild": "cp -r ../../build/oss-experimental/* ./node_modules/", + "dev": "concurrently \"npm run dev:server\" \"npm run dev:client\"", + "dev:client": "PORT=3001 react-scripts start", + "dev:server": "NODE_ENV=development node server", + "start": "react-scripts build && NODE_ENV=production node server", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/fixtures/view-transition/public/favicon.ico b/fixtures/view-transition/public/favicon.ico new file mode 100644 index 0000000000000..5c125de5d897c Binary files /dev/null and b/fixtures/view-transition/public/favicon.ico differ diff --git a/fixtures/view-transition/public/index.html b/fixtures/view-transition/public/index.html new file mode 100644 index 0000000000000..a94d9ac64a92b --- /dev/null +++ b/fixtures/view-transition/public/index.html @@ -0,0 +1,13 @@ + + + + + + diff --git a/fixtures/view-transition/server/index.js b/fixtures/view-transition/server/index.js new file mode 100644 index 0000000000000..3f542b8f6e67d --- /dev/null +++ b/fixtures/view-transition/server/index.js @@ -0,0 +1,71 @@ +require('ignore-styles'); +const babelRegister = require('@babel/register'); +const proxy = require('http-proxy-middleware'); + +babelRegister({ + ignore: [/\/(build|node_modules)\//], + presets: ['react-app'], +}); + +const express = require('express'); +const path = require('path'); + +const app = express(); + +// Application +if (process.env.NODE_ENV === 'development') { + app.get('/', function (req, res) { + // In development mode we clear the module cache between each request to + // get automatic hot reloading. + for (var key in require.cache) { + delete require.cache[key]; + } + const render = require('./render').default; + render(req.url, res); + }); +} else { + const render = require('./render').default; + app.get('/', function (req, res) { + render(req.url, res); + }); +} + +// Static resources +app.use(express.static(path.resolve(__dirname, '..', 'build'))); + +// Proxy everything else to create-react-app's webpack development server +if (process.env.NODE_ENV === 'development') { + app.use( + '/', + proxy.createProxyMiddleware({ + ws: true, + changeOrigin: true, + target: 'http://127.0.0.1:3001', + }) + ); +} + +app.listen(3000, () => { + console.log('Listening on port 3000...'); +}); + +app.on('error', function (error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' ? '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; + } +}); diff --git a/fixtures/view-transition/server/render.js b/fixtures/view-transition/server/render.js new file mode 100644 index 0000000000000..0d956fd66caf7 --- /dev/null +++ b/fixtures/view-transition/server/render.js @@ -0,0 +1,44 @@ +import React from 'react'; +import {renderToPipeableStream} from 'react-dom/server'; + +import App from '../src/components/App'; + +let assets; +if (process.env.NODE_ENV === 'development') { + // Use the bundle from create-react-app's server in development mode. + assets = { + 'main.js': '/static/js/bundle.js', + // 'main.css': '', + }; +} else { + assets = require('../build/asset-manifest.json').files; +} + +export default function render(url, res) { + res.socket.on('error', error => { + // Log fatal errors + console.error('Fatal', error); + }); + let didError = false; + const {pipe, abort} = renderToPipeableStream(, { + bootstrapScripts: [assets['main.js']], + onShellReady() { + // If something errored before we started streaming, we set the error code appropriately. + res.statusCode = didError ? 500 : 200; + res.setHeader('Content-type', 'text/html'); + pipe(res); + }, + onShellError(x) { + // Something errored before we could complete the shell so we emit an alternative shell. + res.statusCode = 500; + res.send('

Error

'); + }, + onError(x) { + didError = true; + console.error(x); + }, + }); + // Abandon and switch to client rendering after 5 seconds. + // Try lowering this to see the client recover. + setTimeout(abort, 5000); +} diff --git a/fixtures/view-transition/src/components/App.js b/fixtures/view-transition/src/components/App.js new file mode 100644 index 0000000000000..6867b29d4c5a5 --- /dev/null +++ b/fixtures/view-transition/src/components/App.js @@ -0,0 +1,12 @@ +import React from 'react'; + +import Chrome from './Chrome'; +import Page from './Page'; + +export default function App({assets}) { + return ( + + + + ); +} diff --git a/fixtures/view-transition/src/components/Chrome.css b/fixtures/view-transition/src/components/Chrome.css new file mode 100644 index 0000000000000..b019b57b1db81 --- /dev/null +++ b/fixtures/view-transition/src/components/Chrome.css @@ -0,0 +1,5 @@ +body { + margin: 10px; + padding: 0; + font-family: sans-serif; +} diff --git a/fixtures/view-transition/src/components/Chrome.js b/fixtures/view-transition/src/components/Chrome.js new file mode 100644 index 0000000000000..0cae4a8dd1953 --- /dev/null +++ b/fixtures/view-transition/src/components/Chrome.js @@ -0,0 +1,33 @@ +import React, {Component} from 'react'; + +import './Chrome.css'; + +export default class Chrome extends Component { + render() { + const assets = this.props.assets; + return ( + + + + + + + {this.props.title} + + +