Skip to content

Commit 38a1aed

Browse files
authored
[Fizz] Add FormatContext and Refactor Work (#21103)
* Add format context * Let the Work node hold all working state for the recursive loop Stacks are nice and all but there's a cost to maintaining each frame both in terms of stack size usage and writing to it. * Move current format context into work * Synchronously render children of a Suspense boundary We don't have to spawn work and snapshot the context. Instead we can try to render the boundary immediately in case it works. * Lazily create the fallback work Instead of eagerly create the fallback work and then immediately abort it. We can just avoid creating it if we finish synchronously.
1 parent 1b7e471 commit 38a1aed

File tree

7 files changed

+188
-58
lines changed

7 files changed

+188
-58
lines changed

packages/react-dom/src/server/ReactDOMFizzServerBrowser.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
abort,
1717
} from 'react-server/src/ReactFizzServer';
1818

19-
import {createResponseState} from './ReactDOMServerFormatConfig';
19+
import {
20+
createResponseState,
21+
createRootFormatContext,
22+
} from './ReactDOMServerFormatConfig';
2023

2124
type Options = {
2225
identifierPrefix?: string,
@@ -46,6 +49,7 @@ function renderToReadableStream(
4649
children,
4750
controller,
4851
createResponseState(options ? options.identifierPrefix : undefined),
52+
createRootFormatContext(), // We call this here in case we need options to initialize it.
4953
options ? options.progressiveChunkSize : undefined,
5054
options ? options.onError : undefined,
5155
options ? options.onCompleteAll : undefined,

packages/react-dom/src/server/ReactDOMFizzServerNode.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import {
1717
abort,
1818
} from 'react-server/src/ReactFizzServer';
1919

20-
import {createResponseState} from './ReactDOMServerFormatConfig';
20+
import {
21+
createResponseState,
22+
createRootFormatContext,
23+
} from './ReactDOMServerFormatConfig';
2124

2225
function createDrainHandler(destination, request) {
2326
return () => startFlowing(request);
@@ -46,6 +49,7 @@ function pipeToNodeWritable(
4649
children,
4750
destination,
4851
createResponseState(options ? options.identifierPrefix : undefined),
52+
createRootFormatContext(), // We call this here in case we need options to initialize it.
4953
options ? options.progressiveChunkSize : undefined,
5054
options ? options.onError : undefined,
5155
options ? options.onCompleteAll : undefined,

packages/react-dom/src/server/ReactDOMServerFormatConfig.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
import escapeTextForBrowser from './escapeTextForBrowser';
2323
import invariant from 'shared/invariant';
2424

25-
// Per response,
25+
// Per response, global state that is not contextual to the rendering subtree.
2626
export type ResponseState = {
2727
placeholderPrefix: PrecomputedChunk,
2828
segmentPrefix: PrecomputedChunk,
@@ -50,6 +50,56 @@ export function createResponseState(
5050
};
5151
}
5252

53+
// Constants for the namespace we use. We don't actually provide the namespace but conditionally
54+
// use different segment parents based on namespace. Therefore we use constants instead of the string.
55+
const ROOT_NAMESPACE = 0; // At the root we don't need to know which namespace it is. We just need to know that it's already the right one.
56+
const HTML_NAMESPACE = 1;
57+
const SVG_NAMESPACE = 2;
58+
const MATHML_NAMESPACE = 3;
59+
60+
type NamespaceFlag = 0 | 1 | 2 | 3;
61+
62+
// Lets us keep track of contextual state and pick it back up after suspending.
63+
export type FormatContext = {
64+
namespace: NamespaceFlag, // root/svg/html/mathml
65+
selectedValue: null | string, // the selected value(s) inside a <select>, or null outside <select>
66+
};
67+
68+
function createFormatContext(
69+
namespace: NamespaceFlag,
70+
selectedValue: null | string,
71+
): FormatContext {
72+
return {
73+
namespace,
74+
selectedValue,
75+
};
76+
}
77+
78+
export function createRootFormatContext(): FormatContext {
79+
return createFormatContext(ROOT_NAMESPACE, null);
80+
}
81+
82+
export function getChildFormatContext(
83+
parentContext: FormatContext,
84+
type: string,
85+
props: Object,
86+
): FormatContext {
87+
switch (type) {
88+
case 'select':
89+
return createFormatContext(
90+
parentContext.namespace,
91+
props.value != null ? props.value : props.defaultValue,
92+
);
93+
case 'svg':
94+
return createFormatContext(SVG_NAMESPACE, null);
95+
case 'math':
96+
return createFormatContext(MATHML_NAMESPACE, null);
97+
case 'foreignObject':
98+
return createFormatContext(HTML_NAMESPACE, null);
99+
}
100+
return parentContext;
101+
}
102+
53103
// This object is used to lazily reuse the ID of the first generated node, or assign one.
54104
// We can't assign an ID up front because the node we're attaching it to might already
55105
// have one. So we need to lazily use that if it's available.

packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,33 @@ export function createResponseState(): ResponseState {
6666
};
6767
}
6868

69+
// isInAParentText
70+
export type FormatContext = boolean;
71+
72+
export function createRootFormatContext(): FormatContext {
73+
return false;
74+
}
75+
76+
export function getChildFormatContext(
77+
parentContext: FormatContext,
78+
type: string,
79+
props: Object,
80+
): FormatContext {
81+
const prevIsInAParentText = parentContext;
82+
const isInAParentText =
83+
type === 'AndroidTextInput' || // Android
84+
type === 'RCTMultilineTextInputView' || // iOS
85+
type === 'RCTSinglelineTextInputView' || // iOS
86+
type === 'RCTText' ||
87+
type === 'RCTVirtualText';
88+
89+
if (prevIsInAParentText !== isInAParentText) {
90+
return isInAParentText;
91+
} else {
92+
return parentContext;
93+
}
94+
}
95+
6996
// This object is used to lazily reuse the ID of the first generated node, or assign one.
7097
// This is very specific to DOM where we can't assign an ID to.
7198
export type SuspenseBoundaryID = number;

packages/react-noop-renderer/src/ReactNoopServer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ const ReactNoopServer = ReactFizzServer({
8282
return {state: 'pending', children: []};
8383
},
8484

85+
getChildFormatContext(): null {
86+
return null;
87+
},
88+
8589
pushTextInstance(target: Array<Uint8Array>, text: string): void {
8690
const textInstance: TextInstance = {
8791
text,
@@ -236,6 +240,7 @@ function render(children: React$Element<any>, options?: Options): Destination {
236240
children,
237241
destination,
238242
null,
243+
null,
239244
options ? options.progressiveChunkSize : undefined,
240245
options ? options.onError : undefined,
241246
options ? options.onCompleteAll : undefined,

0 commit comments

Comments
 (0)