Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions fixtures/view-transition/README.md
Original file line number Diff line number Diff line change
@@ -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).
48 changes: 48 additions & 0 deletions fixtures/view-transition/package.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
}
Binary file added fixtures/view-transition/public/favicon.ico
Binary file not shown.
13 changes: 13 additions & 0 deletions fixtures/view-transition/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<body>
<script>
/*
This is just a placeholder to make react-scripts happy.
We're not using it. If we end up here, redirect to the
primary server.
*/
location.href = '//localhost:3000/';
</script>
</body>
</html>
71 changes: 71 additions & 0 deletions fixtures/view-transition/server/index.js
Original file line number Diff line number Diff line change
@@ -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;
}
});
44 changes: 44 additions & 0 deletions fixtures/view-transition/server/render.js
Original file line number Diff line number Diff line change
@@ -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(<App assets={assets} />, {
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('<!doctype><p>Error</p>');
},
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);
}
12 changes: 12 additions & 0 deletions fixtures/view-transition/src/components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import Chrome from './Chrome';
import Page from './Page';

export default function App({assets}) {
return (
<Chrome title="Hello World" assets={assets}>
<Page />
</Chrome>
);
}
5 changes: 5 additions & 0 deletions fixtures/view-transition/src/components/Chrome.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
body {
margin: 10px;
padding: 0;
font-family: sans-serif;
}
33 changes: 33 additions & 0 deletions fixtures/view-transition/src/components/Chrome.js
Original file line number Diff line number Diff line change
@@ -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 (
<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>{this.props.title}</title>
</head>
<body>
<noscript
dangerouslySetInnerHTML={{
__html: `<b>Enable JavaScript to run this app.</b>`,
}}
/>
{this.props.children}
<script
dangerouslySetInnerHTML={{
__html: `assetManifest = ${JSON.stringify(assets)};`,
}}
/>
</body>
</html>
);
}
}
Empty file.
93 changes: 93 additions & 0 deletions fixtures/view-transition/src/components/Page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, {
unstable_ViewTransition as ViewTransition,
startTransition,
useEffect,
useState,
unstable_Activity as Activity,
} from 'react';

import './Page.css';

import transitions from './Transitions.module.css';

const a = (
<div key="a">
<ViewTransition>
<div>a</div>
</ViewTransition>
</div>
);

const b = (
<div key="b">
<ViewTransition>
<div>b</div>
</ViewTransition>
</div>
);

function Component() {
return (
<ViewTransition
className={
transitions['enter-slide-right'] + ' ' + transitions['exit-slide-left']
}>
<p>Slide In from Left, Slide Out to Right</p>
</ViewTransition>
);
}

export default function Page() {
const [show, setShow] = useState(false);
useEffect(() => {
startTransition(() => {
setShow(true);
});
}, []);
const exclamation = (
<ViewTransition name="exclamation">
<span>!</span>
</ViewTransition>
);
return (
<div>
<button
onClick={() => {
startTransition(() => {
setShow(show => !show);
});
}}>
{show ? 'A' : 'B'}
</button>
<ViewTransition>
<div>
{show ? (
<div>
{a}
{b}
</div>
) : (
<div>
{b}
{a}
</div>
)}
<ViewTransition>
{show ? <div>hello{exclamation}</div> : <section>Loading</section>}
</ViewTransition>
{show ? null : (
<ViewTransition>
<div>world{exclamation}</div>
</ViewTransition>
)}
<Activity mode={show ? 'visible' : 'hidden'}>
<ViewTransition>
<div>!!</div>
</ViewTransition>
</Activity>
{show ? <Component /> : <p>&nbsp;</p>}
</div>
</ViewTransition>
</div>
);
}
28 changes: 28 additions & 0 deletions fixtures/view-transition/src/components/Transitions.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@keyframes enter-slide-right {
0% {
opacity: 0;
translate: -200px 0;
}
100% {
opacity: 1;
translate: 0 0;
}
}

@keyframes exit-slide-left {
0% {
opacity: 1;
translate: 0 0;
}
100% {
opacity: 0;
translate: 200px 0;
}
}

::view-transition-new(.enter-slide-right):only-child {
animation: enter-slide-right ease-in 0.25s;
}
::view-transition-old(.exit-slide-left):only-child {
animation: exit-slide-left ease-in 0.25s;
}
6 changes: 6 additions & 0 deletions fixtures/view-transition/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import {hydrateRoot} from 'react-dom/client';

import App from './components/App';

hydrateRoot(document, <App assets={window.assetManifest} />);
Loading
Loading