Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit df14b5b

Browse files
authored
add new IDs for each each server renderer instance and prefixes to distinguish between each server render (facebook#18576)
There is a worry that `useOpaqueIdentifier` might run out of unique IDs if running for long enough. This PR moves the unique ID counter so it's generated per server renderer object instead. For people who render different subtrees, this PR adds a prefix option to `renderToString`, `renderToStaticMarkup`, `renderToNodeStream`, and `renderToStaticNodeStream` so identifiers can be differentiated for each individual subtree.
1 parent 69e732a commit df14b5b

12 files changed

+208
-51
lines changed

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,10 +495,6 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
495495
throw new Error('Not yet implemented');
496496
}
497497

498-
export function makeServerId(): OpaqueIDType {
499-
throw new Error('Not yet implemented');
500-
}
501-
502498
export function beforeActiveInstanceBlur() {
503499
// noop
504500
}

packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,162 @@ describe('ReactDOMServerHooks', () => {
10321032
);
10331033
});
10341034

1035+
it('useOpaqueIdentifier identifierPrefix works for server renderer and does not clash', async () => {
1036+
function ChildTwo({id}) {
1037+
return <div id={id}>Child Three</div>;
1038+
}
1039+
function App() {
1040+
const id = useOpaqueIdentifier();
1041+
const idTwo = useOpaqueIdentifier();
1042+
1043+
return (
1044+
<div>
1045+
<div aria-labelledby={id}>Chid One</div>
1046+
<ChildTwo id={id} />
1047+
<div aria-labelledby={idTwo}>Child Three</div>
1048+
<div id={idTwo}>Child Four</div>
1049+
</div>
1050+
);
1051+
}
1052+
1053+
const containerOne = document.createElement('div');
1054+
document.body.append(containerOne);
1055+
1056+
containerOne.innerHTML = ReactDOMServer.renderToString(<App />, {
1057+
identifierPrefix: 'one',
1058+
});
1059+
1060+
const containerTwo = document.createElement('div');
1061+
document.body.append(containerTwo);
1062+
1063+
containerTwo.innerHTML = ReactDOMServer.renderToString(<App />, {
1064+
identifierPrefix: 'two',
1065+
});
1066+
1067+
expect(document.body.children.length).toEqual(2);
1068+
const childOne = document.body.children[0];
1069+
const childTwo = document.body.children[1];
1070+
1071+
expect(
1072+
childOne.children[0].children[0].getAttribute('aria-labelledby'),
1073+
).toEqual(childOne.children[0].children[1].getAttribute('id'));
1074+
expect(
1075+
childOne.children[0].children[2].getAttribute('aria-labelledby'),
1076+
).toEqual(childOne.children[0].children[3].getAttribute('id'));
1077+
1078+
expect(
1079+
childOne.children[0].children[0].getAttribute('aria-labelledby'),
1080+
).not.toEqual(
1081+
childOne.children[0].children[2].getAttribute('aria-labelledby'),
1082+
);
1083+
1084+
expect(
1085+
childOne.children[0].children[0]
1086+
.getAttribute('aria-labelledby')
1087+
.startsWith('one'),
1088+
).toBe(true);
1089+
expect(
1090+
childOne.children[0].children[2]
1091+
.getAttribute('aria-labelledby')
1092+
.includes('one'),
1093+
).toBe(true);
1094+
1095+
expect(
1096+
childTwo.children[0].children[0].getAttribute('aria-labelledby'),
1097+
).toEqual(childTwo.children[0].children[1].getAttribute('id'));
1098+
expect(
1099+
childTwo.children[0].children[2].getAttribute('aria-labelledby'),
1100+
).toEqual(childTwo.children[0].children[3].getAttribute('id'));
1101+
1102+
expect(
1103+
childTwo.children[0].children[0].getAttribute('aria-labelledby'),
1104+
).not.toEqual(
1105+
childTwo.children[0].children[2].getAttribute('aria-labelledby'),
1106+
);
1107+
1108+
expect(
1109+
childTwo.children[0].children[0]
1110+
.getAttribute('aria-labelledby')
1111+
.startsWith('two'),
1112+
).toBe(true);
1113+
expect(
1114+
childTwo.children[0].children[2]
1115+
.getAttribute('aria-labelledby')
1116+
.startsWith('two'),
1117+
).toBe(true);
1118+
});
1119+
1120+
it('useOpaqueIdentifier identifierPrefix works for multiple reads on a streaming server renderer', async () => {
1121+
function ChildTwo() {
1122+
const id = useOpaqueIdentifier();
1123+
1124+
return <div id={id}>Child Two</div>;
1125+
}
1126+
1127+
function App() {
1128+
const id = useOpaqueIdentifier();
1129+
1130+
return (
1131+
<>
1132+
<div id={id}>Child One</div>
1133+
<ChildTwo />
1134+
<div aria-labelledby={id}>Aria One</div>
1135+
</>
1136+
);
1137+
}
1138+
1139+
const container = document.createElement('div');
1140+
document.body.append(container);
1141+
1142+
const streamOne = ReactDOMServer.renderToNodeStream(<App />, {
1143+
identifierPrefix: 'one',
1144+
}).setEncoding('utf8');
1145+
const streamTwo = ReactDOMServer.renderToNodeStream(<App />, {
1146+
identifierPrefix: 'two',
1147+
}).setEncoding('utf8');
1148+
1149+
const containerOne = document.createElement('div');
1150+
const containerTwo = document.createElement('div');
1151+
1152+
streamOne._read(10);
1153+
streamTwo._read(10);
1154+
1155+
containerOne.innerHTML = streamOne.read();
1156+
containerTwo.innerHTML = streamTwo.read();
1157+
1158+
expect(containerOne.children[0].getAttribute('id')).not.toEqual(
1159+
containerOne.children[1].getAttribute('id'),
1160+
);
1161+
expect(containerTwo.children[0].getAttribute('id')).not.toEqual(
1162+
containerTwo.children[1].getAttribute('id'),
1163+
);
1164+
expect(containerOne.children[0].getAttribute('id')).not.toEqual(
1165+
containerTwo.children[0].getAttribute('id'),
1166+
);
1167+
expect(
1168+
containerOne.children[0].getAttribute('id').includes('one'),
1169+
).toBe(true);
1170+
expect(
1171+
containerOne.children[1].getAttribute('id').includes('one'),
1172+
).toBe(true);
1173+
expect(
1174+
containerTwo.children[0].getAttribute('id').includes('two'),
1175+
).toBe(true);
1176+
expect(
1177+
containerTwo.children[1].getAttribute('id').includes('two'),
1178+
).toBe(true);
1179+
1180+
expect(containerOne.children[1].getAttribute('id')).not.toEqual(
1181+
containerTwo.children[1].getAttribute('id'),
1182+
);
1183+
expect(containerOne.children[0].getAttribute('id')).toEqual(
1184+
containerOne.children[2].getAttribute('aria-labelledby'),
1185+
);
1186+
expect(containerTwo.children[0].getAttribute('id')).toEqual(
1187+
containerTwo.children[2].getAttribute('aria-labelledby'),
1188+
);
1189+
});
1190+
10351191
it('useOpaqueIdentifier: IDs match when, after hydration, a new component that uses the ID is rendered', async () => {
10361192
let _setShowDiv;
10371193
function App() {

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,11 +1102,6 @@ export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
11021102
};
11031103
}
11041104

1105-
let serverId: number = 0;
1106-
export function makeServerId(): OpaqueIDType {
1107-
return 'R:' + (serverId++).toString(36);
1108-
}
1109-
11101105
export function isOpaqueHydratingObject(value: mixed): boolean {
11111106
return (
11121107
value !== null &&

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,23 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
7+
import type {ServerOptions} from './ReactPartialRenderer';
78

89
import {Readable} from 'stream';
910

1011
import ReactPartialRenderer from './ReactPartialRenderer';
1112

1213
// This is a Readable Node.js stream which wraps the ReactDOMPartialRenderer.
1314
class ReactMarkupReadableStream extends Readable {
14-
constructor(element, makeStaticMarkup) {
15+
constructor(element, makeStaticMarkup, options) {
1516
// Calls the stream.Readable(options) constructor. Consider exposing built-in
1617
// features like highWaterMark in the future.
1718
super({});
18-
this.partialRenderer = new ReactPartialRenderer(element, makeStaticMarkup);
19+
this.partialRenderer = new ReactPartialRenderer(
20+
element,
21+
makeStaticMarkup,
22+
options,
23+
);
1924
}
2025

2126
_destroy(err, callback) {
@@ -36,15 +41,15 @@ class ReactMarkupReadableStream extends Readable {
3641
* server.
3742
* See https://reactjs.org/docs/react-dom-server.html#rendertonodestream
3843
*/
39-
export function renderToNodeStream(element) {
40-
return new ReactMarkupReadableStream(element, false);
44+
export function renderToNodeStream(element, options?: ServerOptions) {
45+
return new ReactMarkupReadableStream(element, false, options);
4146
}
4247

4348
/**
4449
* Similar to renderToNodeStream, except this doesn't create extra DOM attributes
4550
* such as data-react-id that React uses internally.
4651
* See https://reactjs.org/docs/react-dom-server.html#rendertostaticnodestream
4752
*/
48-
export function renderToStaticNodeStream(element) {
49-
return new ReactMarkupReadableStream(element, true);
53+
export function renderToStaticNodeStream(element, options?: ServerOptions) {
54+
return new ReactMarkupReadableStream(element, true, options);
5055
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
import type {ServerOptions} from './ReactPartialRenderer';
89
import ReactPartialRenderer from './ReactPartialRenderer';
910

1011
/**
1112
* Render a ReactElement to its initial HTML. This should only be used on the
1213
* server.
1314
* See https://reactjs.org/docs/react-dom-server.html#rendertostring
1415
*/
15-
export function renderToString(element) {
16-
const renderer = new ReactPartialRenderer(element, false);
16+
export function renderToString(element, options?: ServerOptions) {
17+
const renderer = new ReactPartialRenderer(element, false, options);
1718
try {
1819
const markup = renderer.read(Infinity);
1920
return markup;
@@ -27,8 +28,8 @@ export function renderToString(element) {
2728
* such as data-react-id that React uses internally.
2829
* See https://reactjs.org/docs/react-dom-server.html#rendertostaticmarkup
2930
*/
30-
export function renderToStaticMarkup(element) {
31-
const renderer = new ReactPartialRenderer(element, true);
31+
export function renderToStaticMarkup(element, options?: ServerOptions) {
32+
const renderer = new ReactPartialRenderer(element, true, options);
3233
try {
3334
const markup = renderer.read(Infinity);
3435
return markup;

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ import {
6060
prepareToUseHooks,
6161
finishHooks,
6262
Dispatcher,
63-
currentThreadID,
64-
setCurrentThreadID,
63+
currentPartialRenderer,
64+
setCurrentPartialRenderer,
6565
} from './ReactPartialRendererHooks';
6666
import {
6767
Namespaces,
@@ -79,6 +79,10 @@ import {validateProperties as validateARIAProperties} from '../shared/ReactDOMIn
7979
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
8080
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';
8181

82+
export type ServerOptions = {
83+
identifierPrefix?: string,
84+
};
85+
8286
// Based on reading the React.Children implementation. TODO: type this somewhere?
8387
type ReactNode = string | number | ReactElement;
8488
type FlatReactChildren = Array<null | ReactNode>;
@@ -726,7 +730,14 @@ class ReactDOMServerRenderer {
726730
contextValueStack: Array<any>;
727731
contextProviderStack: ?Array<ReactProvider<any>>; // DEV-only
728732

729-
constructor(children: mixed, makeStaticMarkup: boolean) {
733+
uniqueID: number;
734+
identifierPrefix: string;
735+
736+
constructor(
737+
children: mixed,
738+
makeStaticMarkup: boolean,
739+
options?: ServerOptions,
740+
) {
730741
const flatChildren = flattenTopLevelChildren(children);
731742

732743
const topFrame: Frame = {
@@ -754,6 +765,11 @@ class ReactDOMServerRenderer {
754765
this.contextIndex = -1;
755766
this.contextStack = [];
756767
this.contextValueStack = [];
768+
769+
// useOpaqueIdentifier ID
770+
this.uniqueID = 0;
771+
this.identifierPrefix = (options && options.identifierPrefix) || '';
772+
757773
if (__DEV__) {
758774
this.contextProviderStack = [];
759775
}
@@ -837,8 +853,8 @@ class ReactDOMServerRenderer {
837853
return null;
838854
}
839855

840-
const prevThreadID = currentThreadID;
841-
setCurrentThreadID(this.threadID);
856+
const prevPartialRenderer = currentPartialRenderer;
857+
setCurrentPartialRenderer(this);
842858
const prevDispatcher = ReactCurrentDispatcher.current;
843859
ReactCurrentDispatcher.current = Dispatcher;
844860
try {
@@ -935,7 +951,7 @@ class ReactDOMServerRenderer {
935951
return out[0];
936952
} finally {
937953
ReactCurrentDispatcher.current = prevDispatcher;
938-
setCurrentThreadID(prevThreadID);
954+
setCurrentPartialRenderer(prevPartialRenderer);
939955
}
940956
}
941957

0 commit comments

Comments
 (0)