Compare commits
23 Commits
pr33310
...
component-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca1d5e068b | ||
|
|
ecb1861dd8 | ||
|
|
a5861172c6 | ||
|
|
df0a663a8c | ||
|
|
6c71a7766d | ||
|
|
9275c835c3 | ||
|
|
9cae1cea4e | ||
|
|
183bd4feac | ||
|
|
81c3a5331e | ||
|
|
ab86a5efe8 | ||
|
|
1e4614bf13 | ||
|
|
c5ab27a649 | ||
|
|
049bfbb169 | ||
|
|
a85b0b0bb4 | ||
|
|
789e5f02c5 | ||
|
|
d6d929e2f1 | ||
|
|
26315d64dc | ||
|
|
2852c9d08c | ||
|
|
94718f18b4 | ||
|
|
76dddd1d57 | ||
|
|
a75932b2ea | ||
|
|
8fa3dfc845 | ||
|
|
0e5c79cfea |
@@ -496,6 +496,7 @@ module.exports = {
|
||||
'packages/react-devtools-shared/src/devtools/views/**/*.js',
|
||||
'packages/react-devtools-shared/src/hook.js',
|
||||
'packages/react-devtools-shared/src/backend/console.js',
|
||||
'packages/react-devtools-shared/src/backend/fiber/renderer.js',
|
||||
'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
|
||||
'packages/react-devtools-shared/src/frontend/utils/withPermissionsCheck.js',
|
||||
],
|
||||
@@ -504,6 +505,7 @@ module.exports = {
|
||||
__IS_FIREFOX__: 'readonly',
|
||||
__IS_EDGE__: 'readonly',
|
||||
__IS_NATIVE__: 'readonly',
|
||||
__IS_INTERNAL_MCP_BUILD__: 'readonly',
|
||||
__IS_INTERNAL_VERSION__: 'readonly',
|
||||
chrome: 'readonly',
|
||||
},
|
||||
|
||||
@@ -452,7 +452,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||
reason:
|
||||
'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)',
|
||||
loc: callee.loc,
|
||||
description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`,
|
||||
description: `Cannot call ${hookKind} within a function component`,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
|
||||
6 | const ComponentWithHookInsideCallback = React.forwardRef((props, ref) => {
|
||||
7 | useEffect(() => {
|
||||
> 8 | useHookInsideCallback();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (8:8)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (8:8)
|
||||
9 | });
|
||||
10 | return <button {...props} ref={ref} />;
|
||||
11 | });
|
||||
|
||||
@@ -23,7 +23,7 @@ const ComponentWithHookInsideCallback = React.memo(props => {
|
||||
6 | const ComponentWithHookInsideCallback = React.memo(props => {
|
||||
7 | useEffect(() => {
|
||||
> 8 | useHookInsideCallback();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (8:8)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (8:8)
|
||||
9 | });
|
||||
10 | return <button {...props} />;
|
||||
11 | });
|
||||
|
||||
@@ -31,7 +31,7 @@ function Component() {
|
||||
8 | const y = {
|
||||
9 | inner() {
|
||||
> 10 | return useFoo();
|
||||
| ^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (10:10)
|
||||
| ^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (10:10)
|
||||
11 | },
|
||||
12 | };
|
||||
13 | return y;
|
||||
|
||||
@@ -27,7 +27,7 @@ function Component() {
|
||||
6 | const y = {
|
||||
7 | inner() {
|
||||
> 8 | return useFoo();
|
||||
| ^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (8:8)
|
||||
| ^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (8:8)
|
||||
9 | },
|
||||
10 | };
|
||||
11 | return y;
|
||||
|
||||
@@ -21,7 +21,7 @@ function createHook() {
|
||||
4 | return function useHookWithConditionalHook() {
|
||||
5 | if (cond) {
|
||||
> 6 | useConditionalHook();
|
||||
| ^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (6:6)
|
||||
| ^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (6:6)
|
||||
7 | }
|
||||
8 | };
|
||||
9 | }
|
||||
|
||||
@@ -21,9 +21,9 @@ function createComponent() {
|
||||
4 | return function ComponentWithHookInsideCallback() {
|
||||
5 | useEffect(() => {
|
||||
> 6 | useHookInsideCallback();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (6:6)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (6:6)
|
||||
|
||||
InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useEffect within a function expression (5:5)
|
||||
InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useEffect within a function component (5:5)
|
||||
7 | });
|
||||
8 | };
|
||||
9 | }
|
||||
|
||||
@@ -21,7 +21,7 @@ function createComponent() {
|
||||
4 | return function ComponentWithHookInsideCallback() {
|
||||
5 | function handleClick() {
|
||||
> 6 | useState();
|
||||
| ^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useState within a function expression (6:6)
|
||||
| ^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useState within a function component (6:6)
|
||||
7 | }
|
||||
8 | };
|
||||
9 | }
|
||||
|
||||
@@ -19,7 +19,7 @@ function ComponentWithHookInsideCallback() {
|
||||
3 | function ComponentWithHookInsideCallback() {
|
||||
4 | function handleClick() {
|
||||
> 5 | useState();
|
||||
| ^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useState within a function expression (5:5)
|
||||
| ^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useState within a function component (5:5)
|
||||
6 | }
|
||||
7 | }
|
||||
8 |
|
||||
|
||||
@@ -21,7 +21,7 @@ function createComponent() {
|
||||
4 | return function ComponentWithConditionalHook() {
|
||||
5 | if (cond) {
|
||||
> 6 | useConditionalHook();
|
||||
| ^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (6:6)
|
||||
| ^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (6:6)
|
||||
7 | }
|
||||
8 | };
|
||||
9 | }
|
||||
|
||||
@@ -19,7 +19,7 @@ function ComponentWithHookInsideCallback() {
|
||||
3 | function ComponentWithHookInsideCallback() {
|
||||
4 | useEffect(() => {
|
||||
> 5 | useHookInsideCallback();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call hook within a function expression (5:5)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call Custom within a function component (5:5)
|
||||
6 | });
|
||||
7 | }
|
||||
8 |
|
||||
|
||||
@@ -31,7 +31,7 @@ function Component(props) {
|
||||
7 | };
|
||||
8 | useEffect(() => {
|
||||
> 9 | useEffect(() => {
|
||||
| ^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useEffect within a function expression (9:9)
|
||||
| ^^^^^^^^^ InvalidReact: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning). Cannot call useEffect within a function component (9:9)
|
||||
10 | function nested() {
|
||||
11 | fire(foo(props));
|
||||
12 | }
|
||||
|
||||
@@ -21,6 +21,7 @@ import {queryAlgolia} from './utils/algolia';
|
||||
import assertExhaustive from './utils/assertExhaustive';
|
||||
import {convert} from 'html-to-text';
|
||||
import {measurePerformance} from './tools/runtimePerf';
|
||||
import {parseReactComponentTree} from './tools/componentTree';
|
||||
|
||||
function calculateMean(values: number[]): string {
|
||||
return values.length > 0
|
||||
@@ -366,6 +367,45 @@ ${calculateMean(results.renderTime)}
|
||||
},
|
||||
);
|
||||
|
||||
server.tool(
|
||||
'parse-react-component-tree',
|
||||
`
|
||||
This tool gets the component tree of a React App.
|
||||
passing in a url will attempt to connect to the browser and get the current state of the component tree. If no url is passed in,
|
||||
the default url will be used (http://localhost:3000).
|
||||
|
||||
<requirements>
|
||||
- The url should be a full url with the protocol (http:// or https://) and the domain name (e.g. localhost:3000).
|
||||
- Also the user should be running a Chrome browser running on debug mode on port 9222. If you receive an error message, advise the user to run
|
||||
the following comand in the terminal:
|
||||
MacOS: "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome"
|
||||
Windows: "chrome.exe --remote-debugging-port=9222 --user-data-dir=C:\temp\chrome"
|
||||
</requirements>
|
||||
`,
|
||||
{
|
||||
url: z.string().optional().default('http://localhost:3000'),
|
||||
},
|
||||
async ({url}) => {
|
||||
try {
|
||||
const componentTree = await parseReactComponentTree(url);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: componentTree,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{type: 'text' as const, text: `Error: ${err.stack}`}],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
server.prompt('review-react-code', () => ({
|
||||
messages: [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
export async function parseReactComponentTree(url: string): Promise<string> {
|
||||
try {
|
||||
const browser = await puppeteer.connect({
|
||||
browserURL: 'http://127.0.0.1:9222',
|
||||
defaultViewport: null,
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
|
||||
let localhostPage = null;
|
||||
for (const page of pages) {
|
||||
const pageUrl = await page.url();
|
||||
|
||||
if (pageUrl.startsWith(url)) {
|
||||
localhostPage = page;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (localhostPage) {
|
||||
const componentTree = await localhostPage.evaluate(() => {
|
||||
return (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces
|
||||
.get(1)
|
||||
.__internal_only_getComponentTree();
|
||||
});
|
||||
|
||||
return componentTree;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Could not open the page at ${url}. Is your server running?`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Failed extract component tree' + error);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
import React, {
|
||||
Fragment,
|
||||
Suspense,
|
||||
unstable_SuspenseList as SuspenseList,
|
||||
} from 'react';
|
||||
import React, {Fragment, Suspense} from 'react';
|
||||
|
||||
export default function LargeContent() {
|
||||
return (
|
||||
<SuspenseList revealOrder="forwards">
|
||||
<Fragment>
|
||||
<Suspense fallback={null}>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
|
||||
@@ -290,6 +286,6 @@ export default function LargeContent() {
|
||||
interdum a. Proin nec odio in nulla vestibulum.
|
||||
</p>
|
||||
</Suspense>
|
||||
</SuspenseList>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -515,22 +515,6 @@ const tests = {
|
||||
`,
|
||||
options: [{additionalHooks: 'useCustomEffect'}],
|
||||
},
|
||||
{
|
||||
// behaves like no deps
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useSpecialEffect(() => {
|
||||
console.log(props.foo);
|
||||
}, null);
|
||||
}
|
||||
`,
|
||||
options: [
|
||||
{
|
||||
additionalHooks: 'useSpecialEffect',
|
||||
experimental_autoDependenciesHooks: ['useSpecialEffect'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
@@ -1486,38 +1470,6 @@ const tests = {
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useSpecialEffect(() => {
|
||||
console.log(props.foo);
|
||||
}, null);
|
||||
}
|
||||
`,
|
||||
options: [{additionalHooks: 'useSpecialEffect'}],
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useSpecialEffect was passed a dependency list that is not an array literal. This means we can't statically verify whether you've passed the correct dependencies.",
|
||||
},
|
||||
{
|
||||
message:
|
||||
"React Hook useSpecialEffect has a missing dependency: 'props.foo'. Either include it or remove the dependency array.",
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [props.foo]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
useSpecialEffect(() => {
|
||||
console.log(props.foo);
|
||||
}, [props.foo]);
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent(props) {
|
||||
@@ -7869,24 +7821,6 @@ const testsTypescript = {
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useSpecialEffect(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
setState(prevState => prevState + someNumber);
|
||||
})
|
||||
}
|
||||
`,
|
||||
options: [
|
||||
{
|
||||
additionalHooks: 'useSpecialEffect',
|
||||
experimental_autoDependenciesHooks: ['useSpecialEffect'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function App() {
|
||||
@@ -8242,48 +8176,6 @@ const testsTypescript = {
|
||||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useSpecialEffect(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
setState(prevState => prevState + someNumber + state);
|
||||
}, [])
|
||||
}
|
||||
`,
|
||||
options: [
|
||||
{
|
||||
additionalHooks: 'useSpecialEffect',
|
||||
experimental_autoDependenciesHooks: ['useSpecialEffect'],
|
||||
},
|
||||
],
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"React Hook useSpecialEffect has a missing dependency: 'state'. " +
|
||||
'Either include it or remove the dependency array. ' +
|
||||
`You can also do a functional update 'setState(s => ...)' ` +
|
||||
`if you only need 'state' in the 'setState' call.`,
|
||||
suggestions: [
|
||||
{
|
||||
desc: 'Update the dependencies array to be: [state]',
|
||||
output: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useSpecialEffect(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
setState(prevState => prevState + someNumber + state);
|
||||
}, [state])
|
||||
}
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: normalizeIndent`
|
||||
function MyComponent() {
|
||||
const [state, setState] = React.useState<number>(0);
|
||||
|
||||
useMemo(() => {
|
||||
const someNumber: typeof state = 2;
|
||||
console.log(someNumber);
|
||||
|
||||
@@ -61,38 +61,27 @@ const rule = {
|
||||
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
||||
type: 'boolean',
|
||||
},
|
||||
experimental_autoDependenciesHooks: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
create(context: Rule.RuleContext) {
|
||||
const rawOptions = context.options && context.options[0];
|
||||
|
||||
// Parse the `additionalHooks` regex.
|
||||
const additionalHooks =
|
||||
rawOptions && rawOptions.additionalHooks
|
||||
? new RegExp(rawOptions.additionalHooks)
|
||||
context.options &&
|
||||
context.options[0] &&
|
||||
context.options[0].additionalHooks
|
||||
? new RegExp(context.options[0].additionalHooks)
|
||||
: undefined;
|
||||
|
||||
const enableDangerousAutofixThisMayCauseInfiniteLoops: boolean =
|
||||
(rawOptions &&
|
||||
rawOptions.enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
||||
(context.options &&
|
||||
context.options[0] &&
|
||||
context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
||||
false;
|
||||
|
||||
const experimental_autoDependenciesHooks: ReadonlyArray<string> =
|
||||
rawOptions && Array.isArray(rawOptions.experimental_autoDependenciesHooks)
|
||||
? rawOptions.experimental_autoDependenciesHooks
|
||||
: [];
|
||||
|
||||
const options = {
|
||||
additionalHooks,
|
||||
experimental_autoDependenciesHooks,
|
||||
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
||||
};
|
||||
|
||||
@@ -173,7 +162,6 @@ const rule = {
|
||||
reactiveHook: Node,
|
||||
reactiveHookName: string,
|
||||
isEffect: boolean,
|
||||
isAutoDepsHook: boolean,
|
||||
): void {
|
||||
if (isEffect && node.async) {
|
||||
reportProblem({
|
||||
@@ -661,9 +649,6 @@ const rule = {
|
||||
}
|
||||
|
||||
if (!declaredDependenciesNode) {
|
||||
if (isAutoDepsHook) {
|
||||
return;
|
||||
}
|
||||
// Check if there are any top-level setState() calls.
|
||||
// Those tend to lead to infinite loops.
|
||||
let setStateInsideEffectWithoutDeps: string | null = null;
|
||||
@@ -726,13 +711,6 @@ const rule = {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isAutoDepsHook &&
|
||||
declaredDependenciesNode.type === 'Literal' &&
|
||||
declaredDependenciesNode.value === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const declaredDependencies: Array<DeclaredDependency> = [];
|
||||
const externalDependencies = new Set<string>();
|
||||
@@ -1340,19 +1318,10 @@ const rule = {
|
||||
return;
|
||||
}
|
||||
|
||||
const isAutoDepsHook =
|
||||
options.experimental_autoDependenciesHooks.includes(reactiveHookName);
|
||||
|
||||
// Check the declared dependencies for this reactive hook. If there is no
|
||||
// second argument then the reactive callback will re-run on every render.
|
||||
// So no need to check for dependency inclusion.
|
||||
if (
|
||||
(!declaredDependenciesNode ||
|
||||
(isAutoDepsHook &&
|
||||
declaredDependenciesNode.type === 'Literal' &&
|
||||
declaredDependenciesNode.value === null)) &&
|
||||
!isEffect
|
||||
) {
|
||||
if (!declaredDependenciesNode && !isEffect) {
|
||||
// These are only used for optimization.
|
||||
if (
|
||||
reactiveHookName === 'useMemo' ||
|
||||
@@ -1386,17 +1355,11 @@ const rule = {
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
isAutoDepsHook,
|
||||
);
|
||||
return; // Handled
|
||||
case 'Identifier':
|
||||
if (
|
||||
!declaredDependenciesNode ||
|
||||
(isAutoDepsHook &&
|
||||
declaredDependenciesNode.type === 'Literal' &&
|
||||
declaredDependenciesNode.value === null)
|
||||
) {
|
||||
// Always runs, no problems.
|
||||
if (!declaredDependenciesNode) {
|
||||
// No deps, no problems.
|
||||
return; // Handled
|
||||
}
|
||||
// The function passed as a callback is not written inline.
|
||||
@@ -1445,7 +1408,6 @@ const rule = {
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
isAutoDepsHook,
|
||||
);
|
||||
return; // Handled
|
||||
case 'VariableDeclarator':
|
||||
@@ -1465,7 +1427,6 @@ const rule = {
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
isAutoDepsHook,
|
||||
);
|
||||
return; // Handled
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ module.exports = {
|
||||
__IS_CHROME__: false,
|
||||
__IS_EDGE__: false,
|
||||
__IS_NATIVE__: true,
|
||||
__IS_INTERNAL_MCP_BUILD__: false,
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-core"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
|
||||
|
||||
@@ -91,6 +91,7 @@ module.exports = {
|
||||
__IS_FIREFOX__: false,
|
||||
__IS_CHROME__: false,
|
||||
__IS_EDGE__: false,
|
||||
__IS_INTERNAL_MCP_BUILD__: false,
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-core"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
|
||||
|
||||
@@ -78,6 +78,7 @@ module.exports = {
|
||||
__IS_FIREFOX__: IS_FIREFOX,
|
||||
__IS_EDGE__: IS_EDGE,
|
||||
__IS_NATIVE__: false,
|
||||
__IS_INTERNAL_MCP_BUILD__: false,
|
||||
}),
|
||||
new Webpack.SourceMapDevToolPlugin({
|
||||
filename: '[file].map',
|
||||
|
||||
@@ -33,6 +33,8 @@ const IS_FIREFOX = process.env.IS_FIREFOX === 'true';
|
||||
const IS_EDGE = process.env.IS_EDGE === 'true';
|
||||
const IS_INTERNAL_VERSION = process.env.FEATURE_FLAG_TARGET === 'extension-fb';
|
||||
|
||||
const IS_INTERNAL_MCP_BUILD = process.env.IS_INTERNAL_MCP_BUILD === 'true';
|
||||
|
||||
const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss';
|
||||
|
||||
const babelOptions = {
|
||||
@@ -113,6 +115,7 @@ module.exports = {
|
||||
__IS_FIREFOX__: IS_FIREFOX,
|
||||
__IS_EDGE__: IS_EDGE,
|
||||
__IS_NATIVE__: false,
|
||||
__IS_INTERNAL_MCP_BUILD__: IS_INTERNAL_MCP_BUILD,
|
||||
__IS_INTERNAL_VERSION__: IS_INTERNAL_VERSION,
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-extensions"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
|
||||
@@ -86,6 +86,7 @@ module.exports = {
|
||||
__IS_CHROME__: false,
|
||||
__IS_FIREFOX__: false,
|
||||
__IS_EDGE__: false,
|
||||
__IS_INTERNAL_MCP_BUILD__: false,
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-fusebox"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
|
||||
|
||||
@@ -78,6 +78,7 @@ module.exports = {
|
||||
__IS_FIREFOX__: false,
|
||||
__IS_EDGE__: false,
|
||||
__IS_NATIVE__: false,
|
||||
__IS_INTERNAL_MCP_BUILD__: false,
|
||||
'process.env.DEVTOOLS_PACKAGE': `"react-devtools-inline"`,
|
||||
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
|
||||
'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null,
|
||||
|
||||
@@ -5859,6 +5859,86 @@ export function attach(
|
||||
return unresolvedSource;
|
||||
}
|
||||
|
||||
type InternalMcpFunctions = {
|
||||
__internal_only_getComponentTree?: Function,
|
||||
};
|
||||
|
||||
const internalMcpFunctions: InternalMcpFunctions = {};
|
||||
if (__IS_INTERNAL_MCP_BUILD__) {
|
||||
// eslint-disable-next-line no-inner-declarations
|
||||
function __internal_only_getComponentTree(): string {
|
||||
let treeString = '';
|
||||
|
||||
function buildTreeString(
|
||||
instance: DevToolsInstance,
|
||||
prefix: string = '',
|
||||
isLastChild: boolean = true,
|
||||
): void {
|
||||
if (!instance) return;
|
||||
|
||||
const name =
|
||||
(instance.kind !== VIRTUAL_INSTANCE
|
||||
? getDisplayNameForFiber(instance.data)
|
||||
: instance.data.name) || 'Unknown';
|
||||
|
||||
const id = instance.id !== undefined ? instance.id : 'unknown';
|
||||
|
||||
if (name !== 'createRoot()') {
|
||||
treeString +=
|
||||
prefix +
|
||||
(isLastChild ? '└── ' : '├── ') +
|
||||
name +
|
||||
' (id: ' +
|
||||
id +
|
||||
')\n';
|
||||
}
|
||||
|
||||
const childPrefix = prefix + (isLastChild ? ' ' : '│ ');
|
||||
|
||||
let childCount = 0;
|
||||
let tempChild = instance.firstChild;
|
||||
while (tempChild !== null) {
|
||||
childCount++;
|
||||
tempChild = tempChild.nextSibling;
|
||||
}
|
||||
|
||||
let child = instance.firstChild;
|
||||
let currentChildIndex = 0;
|
||||
|
||||
while (child !== null) {
|
||||
currentChildIndex++;
|
||||
const isLastSibling = currentChildIndex === childCount;
|
||||
buildTreeString(child, childPrefix, isLastSibling);
|
||||
child = child.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
const rootInstances: Array<DevToolsInstance> = [];
|
||||
idToDevToolsInstanceMap.forEach(instance => {
|
||||
if (instance.parent === null || instance.parent.parent === null) {
|
||||
rootInstances.push(instance);
|
||||
}
|
||||
});
|
||||
|
||||
if (rootInstances.length > 0) {
|
||||
for (let i = 0; i < rootInstances.length; i++) {
|
||||
const isLast = i === rootInstances.length - 1;
|
||||
buildTreeString(rootInstances[i], '', isLast);
|
||||
if (!isLast) {
|
||||
treeString += '\n';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
treeString = 'No component tree found.';
|
||||
}
|
||||
|
||||
return treeString;
|
||||
}
|
||||
|
||||
internalMcpFunctions.__internal_only_getComponentTree =
|
||||
__internal_only_getComponentTree;
|
||||
}
|
||||
|
||||
return {
|
||||
cleanup,
|
||||
clearErrorsAndWarnings,
|
||||
@@ -5898,5 +5978,6 @@ export function attach(
|
||||
storeAsGlobal,
|
||||
updateComponentFilters,
|
||||
getEnvironmentNames,
|
||||
...internalMcpFunctions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1318,8 +1318,10 @@ describe('ReactDOMFizzServer', () => {
|
||||
expect(ref.current).toBe(null);
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
{'Loading A'}
|
||||
{'Loading B'}
|
||||
Loading A
|
||||
{/* // TODO: This is incorrect. It should be "Loading B" but Fizz SuspenseList
|
||||
// isn't implemented fully yet. */}
|
||||
<span>B</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
@@ -1333,9 +1335,11 @@ describe('ReactDOMFizzServer', () => {
|
||||
// We haven't resolved yet.
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
{'Loading A'}
|
||||
{'Loading B'}
|
||||
{'Loading C'}
|
||||
Loading A
|
||||
{/* // TODO: This is incorrect. It should be "Loading B" but Fizz SuspenseList
|
||||
// isn't implemented fully yet. */}
|
||||
<span>B</span>
|
||||
Loading C
|
||||
</div>,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
/**
|
||||
* 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 ./scripts/jest/ReactDOMServerIntegrationEnvironment
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
import {
|
||||
insertNodesAndExecuteScripts,
|
||||
getVisibleChildren,
|
||||
} from '../test-utils/FizzTestUtils';
|
||||
|
||||
let JSDOM;
|
||||
let React;
|
||||
let Suspense;
|
||||
let SuspenseList;
|
||||
let assertLog;
|
||||
let Scheduler;
|
||||
let ReactDOMFizzServer;
|
||||
let Stream;
|
||||
let document;
|
||||
let writable;
|
||||
let container;
|
||||
let buffer = '';
|
||||
let hasErrored = false;
|
||||
let fatalError = undefined;
|
||||
|
||||
describe('ReactDOMFizSuspenseList', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
JSDOM = require('jsdom').JSDOM;
|
||||
React = require('react');
|
||||
assertLog = require('internal-test-utils').assertLog;
|
||||
ReactDOMFizzServer = require('react-dom/server');
|
||||
Stream = require('stream');
|
||||
|
||||
Suspense = React.Suspense;
|
||||
SuspenseList = React.unstable_SuspenseList;
|
||||
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
// Test Environment
|
||||
const jsdom = new JSDOM(
|
||||
'<!DOCTYPE html><html><head></head><body><div id="container">',
|
||||
{
|
||||
runScripts: 'dangerously',
|
||||
},
|
||||
);
|
||||
document = jsdom.window.document;
|
||||
container = document.getElementById('container');
|
||||
global.window = jsdom.window;
|
||||
// The Fizz runtime assumes requestAnimationFrame exists so we need to polyfill it.
|
||||
global.requestAnimationFrame = global.window.requestAnimationFrame = cb =>
|
||||
setTimeout(cb);
|
||||
|
||||
buffer = '';
|
||||
hasErrored = false;
|
||||
|
||||
writable = new Stream.PassThrough();
|
||||
writable.setEncoding('utf8');
|
||||
writable.on('data', chunk => {
|
||||
buffer += chunk;
|
||||
});
|
||||
writable.on('error', error => {
|
||||
hasErrored = true;
|
||||
fatalError = error;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
async function serverAct(callback) {
|
||||
await callback();
|
||||
// Await one turn around the event loop.
|
||||
// This assumes that we'll flush everything we have so far.
|
||||
await new Promise(resolve => {
|
||||
setImmediate(resolve);
|
||||
});
|
||||
if (hasErrored) {
|
||||
throw fatalError;
|
||||
}
|
||||
// JSDOM doesn't support stream HTML parser so we need to give it a proper fragment.
|
||||
// We also want to execute any scripts that are embedded.
|
||||
// We assume that we have now received a proper fragment of HTML.
|
||||
const bufferedContent = buffer;
|
||||
buffer = '';
|
||||
const temp = document.createElement('body');
|
||||
temp.innerHTML = bufferedContent;
|
||||
await insertNodesAndExecuteScripts(temp, container, null);
|
||||
jest.runAllTimers();
|
||||
}
|
||||
|
||||
function Text(props) {
|
||||
Scheduler.log(props.text);
|
||||
return <span>{props.text}</span>;
|
||||
}
|
||||
|
||||
function createAsyncText(text) {
|
||||
let resolved = false;
|
||||
const Component = function () {
|
||||
if (!resolved) {
|
||||
Scheduler.log('Suspend! [' + text + ']');
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={text} />;
|
||||
};
|
||||
const promise = new Promise(resolve => {
|
||||
Component.resolve = function () {
|
||||
resolved = true;
|
||||
return resolve();
|
||||
};
|
||||
});
|
||||
return Component;
|
||||
}
|
||||
|
||||
// @gate enableSuspenseList
|
||||
it('shows content independently by default', async () => {
|
||||
const A = createAsyncText('A');
|
||||
const B = createAsyncText('B');
|
||||
const C = createAsyncText('C');
|
||||
|
||||
function Foo() {
|
||||
return (
|
||||
<div>
|
||||
<SuspenseList>
|
||||
<Suspense fallback={<Text text="Loading A" />}>
|
||||
<A />
|
||||
</Suspense>
|
||||
<Suspense fallback={<Text text="Loading B" />}>
|
||||
<B />
|
||||
</Suspense>
|
||||
<Suspense fallback={<Text text="Loading C" />}>
|
||||
<C />
|
||||
</Suspense>
|
||||
</SuspenseList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
await A.resolve();
|
||||
|
||||
await serverAct(async () => {
|
||||
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
|
||||
pipe(writable);
|
||||
});
|
||||
|
||||
assertLog(['A', 'Suspend! [B]', 'Suspend! [C]', 'Loading B', 'Loading C']);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>A</span>
|
||||
<span>Loading B</span>
|
||||
<span>Loading C</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await serverAct(() => C.resolve());
|
||||
assertLog(['C']);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>A</span>
|
||||
<span>Loading B</span>
|
||||
<span>C</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await serverAct(() => B.resolve());
|
||||
assertLog(['B']);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>A</span>
|
||||
<span>B</span>
|
||||
<span>C</span>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableSuspenseList
|
||||
it('displays each items in "forwards" order', async () => {
|
||||
const A = createAsyncText('A');
|
||||
const B = createAsyncText('B');
|
||||
const C = createAsyncText('C');
|
||||
|
||||
function Foo() {
|
||||
return (
|
||||
<div>
|
||||
<SuspenseList revealOrder="forwards">
|
||||
<Suspense fallback={<Text text="Loading A" />}>
|
||||
<A />
|
||||
</Suspense>
|
||||
<Suspense fallback={<Text text="Loading B" />}>
|
||||
<B />
|
||||
</Suspense>
|
||||
<Suspense fallback={<Text text="Loading C" />}>
|
||||
<C />
|
||||
</Suspense>
|
||||
</SuspenseList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
await C.resolve();
|
||||
|
||||
await serverAct(async () => {
|
||||
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
|
||||
pipe(writable);
|
||||
});
|
||||
|
||||
assertLog([
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]', // TODO: Defer rendering the content after fallback if previous suspended,
|
||||
'C',
|
||||
'Loading A',
|
||||
'Loading B',
|
||||
'Loading C',
|
||||
]);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>Loading A</span>
|
||||
<span>Loading B</span>
|
||||
<span>Loading C</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await serverAct(() => A.resolve());
|
||||
assertLog(['A']);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>A</span>
|
||||
<span>Loading B</span>
|
||||
<span>Loading C</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await serverAct(() => B.resolve());
|
||||
assertLog(['B']);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>A</span>
|
||||
<span>B</span>
|
||||
<span>C</span>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate enableSuspenseList
|
||||
it('displays each items in "backwards" order', async () => {
|
||||
const A = createAsyncText('A');
|
||||
const B = createAsyncText('B');
|
||||
const C = createAsyncText('C');
|
||||
|
||||
function Foo() {
|
||||
return (
|
||||
<div>
|
||||
<SuspenseList revealOrder="backwards">
|
||||
<Suspense fallback={<Text text="Loading A" />}>
|
||||
<A />
|
||||
</Suspense>
|
||||
<Suspense fallback={<Text text="Loading B" />}>
|
||||
<B />
|
||||
</Suspense>
|
||||
<Suspense fallback={<Text text="Loading C" />}>
|
||||
<C />
|
||||
</Suspense>
|
||||
</SuspenseList>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
await A.resolve();
|
||||
|
||||
await serverAct(async () => {
|
||||
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
|
||||
pipe(writable);
|
||||
});
|
||||
|
||||
assertLog([
|
||||
'Suspend! [C]',
|
||||
'Suspend! [B]', // TODO: Defer rendering the content after fallback if previous suspended,
|
||||
'A',
|
||||
'Loading C',
|
||||
'Loading B',
|
||||
'Loading A',
|
||||
]);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>Loading A</span>
|
||||
<span>Loading B</span>
|
||||
<span>Loading C</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await serverAct(() => C.resolve());
|
||||
assertLog(['C']);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>Loading A</span>
|
||||
<span>Loading B</span>
|
||||
<span>C</span>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await serverAct(() => B.resolve());
|
||||
assertLog(['B']);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
<span>A</span>
|
||||
<span>B</span>
|
||||
<span>C</span>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
});
|
||||
472
packages/react-server/src/ReactFizzServer.js
vendored
472
packages/react-server/src/ReactFizzServer.js
vendored
@@ -24,8 +24,6 @@ import type {
|
||||
ViewTransitionProps,
|
||||
ActivityProps,
|
||||
SuspenseProps,
|
||||
SuspenseListProps,
|
||||
SuspenseListRevealOrder,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
|
||||
import type {
|
||||
@@ -233,12 +231,6 @@ type LegacyContext = {
|
||||
[key: string]: any,
|
||||
};
|
||||
|
||||
type SuspenseListRow = {
|
||||
pendingTasks: number, // The number of tasks, previous rows and inner suspense boundaries blocking this row.
|
||||
boundaries: null | Array<SuspenseBoundary>, // The boundaries in this row waiting to be unblocked by the previous row. (null means this row is not blocked)
|
||||
next: null | SuspenseListRow, // The next row blocked by this one.
|
||||
};
|
||||
|
||||
const CLIENT_RENDERED = 4; // if it errors or infinitely suspends
|
||||
|
||||
type SuspenseBoundary = {
|
||||
@@ -246,7 +238,6 @@ type SuspenseBoundary = {
|
||||
rootSegmentID: number,
|
||||
parentFlushed: boolean,
|
||||
pendingTasks: number, // when it reaches zero we can show this boundary's content
|
||||
row: null | SuspenseListRow, // the row that this boundary blocks from completing.
|
||||
completedSegments: Array<Segment>, // completed but not yet flushed segments.
|
||||
byteSize: number, // used to determine whether to inline children boundaries.
|
||||
fallbackAbortableTasks: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.
|
||||
@@ -277,12 +268,11 @@ type RenderTask = {
|
||||
formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)
|
||||
context: ContextSnapshot, // the current new context that this task is executing in
|
||||
treeContext: TreeContext, // the current tree context that this task is executing in
|
||||
row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside
|
||||
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
|
||||
thenableState: null | ThenableState,
|
||||
legacyContext: LegacyContext, // the current legacy context that this task is executing in
|
||||
debugTask: null | ConsoleTask, // DEV only
|
||||
// DON'T ANY MORE FIELDS. We at 16 in prod already which otherwise requires converting to a constructor.
|
||||
// DON'T ANY MORE FIELDS. We at 16 already which otherwise requires converting to a constructor.
|
||||
// Consider splitting into multiple objects or consolidating some fields.
|
||||
};
|
||||
|
||||
@@ -308,11 +298,12 @@ type ReplayTask = {
|
||||
formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)
|
||||
context: ContextSnapshot, // the current new context that this task is executing in
|
||||
treeContext: TreeContext, // the current tree context that this task is executing in
|
||||
row: null | SuspenseListRow, // the current SuspenseList row that this is rendering inside
|
||||
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
|
||||
thenableState: null | ThenableState,
|
||||
legacyContext: LegacyContext, // the current legacy context that this task is executing in
|
||||
debugTask: null | ConsoleTask, // DEV only
|
||||
// DON'T ANY MORE FIELDS. We at 16 already which otherwise requires converting to a constructor.
|
||||
// Consider splitting into multiple objects or consolidating some fields.
|
||||
};
|
||||
|
||||
export type Task = RenderTask | ReplayTask;
|
||||
@@ -551,7 +542,6 @@ export function createRequest(
|
||||
rootContextSnapshot,
|
||||
emptyTreeContext,
|
||||
null,
|
||||
null,
|
||||
emptyContextObject,
|
||||
null,
|
||||
);
|
||||
@@ -657,7 +647,6 @@ export function resumeRequest(
|
||||
rootContextSnapshot,
|
||||
emptyTreeContext,
|
||||
null,
|
||||
null,
|
||||
emptyContextObject,
|
||||
null,
|
||||
);
|
||||
@@ -685,7 +674,6 @@ export function resumeRequest(
|
||||
rootContextSnapshot,
|
||||
emptyTreeContext,
|
||||
null,
|
||||
null,
|
||||
emptyContextObject,
|
||||
null,
|
||||
);
|
||||
@@ -751,7 +739,6 @@ function pingTask(request: Request, task: Task): void {
|
||||
|
||||
function createSuspenseBoundary(
|
||||
request: Request,
|
||||
row: null | SuspenseListRow,
|
||||
fallbackAbortableTasks: Set<Task>,
|
||||
contentPreamble: null | Preamble,
|
||||
fallbackPreamble: null | Preamble,
|
||||
@@ -761,7 +748,6 @@ function createSuspenseBoundary(
|
||||
rootSegmentID: -1,
|
||||
parentFlushed: false,
|
||||
pendingTasks: 0,
|
||||
row: row,
|
||||
completedSegments: [],
|
||||
byteSize: 0,
|
||||
fallbackAbortableTasks,
|
||||
@@ -779,17 +765,6 @@ function createSuspenseBoundary(
|
||||
boundary.errorStack = null;
|
||||
boundary.errorComponentStack = null;
|
||||
}
|
||||
if (row !== null) {
|
||||
// This boundary will block this row from completing.
|
||||
row.pendingTasks++;
|
||||
const blockedBoundaries = row.boundaries;
|
||||
if (blockedBoundaries !== null) {
|
||||
// Previous rows will block this boundary itself from completing.
|
||||
request.allPendingTasks++;
|
||||
boundary.pendingTasks++;
|
||||
blockedBoundaries.push(boundary);
|
||||
}
|
||||
}
|
||||
return boundary;
|
||||
}
|
||||
|
||||
@@ -807,7 +782,6 @@ function createRenderTask(
|
||||
formatContext: FormatContext,
|
||||
context: ContextSnapshot,
|
||||
treeContext: TreeContext,
|
||||
row: null | SuspenseListRow,
|
||||
componentStack: null | ComponentStackNode,
|
||||
legacyContext: LegacyContext,
|
||||
debugTask: null | ConsoleTask,
|
||||
@@ -818,9 +792,6 @@ function createRenderTask(
|
||||
} else {
|
||||
blockedBoundary.pendingTasks++;
|
||||
}
|
||||
if (row !== null) {
|
||||
row.pendingTasks++;
|
||||
}
|
||||
const task: RenderTask = ({
|
||||
replay: null,
|
||||
node,
|
||||
@@ -835,7 +806,6 @@ function createRenderTask(
|
||||
formatContext,
|
||||
context,
|
||||
treeContext,
|
||||
row,
|
||||
componentStack,
|
||||
thenableState,
|
||||
}: any);
|
||||
@@ -862,7 +832,6 @@ function createReplayTask(
|
||||
formatContext: FormatContext,
|
||||
context: ContextSnapshot,
|
||||
treeContext: TreeContext,
|
||||
row: null | SuspenseListRow,
|
||||
componentStack: null | ComponentStackNode,
|
||||
legacyContext: LegacyContext,
|
||||
debugTask: null | ConsoleTask,
|
||||
@@ -873,9 +842,6 @@ function createReplayTask(
|
||||
} else {
|
||||
blockedBoundary.pendingTasks++;
|
||||
}
|
||||
if (row !== null) {
|
||||
row.pendingTasks++;
|
||||
}
|
||||
replay.pendingTasks++;
|
||||
const task: ReplayTask = ({
|
||||
replay,
|
||||
@@ -891,7 +857,6 @@ function createReplayTask(
|
||||
formatContext,
|
||||
context,
|
||||
treeContext,
|
||||
row,
|
||||
componentStack,
|
||||
thenableState,
|
||||
}: any);
|
||||
@@ -1180,20 +1145,17 @@ function renderSuspenseBoundary(
|
||||
// so we can just render through it.
|
||||
const prevKeyPath = someTask.keyPath;
|
||||
const prevContext = someTask.formatContext;
|
||||
const prevRow = someTask.row;
|
||||
someTask.keyPath = keyPath;
|
||||
someTask.formatContext = getSuspenseContentFormatContext(
|
||||
request.resumableState,
|
||||
prevContext,
|
||||
);
|
||||
someTask.row = null;
|
||||
const content: ReactNodeList = props.children;
|
||||
try {
|
||||
renderNode(request, someTask, content, -1);
|
||||
} finally {
|
||||
someTask.keyPath = prevKeyPath;
|
||||
someTask.formatContext = prevContext;
|
||||
someTask.row = prevRow;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1202,7 +1164,6 @@ function renderSuspenseBoundary(
|
||||
|
||||
const prevKeyPath = task.keyPath;
|
||||
const prevContext = task.formatContext;
|
||||
const prevRow = task.row;
|
||||
const parentBoundary = task.blockedBoundary;
|
||||
const parentPreamble = task.blockedPreamble;
|
||||
const parentHoistableState = task.hoistableState;
|
||||
@@ -1220,19 +1181,12 @@ function renderSuspenseBoundary(
|
||||
if (canHavePreamble(task.formatContext)) {
|
||||
newBoundary = createSuspenseBoundary(
|
||||
request,
|
||||
task.row,
|
||||
fallbackAbortSet,
|
||||
createPreambleState(),
|
||||
createPreambleState(),
|
||||
);
|
||||
} else {
|
||||
newBoundary = createSuspenseBoundary(
|
||||
request,
|
||||
task.row,
|
||||
fallbackAbortSet,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
newBoundary = createSuspenseBoundary(request, fallbackAbortSet, null, null);
|
||||
}
|
||||
if (request.trackedPostpones !== null) {
|
||||
newBoundary.trackedContentKeyPath = keyPath;
|
||||
@@ -1336,7 +1290,6 @@ function renderSuspenseBoundary(
|
||||
),
|
||||
task.context,
|
||||
task.treeContext,
|
||||
null, // The row gets reset inside the Suspense boundary.
|
||||
task.componentStack,
|
||||
!disableLegacyContext ? task.legacyContext : emptyContextObject,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
@@ -1365,7 +1318,6 @@ function renderSuspenseBoundary(
|
||||
request.resumableState,
|
||||
prevContext,
|
||||
);
|
||||
task.row = null;
|
||||
contentRootSegment.status = RENDERING;
|
||||
|
||||
try {
|
||||
@@ -1387,14 +1339,6 @@ function renderSuspenseBoundary(
|
||||
// the fallback. However, if this boundary ended up big enough to be eligible for outlining
|
||||
// we can't do that because we might still need the fallback if we outline it.
|
||||
if (!isEligibleForOutlining(request, newBoundary)) {
|
||||
if (prevRow !== null) {
|
||||
// If we have synchronously completed the boundary and it's not eligible for outlining
|
||||
// then we don't have to wait for it to be flushed before we unblock future rows.
|
||||
// This lets us inline small rows in order.
|
||||
if (--prevRow.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, prevRow);
|
||||
}
|
||||
}
|
||||
if (request.pendingRootTasks === 0 && task.blockedPreamble) {
|
||||
// The root is complete and this boundary may contribute part of the preamble.
|
||||
// We eagerly attempt to prepare the preamble here because we expect most requests
|
||||
@@ -1461,7 +1405,6 @@ function renderSuspenseBoundary(
|
||||
task.blockedSegment = parentSegment;
|
||||
task.keyPath = prevKeyPath;
|
||||
task.formatContext = prevContext;
|
||||
task.row = prevRow;
|
||||
}
|
||||
|
||||
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
|
||||
@@ -1484,7 +1427,6 @@ function renderSuspenseBoundary(
|
||||
),
|
||||
task.context,
|
||||
task.treeContext,
|
||||
task.row,
|
||||
task.componentStack,
|
||||
!disableLegacyContext ? task.legacyContext : emptyContextObject,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
@@ -1509,7 +1451,6 @@ function replaySuspenseBoundary(
|
||||
): void {
|
||||
const prevKeyPath = task.keyPath;
|
||||
const prevContext = task.formatContext;
|
||||
const prevRow = task.row;
|
||||
const previousReplaySet: ReplaySet = task.replay;
|
||||
|
||||
const parentBoundary = task.blockedBoundary;
|
||||
@@ -1523,7 +1464,6 @@ function replaySuspenseBoundary(
|
||||
if (canHavePreamble(task.formatContext)) {
|
||||
resumedBoundary = createSuspenseBoundary(
|
||||
request,
|
||||
task.row,
|
||||
fallbackAbortSet,
|
||||
createPreambleState(),
|
||||
createPreambleState(),
|
||||
@@ -1531,7 +1471,6 @@ function replaySuspenseBoundary(
|
||||
} else {
|
||||
resumedBoundary = createSuspenseBoundary(
|
||||
request,
|
||||
task.row,
|
||||
fallbackAbortSet,
|
||||
null,
|
||||
null,
|
||||
@@ -1551,7 +1490,6 @@ function replaySuspenseBoundary(
|
||||
request.resumableState,
|
||||
prevContext,
|
||||
);
|
||||
task.row = null;
|
||||
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
|
||||
|
||||
try {
|
||||
@@ -1628,7 +1566,6 @@ function replaySuspenseBoundary(
|
||||
task.replay = previousReplaySet;
|
||||
task.keyPath = prevKeyPath;
|
||||
task.formatContext = prevContext;
|
||||
task.row = prevRow;
|
||||
}
|
||||
|
||||
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
|
||||
@@ -1656,7 +1593,6 @@ function replaySuspenseBoundary(
|
||||
),
|
||||
task.context,
|
||||
task.treeContext,
|
||||
task.row,
|
||||
task.componentStack,
|
||||
!disableLegacyContext ? task.legacyContext : emptyContextObject,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
@@ -1668,317 +1604,6 @@ function replaySuspenseBoundary(
|
||||
request.pingedTasks.push(suspendedFallbackTask);
|
||||
}
|
||||
|
||||
function finishSuspenseListRow(request: Request, row: SuspenseListRow): void {
|
||||
// This row finished. Now we have to unblock all the next rows that were blocked on this.
|
||||
// We do this in a loop to avoid stack overflow for very long lists that get unblocked.
|
||||
let unblockedRow = row.next;
|
||||
while (unblockedRow !== null) {
|
||||
// Unblocking the boundaries will decrement the count of this row but we keep it above
|
||||
// zero so they never finish this row recursively.
|
||||
const unblockedBoundaries = unblockedRow.boundaries;
|
||||
if (unblockedBoundaries !== null) {
|
||||
unblockedRow.boundaries = null;
|
||||
for (let i = 0; i < unblockedBoundaries.length; i++) {
|
||||
finishedTask(request, unblockedBoundaries[i], null, null);
|
||||
}
|
||||
}
|
||||
// Instead we decrement at the end to keep it all in this loop.
|
||||
unblockedRow.pendingTasks--;
|
||||
if (unblockedRow.pendingTasks > 0) {
|
||||
// Still blocked.
|
||||
break;
|
||||
}
|
||||
unblockedRow = unblockedRow.next;
|
||||
}
|
||||
}
|
||||
|
||||
function createSuspenseListRow(
|
||||
previousRow: null | SuspenseListRow,
|
||||
): SuspenseListRow {
|
||||
const newRow: SuspenseListRow = {
|
||||
pendingTasks: 1, // At first the row is blocked on attempting rendering itself.
|
||||
boundaries: null,
|
||||
next: null,
|
||||
};
|
||||
if (previousRow !== null && previousRow.pendingTasks > 0) {
|
||||
// If the previous row is not done yet, we add ourselves to be blocked on it.
|
||||
// When it finishes, we'll decrement our pending tasks.
|
||||
newRow.pendingTasks++;
|
||||
newRow.boundaries = [];
|
||||
previousRow.next = newRow;
|
||||
}
|
||||
return newRow;
|
||||
}
|
||||
|
||||
function renderSuspenseListRows(
|
||||
request: Request,
|
||||
task: Task,
|
||||
keyPath: KeyNode,
|
||||
rows: Array<ReactNodeList>,
|
||||
revealOrder: 'forwards' | 'backwards',
|
||||
): void {
|
||||
// This is a fork of renderChildrenArray that's aware of tracking rows.
|
||||
const prevKeyPath = task.keyPath;
|
||||
const previousComponentStack = task.componentStack;
|
||||
let previousDebugTask = null;
|
||||
if (__DEV__) {
|
||||
previousDebugTask = task.debugTask;
|
||||
// We read debugInfo from task.node.props.children instead of rows because it
|
||||
// might have been an unwrapped iterable so we read from the original node.
|
||||
pushServerComponentStack(task, (task.node: any).props.children._debugInfo);
|
||||
}
|
||||
|
||||
const prevTreeContext = task.treeContext;
|
||||
const prevRow = task.row;
|
||||
const totalChildren = rows.length;
|
||||
|
||||
if (task.replay !== null) {
|
||||
// Replay
|
||||
// First we need to check if we have any resume slots at this level.
|
||||
const resumeSlots = task.replay.slots;
|
||||
if (resumeSlots !== null && typeof resumeSlots === 'object') {
|
||||
let previousSuspenseListRow: null | SuspenseListRow = null;
|
||||
for (let n = 0; n < totalChildren; n++) {
|
||||
// Since we are going to resume into a slot whose order was already
|
||||
// determined by the prerender, we can safely resume it even in reverse
|
||||
// render order.
|
||||
const i = revealOrder !== 'backwards' ? n : totalChildren - 1 - n;
|
||||
const node = rows[i];
|
||||
task.row = previousSuspenseListRow = createSuspenseListRow(
|
||||
previousSuspenseListRow,
|
||||
);
|
||||
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
|
||||
const resumeSegmentID = resumeSlots[i];
|
||||
// TODO: If this errors we should still continue with the next sibling.
|
||||
if (typeof resumeSegmentID === 'number') {
|
||||
resumeNode(request, task, resumeSegmentID, node, i);
|
||||
// We finished rendering this node, so now we can consume this
|
||||
// slot. This must happen after in case we rerender this task.
|
||||
delete resumeSlots[i];
|
||||
} else {
|
||||
renderNode(request, task, node, i);
|
||||
}
|
||||
if (--previousSuspenseListRow.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, previousSuspenseListRow);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let previousSuspenseListRow: null | SuspenseListRow = null;
|
||||
for (let n = 0; n < totalChildren; n++) {
|
||||
// Since we are going to resume into a slot whose order was already
|
||||
// determined by the prerender, we can safely resume it even in reverse
|
||||
// render order.
|
||||
const i = revealOrder !== 'backwards' ? n : totalChildren - 1 - n;
|
||||
const node = rows[i];
|
||||
if (__DEV__) {
|
||||
warnForMissingKey(request, task, node);
|
||||
}
|
||||
task.row = previousSuspenseListRow = createSuspenseListRow(
|
||||
previousSuspenseListRow,
|
||||
);
|
||||
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
|
||||
renderNode(request, task, node, i);
|
||||
if (--previousSuspenseListRow.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, previousSuspenseListRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
task = ((task: any): RenderTask); // Refined
|
||||
if (revealOrder !== 'backwards') {
|
||||
// Forwards direction
|
||||
let previousSuspenseListRow: null | SuspenseListRow = null;
|
||||
for (let i = 0; i < totalChildren; i++) {
|
||||
const node = rows[i];
|
||||
if (__DEV__) {
|
||||
warnForMissingKey(request, task, node);
|
||||
}
|
||||
task.row = previousSuspenseListRow = createSuspenseListRow(
|
||||
previousSuspenseListRow,
|
||||
);
|
||||
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
|
||||
renderNode(request, task, node, i);
|
||||
if (--previousSuspenseListRow.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, previousSuspenseListRow);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For backwards direction we need to do things a bit differently.
|
||||
// We give each row its own segment so that we can render the content in
|
||||
// reverse order but still emit it in the right order when we flush.
|
||||
const parentSegment = task.blockedSegment;
|
||||
const childIndex = parentSegment.children.length;
|
||||
const insertionIndex = parentSegment.chunks.length;
|
||||
let previousSuspenseListRow: null | SuspenseListRow = null;
|
||||
for (let i = totalChildren - 1; i >= 0; i--) {
|
||||
const node = rows[i];
|
||||
task.row = previousSuspenseListRow = createSuspenseListRow(
|
||||
previousSuspenseListRow,
|
||||
);
|
||||
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
|
||||
const newSegment = createPendingSegment(
|
||||
request,
|
||||
insertionIndex,
|
||||
null,
|
||||
task.formatContext,
|
||||
// Assume we are text embedded at the trailing edges
|
||||
i === 0 ? parentSegment.lastPushedText : true,
|
||||
true,
|
||||
);
|
||||
// Insert in the beginning of the sequence, which will insert before any previous rows.
|
||||
parentSegment.children.splice(childIndex, 0, newSegment);
|
||||
task.blockedSegment = newSegment;
|
||||
if (__DEV__) {
|
||||
warnForMissingKey(request, task, node);
|
||||
}
|
||||
try {
|
||||
renderNode(request, task, node, i);
|
||||
pushSegmentFinale(
|
||||
newSegment.chunks,
|
||||
request.renderState,
|
||||
newSegment.lastPushedText,
|
||||
newSegment.textEmbedded,
|
||||
);
|
||||
newSegment.status = COMPLETED;
|
||||
finishedSegment(request, task.blockedBoundary, newSegment);
|
||||
if (--previousSuspenseListRow.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, previousSuspenseListRow);
|
||||
}
|
||||
} catch (thrownValue: mixed) {
|
||||
if (request.status === ABORTING) {
|
||||
newSegment.status = ABORTED;
|
||||
} else {
|
||||
newSegment.status = ERRORED;
|
||||
}
|
||||
throw thrownValue;
|
||||
}
|
||||
}
|
||||
task.blockedSegment = parentSegment;
|
||||
// Reset lastPushedText for current Segment since the new Segments "consumed" it
|
||||
parentSegment.lastPushedText = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Because this context is always set right before rendering every child, we
|
||||
// only need to reset it to the previous value at the very end.
|
||||
task.treeContext = prevTreeContext;
|
||||
task.row = prevRow;
|
||||
task.keyPath = prevKeyPath;
|
||||
if (__DEV__) {
|
||||
task.componentStack = previousComponentStack;
|
||||
task.debugTask = previousDebugTask;
|
||||
}
|
||||
}
|
||||
|
||||
function renderSuspenseList(
|
||||
request: Request,
|
||||
task: Task,
|
||||
keyPath: KeyNode,
|
||||
props: SuspenseListProps,
|
||||
): void {
|
||||
const children: any = props.children;
|
||||
const revealOrder: SuspenseListRevealOrder = props.revealOrder;
|
||||
// TODO: Support tail hidden/collapsed modes.
|
||||
// const tailMode: SuspenseListTailMode = props.tail;
|
||||
if (revealOrder === 'forwards' || revealOrder === 'backwards') {
|
||||
// For ordered reveal, we need to produce rows from the children.
|
||||
if (isArray(children)) {
|
||||
renderSuspenseListRows(request, task, keyPath, children, revealOrder);
|
||||
return;
|
||||
}
|
||||
const iteratorFn = getIteratorFn(children);
|
||||
if (iteratorFn) {
|
||||
const iterator = iteratorFn.call(children);
|
||||
if (iterator) {
|
||||
if (__DEV__) {
|
||||
validateIterable(task, children, -1, iterator, iteratorFn);
|
||||
}
|
||||
// TODO: We currently use the same id algorithm as regular nodes
|
||||
// but we need a new algorithm for SuspenseList that doesn't require
|
||||
// a full set to be loaded up front to support Async Iterable.
|
||||
// When we have that, we shouldn't buffer anymore.
|
||||
let step = iterator.next();
|
||||
if (!step.done) {
|
||||
const rows = [];
|
||||
do {
|
||||
rows.push(step.value);
|
||||
step = iterator.next();
|
||||
} while (!step.done);
|
||||
renderSuspenseListRows(request, task, keyPath, children, revealOrder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (
|
||||
enableAsyncIterableChildren &&
|
||||
typeof (children: any)[ASYNC_ITERATOR] === 'function'
|
||||
) {
|
||||
const iterator: AsyncIterator<ReactNodeList> = (children: any)[
|
||||
ASYNC_ITERATOR
|
||||
]();
|
||||
if (iterator) {
|
||||
if (__DEV__) {
|
||||
validateAsyncIterable(task, (children: any), -1, iterator);
|
||||
}
|
||||
// TODO: Update the task.children to be the iterator to avoid asking
|
||||
// for new iterators, but we currently warn for rendering these
|
||||
// so needs some refactoring to deal with the warning.
|
||||
|
||||
// Restore the thenable state before resuming.
|
||||
const prevThenableState = task.thenableState;
|
||||
task.thenableState = null;
|
||||
prepareToUseThenableState(prevThenableState);
|
||||
|
||||
// We need to know how many total rows are in this set, so that we
|
||||
// can allocate enough id slots to acommodate them. So we must exhaust
|
||||
// the iterator before we start recursively rendering the rows.
|
||||
// TODO: This is not great but I think it's inherent to the id
|
||||
// generation algorithm.
|
||||
|
||||
const rows = [];
|
||||
|
||||
let done = false;
|
||||
|
||||
if (iterator === children) {
|
||||
// If it's an iterator we need to continue reading where we left
|
||||
// off. We can do that by reading the first few rows from the previous
|
||||
// thenable state.
|
||||
// $FlowFixMe
|
||||
let step = readPreviousThenableFromState();
|
||||
while (step !== undefined) {
|
||||
if (step.done) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
rows.push(step.value);
|
||||
step = readPreviousThenableFromState();
|
||||
}
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
let step = unwrapThenable(iterator.next());
|
||||
while (!step.done) {
|
||||
rows.push(step.value);
|
||||
step = unwrapThenable(iterator.next());
|
||||
}
|
||||
}
|
||||
renderSuspenseListRows(request, task, keyPath, rows, revealOrder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// This case will warn on the client. It's the same as independent revealOrder.
|
||||
}
|
||||
|
||||
if (revealOrder === 'together') {
|
||||
// TODO
|
||||
}
|
||||
// For other reveal order modes, we just render it as a fragment.
|
||||
const prevKeyPath = task.keyPath;
|
||||
task.keyPath = keyPath;
|
||||
renderNodeDestructive(request, task, children, -1);
|
||||
task.keyPath = prevKeyPath;
|
||||
}
|
||||
|
||||
function renderPreamble(
|
||||
request: Request,
|
||||
task: Task,
|
||||
@@ -2009,7 +1634,6 @@ function renderPreamble(
|
||||
task.formatContext,
|
||||
task.context,
|
||||
task.treeContext,
|
||||
task.row,
|
||||
task.componentStack,
|
||||
!disableLegacyContext ? task.legacyContext : emptyContextObject,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
@@ -2759,7 +2383,11 @@ function renderElement(
|
||||
return;
|
||||
}
|
||||
case REACT_SUSPENSE_LIST_TYPE: {
|
||||
renderSuspenseList(request, task, keyPath, props);
|
||||
// TODO: SuspenseList should control the boundaries.
|
||||
const prevKeyPath = task.keyPath;
|
||||
task.keyPath = keyPath;
|
||||
renderNodeDestructive(request, task, props.children, -1);
|
||||
task.keyPath = prevKeyPath;
|
||||
return;
|
||||
}
|
||||
case REACT_VIEW_TRANSITION_TYPE: {
|
||||
@@ -3909,7 +3537,6 @@ function spawnNewSuspendedReplayTask(
|
||||
task.formatContext,
|
||||
task.context,
|
||||
task.treeContext,
|
||||
task.row,
|
||||
task.componentStack,
|
||||
!disableLegacyContext ? task.legacyContext : emptyContextObject,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
@@ -3951,7 +3578,6 @@ function spawnNewSuspendedRenderTask(
|
||||
task.formatContext,
|
||||
task.context,
|
||||
task.treeContext,
|
||||
task.row,
|
||||
task.componentStack,
|
||||
!disableLegacyContext ? task.legacyContext : emptyContextObject,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
@@ -4260,19 +3886,10 @@ function erroredReplay(
|
||||
function erroredTask(
|
||||
request: Request,
|
||||
boundary: Root | SuspenseBoundary,
|
||||
row: null | SuspenseListRow,
|
||||
error: mixed,
|
||||
errorInfo: ThrownInfo,
|
||||
debugTask: null | ConsoleTask,
|
||||
) {
|
||||
if (row !== null) {
|
||||
if (--row.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, row);
|
||||
}
|
||||
}
|
||||
|
||||
request.allPendingTasks--;
|
||||
|
||||
// Report the error to a global handler.
|
||||
let errorDigest;
|
||||
// We don't handle halts here because we only halt when prerendering and
|
||||
@@ -4324,6 +3941,7 @@ function erroredTask(
|
||||
}
|
||||
}
|
||||
|
||||
request.allPendingTasks--;
|
||||
if (request.allPendingTasks === 0) {
|
||||
completeAll(request);
|
||||
}
|
||||
@@ -4338,7 +3956,7 @@ function abortTaskSoft(this: Request, task: Task): void {
|
||||
const segment = task.blockedSegment;
|
||||
if (segment !== null) {
|
||||
segment.status = ABORTED;
|
||||
finishedTask(request, boundary, task.row, segment);
|
||||
finishedTask(request, boundary, segment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4352,7 +3970,6 @@ function abortRemainingSuspenseBoundary(
|
||||
): void {
|
||||
const resumedBoundary = createSuspenseBoundary(
|
||||
request,
|
||||
null,
|
||||
new Set(),
|
||||
null,
|
||||
null,
|
||||
@@ -4452,13 +4069,6 @@ function abortTask(task: Task, request: Request, error: mixed): void {
|
||||
segment.status = ABORTED;
|
||||
}
|
||||
|
||||
const row = task.row;
|
||||
if (row !== null) {
|
||||
if (--row.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, row);
|
||||
}
|
||||
}
|
||||
|
||||
const errorInfo = getThrownInfo(task.componentStack);
|
||||
|
||||
if (boundary === null) {
|
||||
@@ -4481,7 +4091,7 @@ function abortTask(task: Task, request: Request, error: mixed): void {
|
||||
// we just need to mark it as postponed.
|
||||
logPostpone(request, postponeInstance.message, errorInfo, null);
|
||||
trackPostpone(request, trackedPostpones, task, segment);
|
||||
finishedTask(request, null, row, segment);
|
||||
finishedTask(request, null, segment);
|
||||
} else {
|
||||
const fatal = new Error(
|
||||
'The render was aborted with postpone when the shell is incomplete. Reason: ' +
|
||||
@@ -4500,7 +4110,7 @@ function abortTask(task: Task, request: Request, error: mixed): void {
|
||||
// We log the error but we still resolve the prerender
|
||||
logRecoverableError(request, error, errorInfo, null);
|
||||
trackPostpone(request, trackedPostpones, task, segment);
|
||||
finishedTask(request, null, row, segment);
|
||||
finishedTask(request, null, segment);
|
||||
} else {
|
||||
logRecoverableError(request, error, errorInfo, null);
|
||||
fatalError(request, error, errorInfo, null);
|
||||
@@ -4572,7 +4182,7 @@ function abortTask(task: Task, request: Request, error: mixed): void {
|
||||
abortTask(fallbackTask, request, error),
|
||||
);
|
||||
boundary.fallbackAbortableTasks.clear();
|
||||
return finishedTask(request, boundary, row, segment);
|
||||
return finishedTask(request, boundary, segment);
|
||||
}
|
||||
}
|
||||
boundary.status = CLIENT_RENDERED;
|
||||
@@ -4589,7 +4199,7 @@ function abortTask(task: Task, request: Request, error: mixed): void {
|
||||
logPostpone(request, postponeInstance.message, errorInfo, null);
|
||||
if (request.trackedPostpones !== null && segment !== null) {
|
||||
trackPostpone(request, request.trackedPostpones, task, segment);
|
||||
finishedTask(request, task.blockedBoundary, row, segment);
|
||||
finishedTask(request, task.blockedBoundary, segment);
|
||||
|
||||
// If this boundary was still pending then we haven't already cancelled its fallbacks.
|
||||
// We'll need to abort the fallbacks, which will also error that parent boundary.
|
||||
@@ -4745,14 +4355,8 @@ function finishedSegment(
|
||||
function finishedTask(
|
||||
request: Request,
|
||||
boundary: Root | SuspenseBoundary,
|
||||
row: null | SuspenseListRow,
|
||||
segment: null | Segment,
|
||||
) {
|
||||
if (row !== null) {
|
||||
if (--row.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, row);
|
||||
}
|
||||
}
|
||||
request.allPendingTasks--;
|
||||
if (boundary === null) {
|
||||
if (segment !== null && segment.parentFlushed) {
|
||||
@@ -4801,13 +4405,6 @@ function finishedTask(
|
||||
if (!isEligibleForOutlining(request, boundary)) {
|
||||
boundary.fallbackAbortableTasks.forEach(abortTaskSoft, request);
|
||||
boundary.fallbackAbortableTasks.clear();
|
||||
const boundaryRow = boundary.row;
|
||||
if (boundaryRow !== null) {
|
||||
// If we aren't eligible for outlining, we don't have to wait until we flush it.
|
||||
if (--boundaryRow.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, boundaryRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -4906,7 +4503,7 @@ function retryRenderTask(
|
||||
task.abortSet.delete(task);
|
||||
segment.status = COMPLETED;
|
||||
finishedSegment(request, task.blockedBoundary, segment);
|
||||
finishedTask(request, task.blockedBoundary, task.row, segment);
|
||||
finishedTask(request, task.blockedBoundary, segment);
|
||||
} catch (thrownValue: mixed) {
|
||||
resetHooksState();
|
||||
|
||||
@@ -4959,7 +4556,7 @@ function retryRenderTask(
|
||||
}
|
||||
|
||||
trackPostpone(request, trackedPostpones, task, segment);
|
||||
finishedTask(request, task.blockedBoundary, task.row, segment);
|
||||
finishedTask(request, task.blockedBoundary, segment);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4993,7 +4590,7 @@ function retryRenderTask(
|
||||
__DEV__ ? task.debugTask : null,
|
||||
);
|
||||
trackPostpone(request, trackedPostpones, task, segment);
|
||||
finishedTask(request, task.blockedBoundary, task.row, segment);
|
||||
finishedTask(request, task.blockedBoundary, segment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -5005,7 +4602,6 @@ function retryRenderTask(
|
||||
erroredTask(
|
||||
request,
|
||||
task.blockedBoundary,
|
||||
task.row,
|
||||
x,
|
||||
errorInfo,
|
||||
__DEV__ ? task.debugTask : null,
|
||||
@@ -5053,7 +4649,7 @@ function retryReplayTask(request: Request, task: ReplayTask): void {
|
||||
task.replay.pendingTasks--;
|
||||
|
||||
task.abortSet.delete(task);
|
||||
finishedTask(request, task.blockedBoundary, task.row, null);
|
||||
finishedTask(request, task.blockedBoundary, null);
|
||||
} catch (thrownValue) {
|
||||
resetHooksState();
|
||||
|
||||
@@ -5365,16 +4961,6 @@ function flushSegment(
|
||||
// Emit a client rendered suspense boundary wrapper.
|
||||
// We never queue the inner boundary so we'll never emit its content or partial segments.
|
||||
|
||||
const row = boundary.row;
|
||||
if (row !== null) {
|
||||
// Since this boundary end up client rendered, we can unblock future suspense list rows.
|
||||
// This means that they may appear out of order if the future rows succeed but this is
|
||||
// a client rendered row.
|
||||
if (--row.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, row);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
writeStartClientRenderedSuspenseBoundary(
|
||||
destination,
|
||||
@@ -5463,16 +5049,6 @@ function flushSegment(
|
||||
if (hoistableState) {
|
||||
hoistHoistables(hoistableState, boundary.contentState);
|
||||
}
|
||||
|
||||
const row = boundary.row;
|
||||
if (row !== null && isEligibleForOutlining(request, boundary)) {
|
||||
// Once we have written the boundary, we can unblock the row and let future
|
||||
// rows be written. This may schedule new completed boundaries.
|
||||
if (--row.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, row);
|
||||
}
|
||||
}
|
||||
|
||||
// We can inline this boundary's content as a complete boundary.
|
||||
writeStartCompletedSuspenseBoundary(destination, request.renderState);
|
||||
|
||||
@@ -5551,15 +5127,6 @@ function flushCompletedBoundary(
|
||||
}
|
||||
completedSegments.length = 0;
|
||||
|
||||
const row = boundary.row;
|
||||
if (row !== null && isEligibleForOutlining(request, boundary)) {
|
||||
// Once we have written the boundary, we can unblock the row and let future
|
||||
// rows be written. This may schedule new completed boundaries.
|
||||
if (--row.pendingTasks === 0) {
|
||||
finishSuspenseListRow(request, row);
|
||||
}
|
||||
}
|
||||
|
||||
writeHoistablesForBoundary(
|
||||
destination,
|
||||
boundary.contentState,
|
||||
@@ -5752,7 +5319,6 @@ function flushCompletedQueues(
|
||||
|
||||
// Next we check the completed boundaries again. This may have had
|
||||
// boundaries added to it in case they were too larged to be inlined.
|
||||
// SuspenseListRows might have been unblocked as well.
|
||||
// New ones might be added in this loop.
|
||||
const largeBoundaries = request.completedBoundaries;
|
||||
for (i = 0; i < largeBoundaries.length; i++) {
|
||||
|
||||
1
scripts/flow/react-devtools.js
vendored
1
scripts/flow/react-devtools.js
vendored
@@ -16,5 +16,6 @@ declare const __IS_FIREFOX__: boolean;
|
||||
declare const __IS_CHROME__: boolean;
|
||||
declare const __IS_EDGE__: boolean;
|
||||
declare const __IS_NATIVE__: boolean;
|
||||
declare const __IS_INTERNAL_MCP_BUILD__: boolean;
|
||||
|
||||
declare const chrome: any;
|
||||
|
||||
@@ -15,6 +15,7 @@ global.__IS_FIREFOX__ = false;
|
||||
global.__IS_CHROME__ = false;
|
||||
global.__IS_EDGE__ = false;
|
||||
global.__IS_NATIVE__ = false;
|
||||
global.__IS_INTERNAL_MCP_BUILD__ = false;
|
||||
|
||||
const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user