mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge 970ba88a46 into sapling-pr-archive-mofeiZ
This commit is contained in:
@@ -17,13 +17,22 @@
|
||||
"@babel/parser": "^7.26",
|
||||
"@babel/plugin-syntax-typescript": "^7.25.9",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"algoliasearch": "^5.23.3",
|
||||
"cheerio": "^1.0.0",
|
||||
"html-to-text": "^9.0.5",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.3.3",
|
||||
"puppeteer": "^24.7.2",
|
||||
"ts-jest": "^29.3.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-transform-runtime": "^7.26.10",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@babel/preset-typescript": "^7.27.0",
|
||||
"@types/html-to-text": "^9.0.4"
|
||||
},
|
||||
"license": "MIT",
|
||||
|
||||
@@ -20,6 +20,7 @@ import * as cheerio from 'cheerio';
|
||||
import {queryAlgolia} from './utils/algolia';
|
||||
import assertExhaustive from './utils/assertExhaustive';
|
||||
import {convert} from 'html-to-text';
|
||||
import {measurePerformance} from './utils/runtimePerf';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'React',
|
||||
@@ -353,6 +354,104 @@ Server Components - Shift data-heavy logic to the server whenever possible. Brea
|
||||
],
|
||||
}));
|
||||
|
||||
server.tool(
|
||||
'review-react-runtime',
|
||||
'Review the runtime of the code and get performance data to evaluate the proposed solution, the react code that is passed into this tool MUST contain an App component.',
|
||||
{
|
||||
text: z.string(),
|
||||
},
|
||||
async ({text}) => {
|
||||
try {
|
||||
const iterations = 20;
|
||||
|
||||
let perfData = {
|
||||
renderTime: 0,
|
||||
webVitals: {
|
||||
cls: 0,
|
||||
lcp: 0,
|
||||
inp: 0,
|
||||
fid: 0,
|
||||
ttfb: 0,
|
||||
},
|
||||
reactProfilerMetrics: {
|
||||
id: 0,
|
||||
phase: 0,
|
||||
actualDuration: 0,
|
||||
baseDuration: 0,
|
||||
startTime: 0,
|
||||
commitTime: 0,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const performanceResults = await measurePerformance(text);
|
||||
perfData.renderTime += performanceResults.renderTime;
|
||||
perfData.webVitals.cls += performanceResults.webVitals.cls?.value || 0;
|
||||
perfData.webVitals.lcp += performanceResults.webVitals.lcp?.value || 0;
|
||||
perfData.webVitals.inp += performanceResults.webVitals.inp?.value || 0;
|
||||
perfData.webVitals.fid += performanceResults.webVitals.fid?.value || 0;
|
||||
perfData.webVitals.ttfb +=
|
||||
performanceResults.webVitals.ttfb?.value || 0;
|
||||
|
||||
perfData.reactProfilerMetrics.id +=
|
||||
performanceResults.reactProfilerMetrics.actualDuration?.value || 0;
|
||||
perfData.reactProfilerMetrics.phase +=
|
||||
performanceResults.reactProfilerMetrics.phase?.value || 0;
|
||||
perfData.reactProfilerMetrics.actualDuration +=
|
||||
performanceResults.reactProfilerMetrics.actualDuration?.value || 0;
|
||||
perfData.reactProfilerMetrics.baseDuration +=
|
||||
performanceResults.reactProfilerMetrics.baseDuration?.value || 0;
|
||||
perfData.reactProfilerMetrics.startTime +=
|
||||
performanceResults.reactProfilerMetrics.startTime?.value || 0;
|
||||
perfData.reactProfilerMetrics.commitTime +=
|
||||
performanceResults.reactProfilerMetrics.commitTim?.value || 0;
|
||||
}
|
||||
|
||||
const formattedResults = `
|
||||
# React Component Performance Results
|
||||
|
||||
## Mean Render Time
|
||||
${perfData.renderTime / iterations}ms
|
||||
|
||||
## Mean Web Vitals
|
||||
- Cumulative Layout Shift (CLS): ${perfData.webVitals.cls / iterations}
|
||||
- Largest Contentful Paint (LCP): ${perfData.webVitals.lcp / iterations}ms
|
||||
- Interaction to Next Paint (INP): ${perfData.webVitals.inp / iterations}ms
|
||||
- First Input Delay (FID): ${perfData.webVitals.fid / iterations}ms
|
||||
- Time to First Byte (TTFB): ${perfData.webVitals.ttfb / iterations}ms
|
||||
|
||||
## Mean React Profiler
|
||||
- Actual Duration: ${perfData.reactProfilerMetrics.actualDuration / iterations}ms
|
||||
- Base Duration: ${perfData.reactProfilerMetrics.baseDuration / iterations}ms
|
||||
- Start Time: ${perfData.reactProfilerMetrics.startTime / iterations}ms
|
||||
- Commit Time: ${perfData.reactProfilerMetrics.commitTime / iterations}ms
|
||||
|
||||
These metrics can help you evaluate the performance of your React component. Lower values generally indicate better performance.
|
||||
`;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: formattedResults,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import * as babel from '@babel/core';
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
export async function measurePerformance(code: any) {
|
||||
let options = {
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
presets: [['@babel/preset-env'], '@babel/preset-react'],
|
||||
};
|
||||
|
||||
const parsed = await babel.parseAsync(code, options);
|
||||
|
||||
if (!parsed) {
|
||||
throw new Error('Failed to parse code');
|
||||
}
|
||||
|
||||
const transpiled = await transformAsync(parsed);
|
||||
|
||||
if (!transpiled) {
|
||||
throw new Error('Failed to transpile code');
|
||||
}
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
protocolTimeout: 600_000,
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({width: 1280, height: 720});
|
||||
const html = buildHtml(transpiled);
|
||||
await page.setContent(html, {waitUntil: 'networkidle0'});
|
||||
|
||||
await page.waitForFunction(
|
||||
'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
|
||||
{timeout: 600_000},
|
||||
);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
return (window as any).__RESULT__;
|
||||
});
|
||||
|
||||
await browser.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform AST into browser-compatible JavaScript
|
||||
* @param {babel.types.File} ast - The AST to transform
|
||||
* @param {Object} opts - Transformation options
|
||||
* @returns {Promise<string>} - The transpiled code
|
||||
*/
|
||||
async function transformAsync(ast: babel.types.Node) {
|
||||
const result = await babel.transformFromAstAsync(ast, undefined, {
|
||||
filename: 'file.jsx',
|
||||
presets: [['@babel/preset-env'], '@babel/preset-react'],
|
||||
plugins: [
|
||||
() => ({
|
||||
visitor: {
|
||||
ImportDeclaration(path: any) {
|
||||
const value = path.node.source.value;
|
||||
if (value === 'react' || value === 'react-dom') {
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
return result?.code || '';
|
||||
}
|
||||
|
||||
function buildHtml(transpiled: string) {
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>React Performance Test</title>
|
||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/web-vitals@3.0.0/dist/web-vitals.iife.js"></script>
|
||||
<style>
|
||||
body { margin: 0; }
|
||||
#root { padding: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
window.__RESULT__ = {
|
||||
renderTime: null,
|
||||
webVitals: {},
|
||||
reactProfilerMetrics: {},
|
||||
error: null
|
||||
};
|
||||
|
||||
webVitals.onCLS((metric) => { window.__RESULT__.webVitals.cls = metric; });
|
||||
webVitals.onLCP((metric) => { window.__RESULT__.webVitals.lcp = metric; });
|
||||
webVitals.onINP((metric) => { window.__RESULT__.webVitals.inp = metric; });
|
||||
webVitals.onFID((metric) => { window.__RESULT__.webVitals.fid = metric; });
|
||||
webVitals.onTTFB((metric) => { window.__RESULT__.webVitals.ttfb = metric; });
|
||||
|
||||
try {
|
||||
${transpiled}
|
||||
|
||||
window.App = App;
|
||||
|
||||
// Render the component to the DOM with profiling
|
||||
const AppComponent = window.App || (() => React.createElement('div', null, 'No App component exported'));
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'), {
|
||||
onUncaughtError: (error, errorInfo) => {
|
||||
window.__RESULT__.error = error;
|
||||
}
|
||||
});
|
||||
|
||||
const renderStart = performance.now()
|
||||
|
||||
root.render(
|
||||
React.createElement(React.Profiler, {
|
||||
id: 'App',
|
||||
onRender: (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
|
||||
window.__RESULT__.reactProfilerMetrics.id = id;
|
||||
window.__RESULT__.reactProfilerMetrics.phase = phase;
|
||||
window.__RESULT__.reactProfilerMetrics.actualDuration = actualDuration;
|
||||
window.__RESULT__.reactProfilerMetrics.baseDuration = baseDuration;
|
||||
window.__RESULT__.reactProfilerMetrics.startTime = startTime;
|
||||
window.__RESULT__.reactProfilerMetrics.commitTime = commitTime;
|
||||
}
|
||||
}, React.createElement(AppComponent))
|
||||
);
|
||||
|
||||
const renderEnd = performance.now();
|
||||
|
||||
window.__RESULT__.renderTime = renderEnd - renderStart;
|
||||
} catch (error) {
|
||||
console.error('Error rendering component:', error);
|
||||
window.__RESULT__.error = {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
window.onerror = function(message, url, lineNumber) {
|
||||
window.__RESULT__.error = message;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
+2827
-2523
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ class ThrottledWritable extends Writable {
|
||||
constructor(destination) {
|
||||
super();
|
||||
this.destination = destination;
|
||||
this.delay = 150;
|
||||
this.delay = 10;
|
||||
}
|
||||
|
||||
_write(chunk, encoding, callback) {
|
||||
@@ -49,10 +49,10 @@ export default function render(url, res) {
|
||||
// Log fatal errors
|
||||
console.error('Fatal', error);
|
||||
});
|
||||
console.log('hello');
|
||||
let didError = false;
|
||||
const {pipe, abort} = renderToPipeableStream(<App assets={assets} />, {
|
||||
bootstrapScripts: [assets['main.js']],
|
||||
progressiveChunkSize: 1024,
|
||||
onShellReady() {
|
||||
// If something errored before we started streaming, we set the error code appropriately.
|
||||
res.statusCode = didError ? 500 : 200;
|
||||
|
||||
@@ -39,11 +39,7 @@ export default class Chrome extends Component {
|
||||
{this.props.children}
|
||||
</Theme.Provider>
|
||||
</Suspense>
|
||||
<p>This should appear in the first paint.</p>
|
||||
<Suspense fallback="Loading...">
|
||||
<p>This content should not block paint.</p>
|
||||
<LargeContent />
|
||||
</Suspense>
|
||||
<LargeContent />
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `assetManifest = ${JSON.stringify(assets)};`,
|
||||
|
||||
@@ -1,243 +1,291 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import React, {Fragment, Suspense} from 'react';
|
||||
|
||||
export default function LargeContent() {
|
||||
return (
|
||||
<Fragment>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
|
||||
porttitor tortor ac lectus faucibus, eget eleifend elit hendrerit.
|
||||
Integer porttitor nisi in leo congue rutrum. Morbi sed ante posuere,
|
||||
aliquam lorem ac, imperdiet orci. Duis malesuada gravida pharetra. Cras
|
||||
facilisis arcu diam, id dictum lorem imperdiet a. Suspendisse aliquet
|
||||
tempus tortor et ultricies. Aliquam libero velit, posuere tempus ante
|
||||
sed, pellentesque tincidunt lorem. Nullam iaculis, eros a varius
|
||||
aliquet, tortor felis tempor metus, nec cursus felis eros aliquam nulla.
|
||||
Vivamus ut orci sed mauris congue lacinia. Cras eget blandit neque.
|
||||
Pellentesque a massa in turpis ullamcorper volutpat vel at massa. Sed
|
||||
ante est, auctor non diam non, vulputate ultrices metus. Maecenas dictum
|
||||
fermentum quam id aliquam. Donec porta risus vitae pretium posuere.
|
||||
Fusce facilisis eros in lacus tincidunt congue.
|
||||
</p>
|
||||
<p>
|
||||
Pellentesque habitant morbi tristique senectus et netus et malesuada
|
||||
fames ac turpis egestas. Phasellus dolor ante, iaculis vel nisl vitae,
|
||||
ornare ornare orci. Praesent sit amet lobortis sapien. Suspendisse
|
||||
pharetra posuere libero ut dapibus. Donec condimentum ante urna. Aliquam
|
||||
laoreet tincidunt lacus, sed interdum tortor dapibus elementum. Nam sed
|
||||
faucibus lorem. Suspendisse finibus, velit sed molestie finibus, risus
|
||||
purus mollis ante, sit amet aliquet sapien nulla ut nibh. In eget ligula
|
||||
metus. Duis in purus mattis, blandit magna nec, dictum nunc.
|
||||
</p>
|
||||
<p>
|
||||
Sed convallis magna id tortor blandit dictum. Suspendisse in porttitor
|
||||
neque. Integer quis metus consequat, rutrum est sit amet, finibus justo.
|
||||
In hac habitasse platea dictumst. Nullam sagittis, risus sed vehicula
|
||||
porta, sapien elit ultrices nibh, vel luctus odio tortor et ante. Sed
|
||||
porta enim in hendrerit tristique. Pellentesque id feugiat libero, sit
|
||||
amet tempor enim. Proin gravida nisl justo, vel ornare dolor bibendum
|
||||
ac. Mauris scelerisque mattis facilisis. Praesent sodales augue mollis
|
||||
orci vulputate aliquet. Mauris molestie luctus neque, sed congue elit
|
||||
congue ut. Cras quis tortor augue. In auctor nulla vel turpis dapibus
|
||||
egestas. Phasellus consequat rhoncus nisi sed dignissim. Quisque varius
|
||||
justo non ex lobortis finibus cursus nec justo. Nulla erat neque,
|
||||
commodo et sem convallis, tristique faucibus odio.
|
||||
</p>
|
||||
<p>
|
||||
Ut condimentum volutpat sem, id accumsan augue placerat vel. Donec ac
|
||||
efficitur turpis. Suspendisse pretium odio euismod sapien bibendum, sed
|
||||
tempus est condimentum. Etiam nisl magna, consequat at ullamcorper at,
|
||||
sollicitudin eu eros. In mattis ligula arcu. Sed eu consectetur turpis,
|
||||
id molestie ligula. Vestibulum et venenatis enim. Donec condimentum
|
||||
vitae nisi et placerat. Sed fringilla vehicula egestas. Proin
|
||||
consectetur, nibh non ornare scelerisque, diam lorem cursus lectus, ut
|
||||
mattis mauris purus id mi. Curabitur non ligula sit amet augue molestie
|
||||
vulputate. Donec maximus magna at volutpat aliquet. Pellentesque
|
||||
dignissim nulla eget odio eleifend tincidunt. Etiam diam lorem, ornare
|
||||
vel scelerisque vel, iaculis id risus. Donec aliquet aliquam felis, ac
|
||||
vehicula lacus suscipit vitae. Morbi eu ligula elit.
|
||||
</p>
|
||||
<p>
|
||||
Praesent pellentesque, libero ut faucibus tempor, purus elit consequat
|
||||
metus, in ornare nulla lectus at erat. Duis quis blandit turpis. Fusce
|
||||
at ligula rutrum metus molestie tempor sit amet eu justo. Maecenas
|
||||
tincidunt nisl nunc. Morbi ac metus tempor, pretium arcu vel, dapibus
|
||||
velit. Nulla convallis ligula at porta mollis. Duis magna ante, mollis
|
||||
eget nibh in, congue tempor dolor. Sed tincidunt sagittis arcu, in
|
||||
ultricies neque tempor non. Suspendisse eget nunc neque. Nulla sit amet
|
||||
odio volutpat, maximus purus id, dictum metus. Integer consequat, orci
|
||||
nec ullamcorper porta, mauris libero vestibulum ipsum, nec tempor tellus
|
||||
enim non nunc. Quisque nisl risus, dapibus sit amet purus nec, aliquam
|
||||
finibus metus. Nullam condimentum urna viverra finibus cursus. Proin et
|
||||
sollicitudin tellus, porta fermentum felis. Maecenas ac turpis sed dui
|
||||
condimentum interdum sed sed erat. Mauris ut dignissim erat.
|
||||
</p>
|
||||
<p>
|
||||
Proin varius porta dui, id fringilla elit lobortis eget. Integer at
|
||||
metus elementum, efficitur eros id, euismod est. Morbi vestibulum nibh
|
||||
ac leo luctus sagittis. Praesent rhoncus, risus sit amet mattis dictum,
|
||||
diam sapien tempor neque, vel dignissim nulla neque eget ex. Nam
|
||||
sollicitudin metus quis ullamcorper dapibus. Nam tristique euismod
|
||||
efficitur. Pellentesque rhoncus vel sem eget lacinia. Pellentesque
|
||||
volutpat velit ac dignissim luctus. Vivamus euismod tortor at ligula
|
||||
mattis porta. Vestibulum ante ipsum primis in faucibus orci luctus et
|
||||
ultrices posuere cubilia curae;
|
||||
</p>
|
||||
<p>
|
||||
Proin blandit vulputate efficitur. Pellentesque sit amet porta odio.
|
||||
Nunc pulvinar varius rhoncus. Mauris fermentum leo a imperdiet pretium.
|
||||
Mauris scelerisque justo vel ante egestas, eget tempus neque malesuada.
|
||||
Sed dictum ex vel justo dignissim, aliquam commodo diam rutrum. Integer
|
||||
dignissim est ullamcorper augue laoreet consectetur id at diam. Vivamus
|
||||
molestie blandit urna, eget pulvinar augue dictum vestibulum. Duis
|
||||
maximus bibendum mauris, ut ultricies elit rhoncus eu. Praesent gravida
|
||||
placerat mauris. Praesent tempor ipsum at nibh rhoncus sagittis. Duis
|
||||
non sem turpis. Quisque et metus leo. Sed eu purus lorem. Pellentesque
|
||||
dictum metus sed leo viverra interdum. Maecenas vel tincidunt mi.
|
||||
</p>
|
||||
<p>
|
||||
Praesent consequat dapibus pellentesque. Fusce at enim id mauris laoreet
|
||||
commodo. Nullam ut mauris euismod, rhoncus tellus vel, facilisis diam.
|
||||
Aenean porta faucibus augue, a iaculis massa iaculis in. Praesent vel
|
||||
metus purus. Etiam quis augue eget orci lobortis eleifend ac ut lorem.
|
||||
Aenean non orci quis nisi molestie maximus. Mauris interdum, eros et
|
||||
aliquam aliquam, lectus diam pharetra velit, in condimentum odio eros
|
||||
non quam. Praesent bibendum pretium turpis vitae tristique. Mauris
|
||||
convallis, massa ut fermentum fermentum, libero orci tempus ipsum,
|
||||
malesuada ultrices metus sapien placerat lectus. Ut fringilla arcu nec
|
||||
lorem ultrices mattis. Etiam id tortor feugiat magna gravida gravida.
|
||||
Morbi aliquam, mi ac pellentesque mattis, erat ex venenatis erat, a
|
||||
vestibulum eros turpis quis metus. Pellentesque tempus justo in ligula
|
||||
ultricies porta. Phasellus congue felis sit amet dolor tristique
|
||||
finibus. Nunc eget eros non est ultricies vestibulum.
|
||||
</p>
|
||||
<p>
|
||||
Donec efficitur ligula quis odio tincidunt tristique. Duis urna dolor,
|
||||
hendrerit quis enim at, accumsan auctor turpis. Vivamus ante lorem,
|
||||
maximus vitae suscipit ut, congue eget velit. Maecenas sed ligula erat.
|
||||
Aliquam mollis purus at nisi porta suscipit in ut magna. Vivamus a
|
||||
turpis nec tellus egestas suscipit nec ornare nisi. Donec vestibulum
|
||||
libero quis ex suscipit, sit amet luctus leo gravida.
|
||||
</p>
|
||||
<p>
|
||||
Praesent pharetra dolor elit, sed volutpat lorem rhoncus non. Etiam a
|
||||
neque ut velit dignissim sodales. Vestibulum neque risus, condimentum
|
||||
nec consectetur vitae, ultricies ut sapien. Integer iaculis at urna sit
|
||||
amet malesuada. Integer tincidunt, felis ac vulputate semper, velit leo
|
||||
facilisis lorem, quis aliquet leo dui id lorem. Morbi non quam quis nisl
|
||||
sagittis consequat nec vitae libero. Nunc molestie pretium libero, eu
|
||||
eleifend nibh feugiat sed. Ut in bibendum diam, sit amet vehicula risus.
|
||||
Nam ornare ac nisi ac euismod. Nullam id egestas nulla. Etiam porta
|
||||
commodo ante sit amet pellentesque. Suspendisse eleifend purus in urna
|
||||
euismod auctor non vel nisi. Suspendisse rutrum est nunc, sit amet
|
||||
lacinia lacus dictum eget. Pellentesque habitant morbi tristique
|
||||
senectus et netus et malesuada fames ac turpis egestas. Morbi a blandit
|
||||
diam.
|
||||
</p>
|
||||
<p>
|
||||
Donec eget efficitur sapien. Suspendisse diam lacus, varius eu interdum
|
||||
et, congue ac justo. Proin ipsum odio, suscipit elementum mauris sed,
|
||||
porttitor congue est. Cras dapibus dictum ante, vitae gravida elit
|
||||
venenatis sed. Sed massa sem, posuere ut enim sit amet, vestibulum
|
||||
condimentum nibh. Pellentesque pulvinar sodales lacinia. Proin id
|
||||
pretium sapien, non convallis nulla. In mollis tincidunt sem et
|
||||
porttitor.
|
||||
</p>
|
||||
<p>
|
||||
Integer at sollicitudin sem. Suspendisse sed semper orci. Nulla at nibh
|
||||
nec risus suscipit posuere egestas vitae enim. Nullam mauris justo,
|
||||
mattis vel laoreet non, finibus nec nisl. Cras iaculis ultrices nibh,
|
||||
non commodo eros aliquam non. Sed vitae mollis dui, at maximus metus. Ut
|
||||
vestibulum, enim ut lobortis vulputate, lorem urna congue elit, non
|
||||
dictum odio lorem eget velit. Morbi eleifend id ligula vitae vulputate.
|
||||
Suspendisse ac laoreet justo. Proin eu mattis diam.
|
||||
</p>
|
||||
<p>
|
||||
Nunc in ex quis enim ullamcorper scelerisque eget ac eros. Class aptent
|
||||
taciti sociosqu ad litora torquent per conubia nostra, per inceptos
|
||||
himenaeos. Aliquam turpis dui, egestas a rhoncus non, fermentum in
|
||||
tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices
|
||||
posuere cubilia curae; Aenean non risus arcu. Nam ultricies lacinia
|
||||
volutpat. Class aptent taciti sociosqu ad litora torquent per conubia
|
||||
nostra, per inceptos himenaeos. Lorem ipsum dolor sit amet, consectetur
|
||||
adipiscing elit.
|
||||
</p>
|
||||
<p>
|
||||
Aliquam a felis leo. Proin lorem ipsum, congue eu cursus in, rhoncus ut
|
||||
libero. Vestibulum sit amet consequat nunc. Ut eleifend lobortis lacus,
|
||||
vel molestie metus viverra eget. Nullam suscipit eu magna scelerisque
|
||||
suscipit. Donec dictum in diam nec lacinia. Mauris pellentesque ex ut
|
||||
purus facilisis, eget placerat turpis semper. Sed dapibus lorem ante, et
|
||||
malesuada dui eleifend ac. Sed diam felis, semper ac nulla vel, posuere
|
||||
ultricies ante.
|
||||
</p>
|
||||
<p>
|
||||
Nunc elementum odio sapien, sit amet vulputate lorem varius at. Fusce
|
||||
non sapien vitae lorem aliquam pretium sit amet congue dolor. Nunc quis
|
||||
tortor luctus, pretium ex a, tincidunt urna. Aliquam fermentum massa a
|
||||
erat pharetra varius. Curabitur at auctor dui. Sed posuere pellentesque
|
||||
massa, vel bibendum urna dictum non. Fusce eget rhoncus urna. Maecenas
|
||||
sed lectus tellus. Pellentesque convallis dapibus nisl vitae venenatis.
|
||||
Quisque ornare a dolor ac pharetra. Nam cursus, mi a lacinia accumsan,
|
||||
felis erat fringilla magna, ac mattis nunc ante a orci.
|
||||
</p>
|
||||
<p>
|
||||
Nunc vel tortor euismod, commodo tortor non, aliquam nisi. Maecenas
|
||||
tempus mollis velit non suscipit. Mauris sit amet dolor sed ex fringilla
|
||||
varius. Suspendisse vel cursus risus. Vivamus pharetra massa nec dolor
|
||||
aliquam feugiat. Fusce finibus enim commodo, scelerisque ante eu,
|
||||
laoreet ex. Curabitur placerat magna quis imperdiet lacinia. Etiam
|
||||
lectus mauris, porttitor ac lacinia sed, posuere eget lacus. Mauris
|
||||
vulputate mattis imperdiet. Nunc id aliquet libero, vitae hendrerit
|
||||
purus. Praesent vestibulum urna ac egestas tempor. In molestie, nunc sit
|
||||
amet sagittis dapibus, ligula enim fermentum mi, lacinia molestie eros
|
||||
dui in tortor. Mauris fermentum pulvinar faucibus. Curabitur laoreet
|
||||
eleifend purus, non tincidunt tortor gravida nec. Nam eu lectus congue,
|
||||
commodo libero et, porttitor est. Nullam tincidunt, nisi eu congue
|
||||
congue, magna justo commodo massa, nec efficitur dui lectus non sem.
|
||||
</p>
|
||||
<p>
|
||||
Nullam vehicula, ipsum quis lacinia tristique, elit nulla dignissim
|
||||
augue, at pulvinar metus justo ac magna. Nullam nec nunc ac sapien
|
||||
mollis cursus eu ac enim. Pellentesque a pharetra erat. Ut tempor magna
|
||||
nisi, accumsan blandit lectus volutpat nec. Vivamus vel lorem nec eros
|
||||
blandit dictum eget ac diam. Nulla nec turpis dolor. Morbi eu euismod
|
||||
libero. Nam ut tortor at arcu porta tincidunt. In gravida ligula
|
||||
fringilla ornare imperdiet. Nulla scelerisque ante erat, efficitur
|
||||
dictum metus ullamcorper vel. Nam ac purus metus. Maecenas eget tempus
|
||||
nulla. Ut magna lorem, efficitur ut ex a, semper aliquam magna. Praesent
|
||||
lobortis, velit ac posuere mattis, justo est accumsan turpis, id
|
||||
sagittis felis mi in lacus.
|
||||
</p>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat lectus
|
||||
eu commodo efficitur. Pellentesque in nunc ac massa porttitor eleifend
|
||||
ut efficitur sem. Aenean at magna auctor, posuere augue in, ultrices
|
||||
arcu. Praesent dignissim augue ex, malesuada maximus metus interdum a.
|
||||
Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat lectus
|
||||
eu commodo efficitur. Pellentesque in nunc ac massa porttitor eleifend
|
||||
ut efficitur sem. Aenean at magna auctor, posuere augue in, ultrices
|
||||
arcu. Praesent dignissim augue ex, malesuada maximus metus interdum a.
|
||||
Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat lectus
|
||||
eu commodo efficitur. Pellentesque in nunc ac massa porttitor eleifend
|
||||
ut efficitur sem. Aenean at magna auctor, posuere augue in, ultrices
|
||||
arcu. Praesent dignissim augue ex, malesuada maximus metus interdum a.
|
||||
Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
|
||||
porttitor tortor ac lectus faucibus, eget eleifend elit hendrerit.
|
||||
Integer porttitor nisi in leo congue rutrum. Morbi sed ante posuere,
|
||||
aliquam lorem ac, imperdiet orci. Duis malesuada gravida pharetra.
|
||||
Cras facilisis arcu diam, id dictum lorem imperdiet a. Suspendisse
|
||||
aliquet tempus tortor et ultricies. Aliquam libero velit, posuere
|
||||
tempus ante sed, pellentesque tincidunt lorem. Nullam iaculis, eros a
|
||||
varius aliquet, tortor felis tempor metus, nec cursus felis eros
|
||||
aliquam nulla. Vivamus ut orci sed mauris congue lacinia. Cras eget
|
||||
blandit neque. Pellentesque a massa in turpis ullamcorper volutpat vel
|
||||
at massa. Sed ante est, auctor non diam non, vulputate ultrices metus.
|
||||
Maecenas dictum fermentum quam id aliquam. Donec porta risus vitae
|
||||
pretium posuere. Fusce facilisis eros in lacus tincidunt congue.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Pellentesque habitant morbi tristique senectus et netus et malesuada
|
||||
fames ac turpis egestas. Phasellus dolor ante, iaculis vel nisl vitae,
|
||||
ornare ornare orci. Praesent sit amet lobortis sapien. Suspendisse
|
||||
pharetra posuere libero ut dapibus. Donec condimentum ante urna.
|
||||
Aliquam laoreet tincidunt lacus, sed interdum tortor dapibus
|
||||
elementum. Nam sed faucibus lorem. Suspendisse finibus, velit sed
|
||||
molestie finibus, risus purus mollis ante, sit amet aliquet sapien
|
||||
nulla ut nibh. In eget ligula metus. Duis in purus mattis, blandit
|
||||
magna nec, dictum nunc.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Sed convallis magna id tortor blandit dictum. Suspendisse in porttitor
|
||||
neque. Integer quis metus consequat, rutrum est sit amet, finibus
|
||||
justo. In hac habitasse platea dictumst. Nullam sagittis, risus sed
|
||||
vehicula porta, sapien elit ultrices nibh, vel luctus odio tortor et
|
||||
ante. Sed porta enim in hendrerit tristique. Pellentesque id feugiat
|
||||
libero, sit amet tempor enim. Proin gravida nisl justo, vel ornare
|
||||
dolor bibendum ac. Mauris scelerisque mattis facilisis. Praesent
|
||||
sodales augue mollis orci vulputate aliquet. Mauris molestie luctus
|
||||
neque, sed congue elit congue ut. Cras quis tortor augue. In auctor
|
||||
nulla vel turpis dapibus egestas. Phasellus consequat rhoncus nisi sed
|
||||
dignissim. Quisque varius justo non ex lobortis finibus cursus nec
|
||||
justo. Nulla erat neque, commodo et sem convallis, tristique faucibus
|
||||
odio.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Ut condimentum volutpat sem, id accumsan augue placerat vel. Donec ac
|
||||
efficitur turpis. Suspendisse pretium odio euismod sapien bibendum,
|
||||
sed tempus est condimentum. Etiam nisl magna, consequat at ullamcorper
|
||||
at, sollicitudin eu eros. In mattis ligula arcu. Sed eu consectetur
|
||||
turpis, id molestie ligula. Vestibulum et venenatis enim. Donec
|
||||
condimentum vitae nisi et placerat. Sed fringilla vehicula egestas.
|
||||
Proin consectetur, nibh non ornare scelerisque, diam lorem cursus
|
||||
lectus, ut mattis mauris purus id mi. Curabitur non ligula sit amet
|
||||
augue molestie vulputate. Donec maximus magna at volutpat aliquet.
|
||||
Pellentesque dignissim nulla eget odio eleifend tincidunt. Etiam diam
|
||||
lorem, ornare vel scelerisque vel, iaculis id risus. Donec aliquet
|
||||
aliquam felis, ac vehicula lacus suscipit vitae. Morbi eu ligula elit.
|
||||
</p>
|
||||
</Suspense>
|
||||
<p>This should appear in the first paint.</p>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Praesent pellentesque, libero ut faucibus tempor, purus elit consequat
|
||||
metus, in ornare nulla lectus at erat. Duis quis blandit turpis. Fusce
|
||||
at ligula rutrum metus molestie tempor sit amet eu justo. Maecenas
|
||||
tincidunt nisl nunc. Morbi ac metus tempor, pretium arcu vel, dapibus
|
||||
velit. Nulla convallis ligula at porta mollis. Duis magna ante, mollis
|
||||
eget nibh in, congue tempor dolor. Sed tincidunt sagittis arcu, in
|
||||
ultricies neque tempor non. Suspendisse eget nunc neque. Nulla sit
|
||||
amet odio volutpat, maximus purus id, dictum metus. Integer consequat,
|
||||
orci nec ullamcorper porta, mauris libero vestibulum ipsum, nec tempor
|
||||
tellus enim non nunc. Quisque nisl risus, dapibus sit amet purus nec,
|
||||
aliquam finibus metus. Nullam condimentum urna viverra finibus cursus.
|
||||
Proin et sollicitudin tellus, porta fermentum felis. Maecenas ac
|
||||
turpis sed dui condimentum interdum sed sed erat. Mauris ut dignissim
|
||||
erat.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Proin varius porta dui, id fringilla elit lobortis eget. Integer at
|
||||
metus elementum, efficitur eros id, euismod est. Morbi vestibulum nibh
|
||||
ac leo luctus sagittis. Praesent rhoncus, risus sit amet mattis
|
||||
dictum, diam sapien tempor neque, vel dignissim nulla neque eget ex.
|
||||
Nam sollicitudin metus quis ullamcorper dapibus. Nam tristique euismod
|
||||
efficitur. Pellentesque rhoncus vel sem eget lacinia. Pellentesque
|
||||
volutpat velit ac dignissim luctus. Vivamus euismod tortor at ligula
|
||||
mattis porta. Vestibulum ante ipsum primis in faucibus orci luctus et
|
||||
ultrices posuere cubilia curae;
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Proin blandit vulputate efficitur. Pellentesque sit amet porta odio.
|
||||
Nunc pulvinar varius rhoncus. Mauris fermentum leo a imperdiet
|
||||
pretium. Mauris scelerisque justo vel ante egestas, eget tempus neque
|
||||
malesuada. Sed dictum ex vel justo dignissim, aliquam commodo diam
|
||||
rutrum. Integer dignissim est ullamcorper augue laoreet consectetur id
|
||||
at diam. Vivamus molestie blandit urna, eget pulvinar augue dictum
|
||||
vestibulum. Duis maximus bibendum mauris, ut ultricies elit rhoncus
|
||||
eu. Praesent gravida placerat mauris. Praesent tempor ipsum at nibh
|
||||
rhoncus sagittis. Duis non sem turpis. Quisque et metus leo. Sed eu
|
||||
purus lorem. Pellentesque dictum metus sed leo viverra interdum.
|
||||
Maecenas vel tincidunt mi.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Praesent consequat dapibus pellentesque. Fusce at enim id mauris
|
||||
laoreet commodo. Nullam ut mauris euismod, rhoncus tellus vel,
|
||||
facilisis diam. Aenean porta faucibus augue, a iaculis massa iaculis
|
||||
in. Praesent vel metus purus. Etiam quis augue eget orci lobortis
|
||||
eleifend ac ut lorem. Aenean non orci quis nisi molestie maximus.
|
||||
Mauris interdum, eros et aliquam aliquam, lectus diam pharetra velit,
|
||||
in condimentum odio eros non quam. Praesent bibendum pretium turpis
|
||||
vitae tristique. Mauris convallis, massa ut fermentum fermentum,
|
||||
libero orci tempus ipsum, malesuada ultrices metus sapien placerat
|
||||
lectus. Ut fringilla arcu nec lorem ultrices mattis. Etiam id tortor
|
||||
feugiat magna gravida gravida. Morbi aliquam, mi ac pellentesque
|
||||
mattis, erat ex venenatis erat, a vestibulum eros turpis quis metus.
|
||||
Pellentesque tempus justo in ligula ultricies porta. Phasellus congue
|
||||
felis sit amet dolor tristique finibus. Nunc eget eros non est
|
||||
ultricies vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Donec efficitur ligula quis odio tincidunt tristique. Duis urna dolor,
|
||||
hendrerit quis enim at, accumsan auctor turpis. Vivamus ante lorem,
|
||||
maximus vitae suscipit ut, congue eget velit. Maecenas sed ligula
|
||||
erat. Aliquam mollis purus at nisi porta suscipit in ut magna. Vivamus
|
||||
a turpis nec tellus egestas suscipit nec ornare nisi. Donec vestibulum
|
||||
libero quis ex suscipit, sit amet luctus leo gravida.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Praesent pharetra dolor elit, sed volutpat lorem rhoncus non. Etiam a
|
||||
neque ut velit dignissim sodales. Vestibulum neque risus, condimentum
|
||||
nec consectetur vitae, ultricies ut sapien. Integer iaculis at urna
|
||||
sit amet malesuada. Integer tincidunt, felis ac vulputate semper,
|
||||
velit leo facilisis lorem, quis aliquet leo dui id lorem. Morbi non
|
||||
quam quis nisl sagittis consequat nec vitae libero. Nunc molestie
|
||||
pretium libero, eu eleifend nibh feugiat sed. Ut in bibendum diam, sit
|
||||
amet vehicula risus. Nam ornare ac nisi ac euismod. Nullam id egestas
|
||||
nulla. Etiam porta commodo ante sit amet pellentesque. Suspendisse
|
||||
eleifend purus in urna euismod auctor non vel nisi. Suspendisse rutrum
|
||||
est nunc, sit amet lacinia lacus dictum eget. Pellentesque habitant
|
||||
morbi tristique senectus et netus et malesuada fames ac turpis
|
||||
egestas. Morbi a blandit diam.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Donec eget efficitur sapien. Suspendisse diam lacus, varius eu
|
||||
interdum et, congue ac justo. Proin ipsum odio, suscipit elementum
|
||||
mauris sed, porttitor congue est. Cras dapibus dictum ante, vitae
|
||||
gravida elit venenatis sed. Sed massa sem, posuere ut enim sit amet,
|
||||
vestibulum condimentum nibh. Pellentesque pulvinar sodales lacinia.
|
||||
Proin id pretium sapien, non convallis nulla. In mollis tincidunt sem
|
||||
et porttitor.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Integer at sollicitudin sem. Suspendisse sed semper orci. Nulla at
|
||||
nibh nec risus suscipit posuere egestas vitae enim. Nullam mauris
|
||||
justo, mattis vel laoreet non, finibus nec nisl. Cras iaculis ultrices
|
||||
nibh, non commodo eros aliquam non. Sed vitae mollis dui, at maximus
|
||||
metus. Ut vestibulum, enim ut lobortis vulputate, lorem urna congue
|
||||
elit, non dictum odio lorem eget velit. Morbi eleifend id ligula vitae
|
||||
vulputate. Suspendisse ac laoreet justo. Proin eu mattis diam.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nunc in ex quis enim ullamcorper scelerisque eget ac eros. Class
|
||||
aptent taciti sociosqu ad litora torquent per conubia nostra, per
|
||||
inceptos himenaeos. Aliquam turpis dui, egestas a rhoncus non,
|
||||
fermentum in tellus. Vestibulum ante ipsum primis in faucibus orci
|
||||
luctus et ultrices posuere cubilia curae; Aenean non risus arcu. Nam
|
||||
ultricies lacinia volutpat. Class aptent taciti sociosqu ad litora
|
||||
torquent per conubia nostra, per inceptos himenaeos. Lorem ipsum dolor
|
||||
sit amet, consectetur adipiscing elit.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aliquam a felis leo. Proin lorem ipsum, congue eu cursus in, rhoncus
|
||||
ut libero. Vestibulum sit amet consequat nunc. Ut eleifend lobortis
|
||||
lacus, vel molestie metus viverra eget. Nullam suscipit eu magna
|
||||
scelerisque suscipit. Donec dictum in diam nec lacinia. Mauris
|
||||
pellentesque ex ut purus facilisis, eget placerat turpis semper. Sed
|
||||
dapibus lorem ante, et malesuada dui eleifend ac. Sed diam felis,
|
||||
semper ac nulla vel, posuere ultricies ante.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nunc elementum odio sapien, sit amet vulputate lorem varius at. Fusce
|
||||
non sapien vitae lorem aliquam pretium sit amet congue dolor. Nunc
|
||||
quis tortor luctus, pretium ex a, tincidunt urna. Aliquam fermentum
|
||||
massa a erat pharetra varius. Curabitur at auctor dui. Sed posuere
|
||||
pellentesque massa, vel bibendum urna dictum non. Fusce eget rhoncus
|
||||
urna. Maecenas sed lectus tellus. Pellentesque convallis dapibus nisl
|
||||
vitae venenatis. Quisque ornare a dolor ac pharetra. Nam cursus, mi a
|
||||
lacinia accumsan, felis erat fringilla magna, ac mattis nunc ante a
|
||||
orci.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nunc vel tortor euismod, commodo tortor non, aliquam nisi. Maecenas
|
||||
tempus mollis velit non suscipit. Mauris sit amet dolor sed ex
|
||||
fringilla varius. Suspendisse vel cursus risus. Vivamus pharetra massa
|
||||
nec dolor aliquam feugiat. Fusce finibus enim commodo, scelerisque
|
||||
ante eu, laoreet ex. Curabitur placerat magna quis imperdiet lacinia.
|
||||
Etiam lectus mauris, porttitor ac lacinia sed, posuere eget lacus.
|
||||
Mauris vulputate mattis imperdiet. Nunc id aliquet libero, vitae
|
||||
hendrerit purus. Praesent vestibulum urna ac egestas tempor. In
|
||||
molestie, nunc sit amet sagittis dapibus, ligula enim fermentum mi,
|
||||
lacinia molestie eros dui in tortor. Mauris fermentum pulvinar
|
||||
faucibus. Curabitur laoreet eleifend purus, non tincidunt tortor
|
||||
gravida nec. Nam eu lectus congue, commodo libero et, porttitor est.
|
||||
Nullam tincidunt, nisi eu congue congue, magna justo commodo massa,
|
||||
nec efficitur dui lectus non sem.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Nullam vehicula, ipsum quis lacinia tristique, elit nulla dignissim
|
||||
augue, at pulvinar metus justo ac magna. Nullam nec nunc ac sapien
|
||||
mollis cursus eu ac enim. Pellentesque a pharetra erat. Ut tempor
|
||||
magna nisi, accumsan blandit lectus volutpat nec. Vivamus vel lorem
|
||||
nec eros blandit dictum eget ac diam. Nulla nec turpis dolor. Morbi eu
|
||||
euismod libero. Nam ut tortor at arcu porta tincidunt. In gravida
|
||||
ligula fringilla ornare imperdiet. Nulla scelerisque ante erat,
|
||||
efficitur dictum metus ullamcorper vel. Nam ac purus metus. Maecenas
|
||||
eget tempus nulla. Ut magna lorem, efficitur ut ex a, semper aliquam
|
||||
magna. Praesent lobortis, velit ac posuere mattis, justo est accumsan
|
||||
turpis, id sagittis felis mi in lacus.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat
|
||||
lectus eu commodo efficitur. Pellentesque in nunc ac massa porttitor
|
||||
eleifend ut efficitur sem. Aenean at magna auctor, posuere augue in,
|
||||
ultrices arcu. Praesent dignissim augue ex, malesuada maximus metus
|
||||
interdum a. Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat
|
||||
lectus eu commodo efficitur. Pellentesque in nunc ac massa porttitor
|
||||
eleifend ut efficitur sem. Aenean at magna auctor, posuere augue in,
|
||||
ultrices arcu. Praesent dignissim augue ex, malesuada maximus metus
|
||||
interdum a. Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Aenean est mi, semper nec sem at, malesuada consectetur nunc. Aenean
|
||||
consequat sem quis sem consequat, non aliquam est placerat. Cras
|
||||
malesuada magna neque, et pellentesque nibh consequat at. Sed interdum
|
||||
velit et ex interdum, vel lobortis ante vestibulum. Nam placerat
|
||||
lectus eu commodo efficitur. Pellentesque in nunc ac massa porttitor
|
||||
eleifend ut efficitur sem. Aenean at magna auctor, posuere augue in,
|
||||
ultrices arcu. Praesent dignissim augue ex, malesuada maximus metus
|
||||
interdum a. Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
+95
-60
@@ -7,52 +7,35 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/* eslint-disable react-internal/no-production-logging */
|
||||
|
||||
import type {ReactComponentInfo} from 'shared/ReactTypes';
|
||||
|
||||
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const supportsUserTiming =
|
||||
enableProfilerTimer &&
|
||||
typeof performance !== 'undefined' &&
|
||||
// $FlowFixMe[method-unbinding]
|
||||
typeof performance.measure === 'function';
|
||||
typeof console !== 'undefined' &&
|
||||
typeof console.timeStamp === 'function';
|
||||
|
||||
const COMPONENTS_TRACK = 'Server Components ⚛';
|
||||
|
||||
const componentsTrackMarker = {
|
||||
startTime: 0.001,
|
||||
detail: {
|
||||
devtools: {
|
||||
color: 'primary-light',
|
||||
track: 'Primary',
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function markAllTracksInOrder() {
|
||||
if (supportsUserTiming) {
|
||||
// Ensure we create the Server Component track groups earlier than the Client Scheduler
|
||||
// and Client Components. We can always add the 0 time slot even if it's in the past.
|
||||
// That's still considered for ordering.
|
||||
performance.mark('Server Components Track', componentsTrackMarker);
|
||||
console.timeStamp(
|
||||
'Server Components Track',
|
||||
0.001,
|
||||
0.001,
|
||||
'Primary',
|
||||
COMPONENTS_TRACK,
|
||||
'primary-light',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Reused to avoid thrashing the GC.
|
||||
const reusableComponentDevToolDetails = {
|
||||
color: 'primary',
|
||||
track: '',
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
};
|
||||
const reusableComponentOptions = {
|
||||
start: -0,
|
||||
end: -0,
|
||||
detail: {
|
||||
devtools: reusableComponentDevToolDetails,
|
||||
},
|
||||
};
|
||||
|
||||
const trackNames = [
|
||||
'Primary',
|
||||
'Parallel',
|
||||
@@ -79,7 +62,7 @@ export function logComponentRender(
|
||||
const name = componentInfo.name;
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const selfTime = endTime - startTime;
|
||||
reusableComponentDevToolDetails.color =
|
||||
const color =
|
||||
selfTime < 0.5
|
||||
? isPrimaryEnv
|
||||
? 'primary-light'
|
||||
@@ -93,12 +76,32 @@ export function logComponentRender(
|
||||
? 'primary-dark'
|
||||
: 'secondary-dark'
|
||||
: 'error';
|
||||
reusableComponentDevToolDetails.track = trackNames[trackIdx];
|
||||
reusableComponentOptions.start = startTime < 0 ? 0 : startTime;
|
||||
reusableComponentOptions.end = childrenEndTime;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
performance.measure(entryName, reusableComponentOptions);
|
||||
const debugTask = componentInfo.debugTask;
|
||||
if (__DEV__ && debugTask) {
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
console.timeStamp.bind(
|
||||
console,
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
color,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +115,17 @@ export function logComponentErrored(
|
||||
error: mixed,
|
||||
): void {
|
||||
if (supportsUserTiming) {
|
||||
const properties = [];
|
||||
if (__DEV__) {
|
||||
const env = componentInfo.env;
|
||||
const name = componentInfo.name;
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
if (
|
||||
__DEV__ &&
|
||||
typeof performance !== 'undefined' &&
|
||||
// $FlowFixMe[method-unbinding]
|
||||
typeof performance.measure === 'function'
|
||||
) {
|
||||
const message =
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
@@ -122,26 +134,30 @@ export function logComponentErrored(
|
||||
String(error.message)
|
||||
: // eslint-disable-next-line react-internal/safe-string-coercion
|
||||
String(error);
|
||||
properties.push(['Error', message]);
|
||||
}
|
||||
const env = componentInfo.env;
|
||||
const name = componentInfo.name;
|
||||
const isPrimaryEnv = env === rootEnv;
|
||||
const entryName =
|
||||
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
|
||||
performance.measure(entryName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: childrenEndTime,
|
||||
detail: {
|
||||
devtools: {
|
||||
color: 'error',
|
||||
track: trackNames[trackIdx],
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
tooltipText: entryName + ' Errored',
|
||||
properties,
|
||||
const properties = [['Error', message]];
|
||||
performance.measure(entryName, {
|
||||
start: startTime < 0 ? 0 : startTime,
|
||||
end: childrenEndTime,
|
||||
detail: {
|
||||
devtools: {
|
||||
color: 'error',
|
||||
track: trackNames[trackIdx],
|
||||
trackGroup: COMPONENTS_TRACK,
|
||||
tooltipText: entryName + ' Errored',
|
||||
properties,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
childrenEndTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
'error',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,11 +169,30 @@ export function logDedupedComponentRender(
|
||||
): void {
|
||||
if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
|
||||
const name = componentInfo.name;
|
||||
reusableComponentDevToolDetails.color = 'tertiary-light';
|
||||
reusableComponentDevToolDetails.track = trackNames[trackIdx];
|
||||
reusableComponentOptions.start = startTime < 0 ? 0 : startTime;
|
||||
reusableComponentOptions.end = endTime;
|
||||
const entryName = name + ' [deduped]';
|
||||
performance.measure(entryName, reusableComponentOptions);
|
||||
const debugTask = componentInfo.debugTask;
|
||||
if (__DEV__ && debugTask) {
|
||||
debugTask.run(
|
||||
// $FlowFixMe[method-unbinding]
|
||||
console.timeStamp.bind(
|
||||
console,
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
endTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
'tertiary-light',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.timeStamp(
|
||||
entryName,
|
||||
startTime < 0 ? 0 : startTime,
|
||||
endTime,
|
||||
trackNames[trackIdx],
|
||||
COMPONENTS_TRACK,
|
||||
'tertiary-light',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {Element as ElementType} from 'react-devtools-shared/src/frontend/ty
|
||||
|
||||
import styles from './Element.css';
|
||||
import Icon from '../Icon';
|
||||
import {useChangeOwnerAction} from './OwnersListContext';
|
||||
|
||||
type Props = {
|
||||
data: ItemData,
|
||||
@@ -66,9 +67,10 @@ export default function Element({data, index, style}: Props): React.Node {
|
||||
warningCount: number,
|
||||
}>(errorsAndWarningsSubscription);
|
||||
|
||||
const changeOwnerAction = useChangeOwnerAction();
|
||||
const handleDoubleClick = () => {
|
||||
if (id !== null) {
|
||||
dispatch({type: 'SELECT_OWNER', payload: id});
|
||||
changeOwnerAction(id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+39
-4
@@ -13,7 +13,7 @@ import * as React from 'react';
|
||||
import {createContext, useCallback, useContext, useEffect} from 'react';
|
||||
import {createResource} from '../../cache';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import {TreeStateContext} from './TreeContext';
|
||||
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
|
||||
import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils';
|
||||
|
||||
import type {OwnersList} from 'react-devtools-shared/src/backend/types';
|
||||
@@ -70,6 +70,43 @@ type Props = {
|
||||
children: React$Node,
|
||||
};
|
||||
|
||||
function useChangeOwnerAction(): (nextOwnerID: number) => void {
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
const treeAction = useContext(TreeDispatcherContext);
|
||||
|
||||
return useCallback(
|
||||
function changeOwnerAction(nextOwnerID: number) {
|
||||
treeAction({type: 'SELECT_OWNER', payload: nextOwnerID});
|
||||
|
||||
const element = store.getElementByID(nextOwnerID);
|
||||
if (element !== null) {
|
||||
if (!inProgressRequests.has(element)) {
|
||||
let resolveFn:
|
||||
| ResolveFn
|
||||
| ((
|
||||
result:
|
||||
| Promise<Array<SerializedElement>>
|
||||
| Array<SerializedElement>,
|
||||
) => void) = ((null: any): ResolveFn);
|
||||
const promise = new Promise(resolve => {
|
||||
resolveFn = resolve;
|
||||
});
|
||||
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
inProgressRequests.set(element, {promise, resolveFn});
|
||||
}
|
||||
|
||||
const rendererID = store.getRendererIDForElement(nextOwnerID);
|
||||
if (rendererID !== null) {
|
||||
bridge.send('getOwnersList', {id: nextOwnerID, rendererID});
|
||||
}
|
||||
}
|
||||
},
|
||||
[bridge, store],
|
||||
);
|
||||
}
|
||||
|
||||
function OwnersListContextController({children}: Props): React.Node {
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
@@ -95,8 +132,6 @@ function OwnersListContextController({children}: Props): React.Node {
|
||||
if (element !== null) {
|
||||
const request = inProgressRequests.get(element);
|
||||
if (request != null) {
|
||||
inProgressRequests.delete(element);
|
||||
|
||||
request.resolveFn(
|
||||
ownersList.owners === null
|
||||
? null
|
||||
@@ -129,4 +164,4 @@ function OwnersListContextController({children}: Props): React.Node {
|
||||
);
|
||||
}
|
||||
|
||||
export {OwnersListContext, OwnersListContextController};
|
||||
export {OwnersListContext, OwnersListContextController, useChangeOwnerAction};
|
||||
|
||||
+3
-2
@@ -20,7 +20,7 @@ import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Toggle from '../Toggle';
|
||||
import ElementBadges from './ElementBadges';
|
||||
import {OwnersListContext} from './OwnersListContext';
|
||||
import {OwnersListContext, useChangeOwnerAction} from './OwnersListContext';
|
||||
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
|
||||
import {useIsOverflowing} from '../hooks';
|
||||
import {StoreContext} from '../context';
|
||||
@@ -81,6 +81,7 @@ export default function OwnerStack(): React.Node {
|
||||
const read = useContext(OwnersListContext);
|
||||
const {ownerID} = useContext(TreeStateContext);
|
||||
const treeDispatch = useContext(TreeDispatcherContext);
|
||||
const changeOwnerAction = useChangeOwnerAction();
|
||||
|
||||
const [state, dispatch] = useReducer<State, State, Action>(dialogReducer, {
|
||||
ownerID: null,
|
||||
@@ -116,7 +117,7 @@ export default function OwnerStack(): React.Node {
|
||||
type: 'UPDATE_SELECTED_INDEX',
|
||||
selectedIndex: index >= 0 ? index : 0,
|
||||
});
|
||||
treeDispatch({type: 'SELECT_OWNER', payload: owner.id});
|
||||
changeOwnerAction(owner.id);
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'UPDATE_SELECTED_INDEX',
|
||||
|
||||
@@ -38,6 +38,7 @@ import ButtonIcon from '../ButtonIcon';
|
||||
import Button from '../Button';
|
||||
import {logEvent} from 'react-devtools-shared/src/Logger';
|
||||
import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/frontend/hooks/useExtensionComponentsPanelVisibility';
|
||||
import {useChangeOwnerAction} from './OwnersListContext';
|
||||
|
||||
// Never indent more than this number of pixels (even if we have the room).
|
||||
const DEFAULT_INDENTATION_SIZE = 12;
|
||||
@@ -217,13 +218,14 @@ export default function Tree(): React.Node {
|
||||
const handleBlur = useCallback(() => setTreeFocused(false), []);
|
||||
const handleFocus = useCallback(() => setTreeFocused(true), []);
|
||||
|
||||
const changeOwnerAction = useChangeOwnerAction();
|
||||
const handleKeyPress = useCallback(
|
||||
(event: $FlowFixMe) => {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
case ' ':
|
||||
if (inspectedElementID !== null) {
|
||||
dispatch({type: 'SELECT_OWNER', payload: inspectedElementID});
|
||||
changeOwnerAction(inspectedElementID);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
+12
-8
@@ -148,6 +148,7 @@ const TreeStateContext: ReactContext<StateContext> =
|
||||
createContext<StateContext>(((null: any): StateContext));
|
||||
TreeStateContext.displayName = 'TreeStateContext';
|
||||
|
||||
// TODO: `dispatch` is an Action and should be named accordingly.
|
||||
const TreeDispatcherContext: ReactContext<DispatcherContext> =
|
||||
createContext<DispatcherContext>(((null: any): DispatcherContext));
|
||||
TreeDispatcherContext.displayName = 'TreeDispatcherContext';
|
||||
@@ -891,19 +892,22 @@ function TreeContextController({
|
||||
? store.getIndexOfElementID(store.lastSelectedHostInstanceElementId)
|
||||
: null,
|
||||
});
|
||||
const dispatchWrapper = useMemo(
|
||||
() => (action: Action) => startTransition(() => dispatch(action)),
|
||||
const transitionDispatch = useMemo(
|
||||
() => (action: Action) =>
|
||||
startTransition(() => {
|
||||
dispatch(action);
|
||||
}),
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
// Listen for host element selections.
|
||||
useEffect(() => {
|
||||
const handler = (id: Element['id']) =>
|
||||
dispatchWrapper({type: 'SELECT_ELEMENT_BY_ID', payload: id});
|
||||
transitionDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: id});
|
||||
|
||||
store.addListener('hostInstanceSelected', handler);
|
||||
return () => store.removeListener('hostInstanceSelected', handler);
|
||||
}, [store, dispatchWrapper]);
|
||||
}, [store, transitionDispatch]);
|
||||
|
||||
// If a newly-selected search result or inspection selection is inside of a collapsed subtree, auto expand it.
|
||||
// This needs to be a layout effect to avoid temporarily flashing an incorrect selection.
|
||||
@@ -927,7 +931,7 @@ function TreeContextController({
|
||||
Array<number>,
|
||||
Map<number, number>,
|
||||
]) => {
|
||||
dispatchWrapper({
|
||||
transitionDispatch({
|
||||
type: 'HANDLE_STORE_MUTATION',
|
||||
payload: [addedElementIDs, removedElementIDs],
|
||||
});
|
||||
@@ -938,7 +942,7 @@ function TreeContextController({
|
||||
// At the moment, we can treat this as a mutation.
|
||||
// We don't know which Elements were newly added/removed, but that should be okay in this case.
|
||||
// It would only impact the search state, which is unlikely to exist yet at this point.
|
||||
dispatchWrapper({
|
||||
transitionDispatch({
|
||||
type: 'HANDLE_STORE_MUTATION',
|
||||
payload: [[], new Map()],
|
||||
});
|
||||
@@ -946,11 +950,11 @@ function TreeContextController({
|
||||
|
||||
store.addListener('mutated', handleStoreMutated);
|
||||
return () => store.removeListener('mutated', handleStoreMutated);
|
||||
}, [dispatchWrapper, initialRevision, store]);
|
||||
}, [dispatch, initialRevision, store]);
|
||||
|
||||
return (
|
||||
<TreeStateContext.Provider value={state}>
|
||||
<TreeDispatcherContext.Provider value={dispatchWrapper}>
|
||||
<TreeDispatcherContext.Provider value={transitionDispatch}>
|
||||
{children}
|
||||
</TreeDispatcherContext.Provider>
|
||||
</TreeStateContext.Provider>
|
||||
|
||||
@@ -14,13 +14,6 @@ import {
|
||||
completeSegment,
|
||||
} from './fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime';
|
||||
|
||||
if (!window.$RC) {
|
||||
// TODO: Eventually remove, we currently need to set these globals for
|
||||
// compatibility with ReactDOMFizzInstructionSet
|
||||
window.$RC = completeBoundary;
|
||||
window.$RM = new Map();
|
||||
}
|
||||
|
||||
if (document.body != null) {
|
||||
if (document.readyState === 'loading') {
|
||||
installFizzInstrObserver(document.body);
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from 'react-reconciler/src/ReactEventPriorities';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import {HostText} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {traverseFragmentInstance} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
|
||||
// Modules provided by RN:
|
||||
import {
|
||||
@@ -622,30 +623,91 @@ export function waitForCommitToBeReady(): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export type FragmentInstanceType = null;
|
||||
export type FragmentInstanceType = {
|
||||
_fragmentFiber: Fiber,
|
||||
_observers: null | Set<IntersectionObserver>,
|
||||
observeUsing: (observer: IntersectionObserver) => void,
|
||||
unobserveUsing: (observer: IntersectionObserver) => void,
|
||||
};
|
||||
|
||||
function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) {
|
||||
this._fragmentFiber = fragmentFiber;
|
||||
this._observers = null;
|
||||
}
|
||||
|
||||
// $FlowFixMe[prop-missing]
|
||||
FragmentInstance.prototype.observeUsing = function (
|
||||
this: FragmentInstanceType,
|
||||
observer: IntersectionObserver,
|
||||
): void {
|
||||
if (this._observers === null) {
|
||||
this._observers = new Set();
|
||||
}
|
||||
this._observers.add(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
|
||||
};
|
||||
function observeChild(instance: Instance, observer: IntersectionObserver) {
|
||||
const publicInstance = getPublicInstance(instance);
|
||||
if (publicInstance == null) {
|
||||
throw new Error('Expected to find a host node. This is a bug in React.');
|
||||
}
|
||||
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
|
||||
observer.observe(publicInstance);
|
||||
return false;
|
||||
}
|
||||
// $FlowFixMe[prop-missing]
|
||||
FragmentInstance.prototype.unobserveUsing = function (
|
||||
this: FragmentInstanceType,
|
||||
observer: IntersectionObserver,
|
||||
): void {
|
||||
if (this._observers === null || !this._observers.has(observer)) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'You are calling unobserveUsing() with an observer that is not being observed with this fragment ' +
|
||||
'instance. First attach the observer with observeUsing()',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._observers.delete(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
|
||||
}
|
||||
};
|
||||
function unobserveChild(instance: Instance, observer: IntersectionObserver) {
|
||||
const publicInstance = getPublicInstance(instance);
|
||||
if (publicInstance == null) {
|
||||
throw new Error('Expected to find a host node. This is a bug in React.');
|
||||
}
|
||||
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
|
||||
observer.unobserve(publicInstance);
|
||||
return false;
|
||||
}
|
||||
|
||||
export function createFragmentInstance(
|
||||
fragmentFiber: Fiber,
|
||||
): FragmentInstanceType {
|
||||
return null;
|
||||
return new (FragmentInstance: any)(fragmentFiber);
|
||||
}
|
||||
|
||||
export function updateFragmentInstanceFiber(
|
||||
fragmentFiber: Fiber,
|
||||
instance: FragmentInstanceType,
|
||||
): void {
|
||||
// Noop
|
||||
instance._fragmentFiber = fragmentFiber;
|
||||
}
|
||||
|
||||
export function commitNewChildToFragmentInstance(
|
||||
child: PublicInstance,
|
||||
child: Instance,
|
||||
fragmentInstance: FragmentInstanceType,
|
||||
): void {
|
||||
// Noop
|
||||
if (fragmentInstance._observers !== null) {
|
||||
fragmentInstance._observers.forEach(observer => {
|
||||
observeChild(child, observer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteChildFromFragmentInstance(
|
||||
child: PublicInstance,
|
||||
child: Instance,
|
||||
fragmentInstance: FragmentInstanceType,
|
||||
): void {
|
||||
// Noop
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactFabric;
|
||||
let createReactNativeComponentClass;
|
||||
let act;
|
||||
let View;
|
||||
let Text;
|
||||
|
||||
describe('Fabric FragmentRefs', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
require('react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager');
|
||||
|
||||
React = require('react');
|
||||
ReactFabric = require('react-native-renderer/fabric');
|
||||
createReactNativeComponentClass =
|
||||
require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
|
||||
.ReactNativeViewConfigRegistry.register;
|
||||
({act} = require('internal-test-utils'));
|
||||
View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {nativeID: true},
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {nativeID: true},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
});
|
||||
|
||||
// @gate enableFragmentRefs
|
||||
it('attaches a ref to Fragment', async () => {
|
||||
const fragmentRef = React.createRef();
|
||||
|
||||
await act(() =>
|
||||
ReactFabric.render(
|
||||
<View>
|
||||
<React.Fragment ref={fragmentRef}>
|
||||
<View>
|
||||
<Text>Hi</Text>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
</View>,
|
||||
11,
|
||||
null,
|
||||
true,
|
||||
),
|
||||
);
|
||||
|
||||
expect(fragmentRef.current).not.toBe(null);
|
||||
});
|
||||
|
||||
// @gate enableFragmentRefs
|
||||
it('accepts a ref callback', async () => {
|
||||
let fragmentRef;
|
||||
|
||||
await act(() => {
|
||||
ReactFabric.render(
|
||||
<React.Fragment ref={ref => (fragmentRef = ref)}>
|
||||
<View nativeID="child">
|
||||
<Text>Hi</Text>
|
||||
</View>
|
||||
</React.Fragment>,
|
||||
11,
|
||||
null,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
expect(fragmentRef && fragmentRef._fragmentFiber).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -180,7 +180,7 @@ const classComponentUpdater = {
|
||||
|
||||
const root = enqueueUpdate(fiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'this.setState()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitions(root, fiber, lane);
|
||||
}
|
||||
@@ -206,7 +206,7 @@ const classComponentUpdater = {
|
||||
|
||||
const root = enqueueUpdate(fiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'this.replaceState()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitions(root, fiber, lane);
|
||||
}
|
||||
@@ -232,7 +232,7 @@ const classComponentUpdater = {
|
||||
|
||||
const root = enqueueUpdate(fiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'this.forceUpdate()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitions(root, fiber, lane);
|
||||
}
|
||||
|
||||
+7
-4
@@ -1847,6 +1847,8 @@ function updateStoreInstance<T>(
|
||||
// snapsho and getSnapshot values to bail out. We need to check one more time.
|
||||
if (checkIfSnapshotChanged(inst)) {
|
||||
// Force a re-render.
|
||||
// We intentionally don't log update times and stacks here because this
|
||||
// was not an external trigger but rather an internal one.
|
||||
forceStoreRerender(fiber);
|
||||
}
|
||||
}
|
||||
@@ -1861,6 +1863,7 @@ function subscribeToStore<T>(
|
||||
// read from the store.
|
||||
if (checkIfSnapshotChanged(inst)) {
|
||||
// Force a re-render.
|
||||
startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()');
|
||||
forceStoreRerender(fiber);
|
||||
}
|
||||
};
|
||||
@@ -3503,7 +3506,7 @@ function refreshCache<T>(fiber: Fiber, seedKey: ?() => T, seedValue: T): void {
|
||||
const refreshUpdate = createLegacyQueueUpdate(lane);
|
||||
const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'refresh()');
|
||||
scheduleUpdateOnFiber(root, provider, lane);
|
||||
entangleLegacyQueueTransitions(root, provider, lane);
|
||||
}
|
||||
@@ -3572,7 +3575,7 @@ function dispatchReducerAction<S, A>(
|
||||
} else {
|
||||
const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'dispatch()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
entangleTransitionUpdate(root, queue, lane);
|
||||
}
|
||||
@@ -3606,7 +3609,7 @@ function dispatchSetState<S, A>(
|
||||
lane,
|
||||
);
|
||||
if (didScheduleUpdate) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'setState()');
|
||||
}
|
||||
markUpdateInDevTools(fiber, lane, action);
|
||||
}
|
||||
@@ -3768,7 +3771,7 @@ function dispatchOptimisticSetState<S, A>(
|
||||
// will never be attempted before the optimistic update. This currently
|
||||
// holds because the optimistic update is always synchronous. If we ever
|
||||
// change that, we'll need to account for this.
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'setOptimistic()');
|
||||
scheduleUpdateOnFiber(root, fiber, lane);
|
||||
// Optimistic updates are always synchronous, so we don't need to call
|
||||
// entangleTransitionUpdate here.
|
||||
|
||||
+539
-248
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -443,7 +443,7 @@ function updateContainerImpl(
|
||||
|
||||
const root = enqueueUpdate(rootFiber, update, lane);
|
||||
if (root !== null) {
|
||||
startUpdateTimerByLane(lane);
|
||||
startUpdateTimerByLane(lane, 'root.render()');
|
||||
scheduleUpdateOnFiber(root, rootFiber, lane);
|
||||
entangleTransitions(root, rootFiber, lane);
|
||||
}
|
||||
|
||||
@@ -345,9 +345,9 @@ export function doesFiberContain(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function traverseFragmentInstance<A, B, C>(
|
||||
export function traverseFragmentInstance<I, A, B, C>(
|
||||
fragmentFiber: Fiber,
|
||||
fn: (Instance, A, B, C) => boolean,
|
||||
fn: (I, A, B, C) => boolean,
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
@@ -355,9 +355,9 @@ export function traverseFragmentInstance<A, B, C>(
|
||||
traverseFragmentInstanceChildren(fragmentFiber.child, fn, a, b, c);
|
||||
}
|
||||
|
||||
function traverseFragmentInstanceChildren<A, B, C>(
|
||||
function traverseFragmentInstanceChildren<I, A, B, C>(
|
||||
child: Fiber | null,
|
||||
fn: (Instance, A, B, C) => boolean,
|
||||
fn: (I, A, B, C) => boolean,
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
|
||||
@@ -264,6 +264,7 @@ import {
|
||||
import {
|
||||
blockingClampTime,
|
||||
blockingUpdateTime,
|
||||
blockingUpdateTask,
|
||||
blockingEventTime,
|
||||
blockingEventType,
|
||||
blockingEventIsRepeat,
|
||||
@@ -272,6 +273,7 @@ import {
|
||||
transitionClampTime,
|
||||
transitionStartTime,
|
||||
transitionUpdateTime,
|
||||
transitionUpdateTask,
|
||||
transitionEventTime,
|
||||
transitionEventType,
|
||||
transitionEventIsRepeat,
|
||||
@@ -1914,6 +1916,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
blockingSpawnedUpdate,
|
||||
renderStartTime,
|
||||
lanes,
|
||||
blockingUpdateTask,
|
||||
);
|
||||
clearBlockingTimers();
|
||||
}
|
||||
@@ -1950,6 +1953,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
transitionEventType,
|
||||
transitionEventIsRepeat,
|
||||
renderStartTime,
|
||||
transitionUpdateTask,
|
||||
);
|
||||
clearTransitionTimers();
|
||||
}
|
||||
|
||||
+15
-1
@@ -41,6 +41,13 @@ import * as Scheduler from 'scheduler';
|
||||
|
||||
const {unstable_now: now} = Scheduler;
|
||||
|
||||
const createTask =
|
||||
// eslint-disable-next-line react-internal/no-production-logging
|
||||
__DEV__ && console.createTask
|
||||
? // eslint-disable-next-line react-internal/no-production-logging
|
||||
console.createTask
|
||||
: (name: string) => null;
|
||||
|
||||
export let renderStartTime: number = -0;
|
||||
export let commitStartTime: number = -0;
|
||||
export let commitEndTime: number = -0;
|
||||
@@ -54,6 +61,7 @@ export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
|
||||
|
||||
export let blockingClampTime: number = -0;
|
||||
export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
|
||||
export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
|
||||
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
|
||||
export let blockingEventType: null | string = null; // Event type of the first setState.
|
||||
export let blockingEventIsRepeat: boolean = false;
|
||||
@@ -63,6 +71,7 @@ export let blockingSuspendedTime: number = -1.1;
|
||||
export let transitionClampTime: number = -0;
|
||||
export let transitionStartTime: number = -1.1; // First startTransition call before setState.
|
||||
export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
|
||||
export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace.
|
||||
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
|
||||
export let transitionEventType: null | string = null; // Event type of the first transition.
|
||||
export let transitionEventIsRepeat: boolean = false;
|
||||
@@ -79,13 +88,14 @@ export function startYieldTimer(reason: SuspendedReason) {
|
||||
yieldReason = reason;
|
||||
}
|
||||
|
||||
export function startUpdateTimerByLane(lane: Lane): void {
|
||||
export function startUpdateTimerByLane(lane: Lane, method: string): void {
|
||||
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
|
||||
return;
|
||||
}
|
||||
if (isSyncLane(lane) || isBlockingLane(lane)) {
|
||||
if (blockingUpdateTime < 0) {
|
||||
blockingUpdateTime = now();
|
||||
blockingUpdateTask = createTask(method);
|
||||
if (isAlreadyRendering()) {
|
||||
blockingSpawnedUpdate = true;
|
||||
}
|
||||
@@ -108,6 +118,7 @@ export function startUpdateTimerByLane(lane: Lane): void {
|
||||
} else if (isTransitionLane(lane)) {
|
||||
if (transitionUpdateTime < 0) {
|
||||
transitionUpdateTime = now();
|
||||
transitionUpdateTask = createTask(method);
|
||||
if (transitionStartTime < 0) {
|
||||
const newEventTime = resolveEventTimeStamp();
|
||||
const newEventType = resolveEventType();
|
||||
@@ -155,6 +166,7 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
|
||||
|
||||
export function clearBlockingTimers(): void {
|
||||
blockingUpdateTime = -1.1;
|
||||
blockingUpdateTask = null;
|
||||
blockingSuspendedTime = -1.1;
|
||||
blockingEventIsRepeat = true;
|
||||
blockingSpawnedUpdate = false;
|
||||
@@ -194,6 +206,7 @@ export function startActionStateUpdate(): void {
|
||||
}
|
||||
if (transitionUpdateTime < 0) {
|
||||
transitionUpdateTime = ACTION_STATE_MARKER;
|
||||
transitionUpdateTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +217,7 @@ export function clearAsyncTransitionTimer(): void {
|
||||
export function clearTransitionTimers(): void {
|
||||
transitionStartTime = -1.1;
|
||||
transitionUpdateTime = -1.1;
|
||||
transitionUpdateTask = null;
|
||||
transitionSuspendedTime = -1.1;
|
||||
transitionEventIsRepeat = true;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,61 @@ describe('ReactFlightDOMEdge', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function dripStream(input) {
|
||||
const reader = input.getReader();
|
||||
let nextDrop = 0;
|
||||
let controller = null;
|
||||
let streamDone = false;
|
||||
const buffer = [];
|
||||
function flush() {
|
||||
if (controller === null || nextDrop === 0) {
|
||||
return;
|
||||
}
|
||||
while (buffer.length > 0 && nextDrop > 0) {
|
||||
const nextChunk = buffer[0];
|
||||
if (nextChunk.byteLength <= nextDrop) {
|
||||
nextDrop -= nextChunk.byteLength;
|
||||
controller.enqueue(nextChunk);
|
||||
buffer.shift();
|
||||
if (streamDone && buffer.length === 0) {
|
||||
controller.done();
|
||||
}
|
||||
} else {
|
||||
controller.enqueue(nextChunk.subarray(0, nextDrop));
|
||||
buffer[0] = nextChunk.subarray(nextDrop);
|
||||
nextDrop = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const output = new ReadableStream({
|
||||
start(c) {
|
||||
controller = c;
|
||||
async function pump() {
|
||||
for (;;) {
|
||||
const {value, done} = await reader.read();
|
||||
if (done) {
|
||||
streamDone = true;
|
||||
break;
|
||||
}
|
||||
buffer.push(value);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
pump();
|
||||
},
|
||||
pull() {},
|
||||
cancel(reason) {
|
||||
reader.cancel(reason);
|
||||
},
|
||||
});
|
||||
function drip(n) {
|
||||
nextDrop += n;
|
||||
flush();
|
||||
}
|
||||
|
||||
return [output, drip];
|
||||
}
|
||||
|
||||
async function readResult(stream) {
|
||||
const reader = stream.getReader();
|
||||
let result = '';
|
||||
@@ -576,6 +631,67 @@ describe('ReactFlightDOMEdge', () => {
|
||||
expect(serializedContent.length).toBeLessThan(150 + expectedDebugInfoSize);
|
||||
});
|
||||
|
||||
it('should break up large sync components by outlining into streamable elements', async () => {
|
||||
const paragraphs = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const text =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris' +
|
||||
'porttitor tortor ac lectus faucibus, eget eleifend elit hendrerit.' +
|
||||
'Integer porttitor nisi in leo congue rutrum. Morbi sed ante posuere,' +
|
||||
'aliquam lorem ac, imperdiet orci. Duis malesuada gravida pharetra. Cras' +
|
||||
'facilisis arcu diam, id dictum lorem imperdiet a. Suspendisse aliquet' +
|
||||
'tempus tortor et ultricies. Aliquam libero velit, posuere tempus ante' +
|
||||
'sed, pellentesque tincidunt lorem. Nullam iaculis, eros a varius' +
|
||||
'aliquet, tortor felis tempor metus, nec cursus felis eros aliquam nulla.' +
|
||||
'Vivamus ut orci sed mauris congue lacinia. Cras eget blandit neque.' +
|
||||
'Pellentesque a massa in turpis ullamcorper volutpat vel at massa. Sed' +
|
||||
'ante est, auctor non diam non, vulputate ultrices metus. Maecenas dictum' +
|
||||
'fermentum quam id aliquam. Donec porta risus vitae pretium posuere.' +
|
||||
'Fusce facilisis eros in lacus tincidunt congue.' +
|
||||
i; /* trick dedupe */
|
||||
paragraphs.push(<p key={i}>{text}</p>);
|
||||
}
|
||||
|
||||
const stream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(paragraphs),
|
||||
);
|
||||
|
||||
const [stream2, drip] = dripStream(stream);
|
||||
|
||||
// Allow some of the content through.
|
||||
drip(5000);
|
||||
|
||||
const result = await ReactServerDOMClient.createFromReadableStream(
|
||||
stream2,
|
||||
{
|
||||
serverConsumerManifest: {
|
||||
moduleMap: null,
|
||||
moduleLoading: null,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// We should have resolved enough to be able to get the array even though some
|
||||
// of the items inside are still lazy.
|
||||
expect(result.length).toBe(20);
|
||||
|
||||
// Unblock the rest
|
||||
drip(Infinity);
|
||||
|
||||
// Use the SSR render to resolve any lazy elements
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(result),
|
||||
);
|
||||
const html = await readResult(ssrStream);
|
||||
|
||||
const ssrStream2 = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(paragraphs),
|
||||
);
|
||||
const html2 = await readResult(ssrStream2);
|
||||
|
||||
expect(html).toBe(html2);
|
||||
});
|
||||
|
||||
it('should be able to serialize any kind of typed array', async () => {
|
||||
const buffer = new Uint8Array([
|
||||
123, 4, 10, 5, 100, 255, 244, 45, 56, 67, 43, 124, 67, 89, 100, 20,
|
||||
|
||||
+22
-4
@@ -404,7 +404,10 @@ function isEligibleForOutlining(
|
||||
request: Request,
|
||||
boundary: SuspenseBoundary,
|
||||
): boolean {
|
||||
return boundary.byteSize > request.progressiveChunkSize;
|
||||
// For very small boundaries, don't bother producing a fallback for outlining.
|
||||
// The larger this limit is, the more we can save on preparing fallbacks in case we end up
|
||||
// outlining.
|
||||
return boundary.byteSize > 500;
|
||||
}
|
||||
|
||||
function defaultErrorHandler(error: mixed) {
|
||||
@@ -4902,6 +4905,10 @@ function flushSubtree(
|
||||
}
|
||||
}
|
||||
|
||||
// Running count for how much bytes of boundaries have flushed inlined into the currently
|
||||
// flushing root or completed boundary.
|
||||
let flushedByteSize = 0;
|
||||
|
||||
function flushSegment(
|
||||
request: Request,
|
||||
destination: Destination,
|
||||
@@ -4971,9 +4978,14 @@ function flushSegment(
|
||||
flushSubtree(request, destination, segment, hoistableState);
|
||||
|
||||
return writeEndPendingSuspenseBoundary(destination, request.renderState);
|
||||
} else if (isEligibleForOutlining(request, boundary)) {
|
||||
// This boundary is large and will be emitted separately so that we can progressively show
|
||||
// other content. We add it to the queue during the flush because we have to ensure that
|
||||
} else if (
|
||||
isEligibleForOutlining(request, boundary) &&
|
||||
flushedByteSize + boundary.byteSize > request.progressiveChunkSize
|
||||
) {
|
||||
// Inlining this boundary would make the current sequence being written too large
|
||||
// and block the parent for too long. Instead, it will be emitted separately so that we
|
||||
// can progressively show other content.
|
||||
// We add it to the queue during the flush because we have to ensure that
|
||||
// the parent flushes first so that there's something to inject it into.
|
||||
// We also have to make sure that it's emitted into the queue in a deterministic slot.
|
||||
// I.e. we can't insert it here when it completes.
|
||||
@@ -4999,6 +5011,8 @@ function flushSegment(
|
||||
|
||||
return writeEndPendingSuspenseBoundary(destination, request.renderState);
|
||||
} else {
|
||||
// We're inlining this boundary so its bytes get counted to the current running count.
|
||||
flushedByteSize += boundary.byteSize;
|
||||
if (hoistableState) {
|
||||
hoistHoistables(hoistableState, boundary.contentState);
|
||||
}
|
||||
@@ -5071,6 +5085,7 @@ function flushCompletedBoundary(
|
||||
destination: Destination,
|
||||
boundary: SuspenseBoundary,
|
||||
): boolean {
|
||||
flushedByteSize = boundary.byteSize; // Start counting bytes
|
||||
const completedSegments = boundary.completedSegments;
|
||||
let i = 0;
|
||||
for (; i < completedSegments.length; i++) {
|
||||
@@ -5098,6 +5113,7 @@ function flushPartialBoundary(
|
||||
destination: Destination,
|
||||
boundary: SuspenseBoundary,
|
||||
): boolean {
|
||||
flushedByteSize = boundary.byteSize; // Start counting bytes
|
||||
const completedSegments = boundary.completedSegments;
|
||||
let i = 0;
|
||||
for (; i < completedSegments.length; i++) {
|
||||
@@ -5191,6 +5207,8 @@ function flushCompletedQueues(
|
||||
return;
|
||||
}
|
||||
|
||||
flushedByteSize = request.byteSize; // Start counting bytes
|
||||
// TODO: Count the size of the preamble chunks too.
|
||||
flushPreamble(
|
||||
request,
|
||||
destination,
|
||||
|
||||
+46
-3
@@ -1600,6 +1600,29 @@ function renderClientElement(
|
||||
// The chunk ID we're currently rendering that we can assign debug data to.
|
||||
let debugID: null | number = null;
|
||||
|
||||
// Approximate string length of the currently serializing row.
|
||||
// Used to power outlining heuristics.
|
||||
let serializedSize = 0;
|
||||
const MAX_ROW_SIZE = 3200;
|
||||
|
||||
function deferTask(request: Request, task: Task): ReactJSONValue {
|
||||
// Like outlineTask but instead the item is scheduled to be serialized
|
||||
// after its parent in the stream.
|
||||
const newTask = createTask(
|
||||
request,
|
||||
task.model, // the currently rendering element
|
||||
task.keyPath, // unlike outlineModel this one carries along context
|
||||
task.implicitSlot,
|
||||
request.abortableTasks,
|
||||
__DEV__ ? task.debugOwner : null,
|
||||
__DEV__ ? task.debugStack : null,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
);
|
||||
|
||||
pingTask(request, newTask);
|
||||
return serializeLazyID(newTask.id);
|
||||
}
|
||||
|
||||
function outlineTask(request: Request, task: Task): ReactJSONValue {
|
||||
const newTask = createTask(
|
||||
request,
|
||||
@@ -2393,6 +2416,8 @@ function renderModelDestructive(
|
||||
// Set the currently rendering model
|
||||
task.model = value;
|
||||
|
||||
serializedSize += parentPropertyName.length;
|
||||
|
||||
// Special Symbol, that's very common.
|
||||
if (value === REACT_ELEMENT_TYPE) {
|
||||
return '$';
|
||||
@@ -2442,6 +2467,10 @@ function renderModelDestructive(
|
||||
|
||||
const element: ReactElement = (value: any);
|
||||
|
||||
if (serializedSize > MAX_ROW_SIZE) {
|
||||
return deferTask(request, task);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const debugInfo: ?ReactDebugInfo = (value: any)._debugInfo;
|
||||
if (debugInfo) {
|
||||
@@ -2500,6 +2529,10 @@ function renderModelDestructive(
|
||||
return newChild;
|
||||
}
|
||||
case REACT_LAZY_TYPE: {
|
||||
if (serializedSize > MAX_ROW_SIZE) {
|
||||
return deferTask(request, task);
|
||||
}
|
||||
|
||||
// Reset the task's thenable state before continuing. If there was one, it was
|
||||
// from suspending the lazy before.
|
||||
task.thenableState = null;
|
||||
@@ -2811,6 +2844,7 @@ function renderModelDestructive(
|
||||
throwTaintViolation(tainted.message);
|
||||
}
|
||||
}
|
||||
serializedSize += value.length;
|
||||
// TODO: Maybe too clever. If we support URL there's no similar trick.
|
||||
if (value[value.length - 1] === 'Z') {
|
||||
// Possibly a Date, whose toJSON automatically calls toISOString
|
||||
@@ -3892,9 +3926,18 @@ function emitChunk(
|
||||
return;
|
||||
}
|
||||
// For anything else we need to try to serialize it using JSON.
|
||||
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
|
||||
const json: string = stringify(value, task.toJSON);
|
||||
emitModelChunk(request, task.id, json);
|
||||
// We stash the outer parent size so we can restore it when we exit.
|
||||
const parentSerializedSize = serializedSize;
|
||||
// We don't reset the serialized size counter from reentry because that indicates that we
|
||||
// are outlining a model and we actually want to include that size into the parent since
|
||||
// it will still block the parent row. It only restores to zero at the top of the stack.
|
||||
try {
|
||||
// $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
|
||||
const json: string = stringify(value, task.toJSON);
|
||||
emitModelChunk(request, task.id, json);
|
||||
} finally {
|
||||
serializedSize = parentSerializedSize;
|
||||
}
|
||||
}
|
||||
|
||||
function erroredTask(request: Request, task: Task, error: mixed): void {
|
||||
|
||||
@@ -28,3 +28,4 @@ export const enableSiblingPrerendering = __VARIANT__;
|
||||
export const enableFastAddPropertiesInDiffing = __VARIANT__;
|
||||
export const enableLazyPublicInstanceInFabric = __VARIANT__;
|
||||
export const renameElementSymbol = __VARIANT__;
|
||||
export const enableFragmentRefs = __VARIANT__;
|
||||
|
||||
@@ -30,6 +30,7 @@ export const {
|
||||
enableFastAddPropertiesInDiffing,
|
||||
enableLazyPublicInstanceInFabric,
|
||||
renameElementSymbol,
|
||||
enableFragmentRefs,
|
||||
} = dynamicFlags;
|
||||
|
||||
// The rest of the flags are static for better dead code elimination.
|
||||
@@ -84,7 +85,6 @@ export const enableGestureTransition = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableSrcObject = false;
|
||||
export const enableFragmentRefs = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
|
||||
@@ -543,5 +543,6 @@
|
||||
"555": "Cannot requestFormReset() inside a startGestureTransition. There should be no side-effects associated with starting a Gesture until its Action is invoked. Move side-effects to the Action instead.",
|
||||
"556": "Expected prepareToHydrateHostActivityInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"557": "Expected to have a hydrated activity instance. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"558": "Client rendering an Activity suspended it again. This is a bug in React."
|
||||
"558": "Client rendering an Activity suspended it again. This is a bug in React.",
|
||||
"559": "Expected to find a host node. This is a bug in React."
|
||||
}
|
||||
|
||||
@@ -33,6 +33,46 @@ declare interface ConsoleTask {
|
||||
run<T>(f: () => T): T;
|
||||
}
|
||||
|
||||
declare var console: {
|
||||
assert(condition: mixed, ...data: Array<any>): void,
|
||||
clear(): void,
|
||||
count(label?: string): void,
|
||||
countReset(label?: string): void,
|
||||
debug(...data: Array<any>): void,
|
||||
dir(...data: Array<any>): void,
|
||||
dirxml(...data: Array<any>): void,
|
||||
error(...data: Array<any>): void,
|
||||
_exception(...data: Array<any>): void,
|
||||
group(...data: Array<any>): void,
|
||||
groupCollapsed(...data: Array<any>): void,
|
||||
groupEnd(): void,
|
||||
info(...data: Array<any>): void,
|
||||
log(...data: Array<any>): void,
|
||||
profile(name?: string): void,
|
||||
profileEnd(name?: string): void,
|
||||
table(
|
||||
tabularData:
|
||||
| {[key: string]: any, ...}
|
||||
| Array<{[key: string]: any, ...}>
|
||||
| Array<Array<any>>,
|
||||
): void,
|
||||
time(label?: string): void,
|
||||
timeEnd(label: string): void,
|
||||
timeStamp(
|
||||
label?: string,
|
||||
start?: string | number,
|
||||
end?: string | number,
|
||||
trackName?: string,
|
||||
trackGroup?: string,
|
||||
color?: string,
|
||||
): void,
|
||||
timeLog(label?: string, ...data?: Array<any>): void,
|
||||
trace(...data: Array<any>): void,
|
||||
warn(...data: Array<any>): void,
|
||||
createTask(label: string): ConsoleTask,
|
||||
...
|
||||
};
|
||||
|
||||
type ScrollTimelineOptions = {
|
||||
source: Element,
|
||||
axis?: 'block' | 'inline' | 'x' | 'y',
|
||||
|
||||
Reference in New Issue
Block a user