Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store unique ID counter on SSR request object and Add Prefix for separate renders #18576

Merged
merged 1 commit into from
May 8, 2020

Conversation

lunaruan
Copy link
Contributor

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.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Apr 11, 2020
@codesandbox-ci
Copy link

codesandbox-ci bot commented Apr 11, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 1134a11:

Sandbox Source
lucid-moser-ob01b Configuration

@sizebot
Copy link

sizebot commented Apr 13, 2020

Details of bundled changes.

Comparing: 53ce0c3...1134a11

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.profiling.min.js 0.0% -0.0% 128.78 KB 128.78 KB 40.29 KB 40.29 KB NODE_PROFILING
ReactDOM-dev.js 0.0% -0.0% 1012.65 KB 1012.65 KB 225.68 KB 225.68 KB FB_WWW_DEV
ReactDOMServer-dev.js -10.5% -10.8% 160.27 KB 143.39 KB 40.83 KB 36.42 KB FB_WWW_DEV
ReactDOM-prod.js 0.0% -0.0% 420.38 KB 420.38 KB 73.8 KB 73.8 KB FB_WWW_PROD
ReactDOMServer-prod.js -10.5% -13.4% 52.03 KB 46.54 KB 12.53 KB 10.86 KB FB_WWW_PROD
react-dom-unstable-fizz.node.production.min.js 0.0% -0.1% 1.17 KB 1.17 KB 667 B 666 B NODE_PROD
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 5.26 KB 5.26 KB 1.77 KB 1.78 KB UMD_DEV
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 4.77 KB 4.77 KB 1.67 KB 1.67 KB NODE_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 13.12 KB 13.12 KB 4.81 KB 4.81 KB NODE_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% -0.2% 1.02 KB 1.02 KB 617 B 616 B NODE_PROD
react-dom-server.node.development.js -10.8% -10.7% 153.88 KB 137.28 KB 40.75 KB 36.38 KB NODE_DEV
react-dom-server.node.production.min.js -13.1% -12.6% 23.7 KB 20.61 KB 8.74 KB 7.64 KB NODE_PROD
react-dom.development.js 0.0% -0.0% 940.77 KB 940.77 KB 205.88 KB 205.88 KB UMD_DEV
react-dom-server.browser.development.js -11.6% -11.4% 162.17 KB 143.38 KB 41.27 KB 36.58 KB UMD_DEV
react-dom.production.min.js 0.0% -0.0% 124.57 KB 124.57 KB 39.93 KB 39.93 KB UMD_PROD
ReactDOMForked-dev.js 0.0% -0.0% 1007.94 KB 1007.94 KB 224.31 KB 224.3 KB FB_WWW_DEV
react-dom-server.browser.production.min.js -13.4% -12.9% 23.4 KB 20.27 KB 8.61 KB 7.5 KB UMD_PROD
react-dom.development.js 0.0% -0.0% 895.68 KB 895.68 KB 203.35 KB 203.35 KB NODE_DEV
ReactDOMForked-profiling.js 0.0% -0.0% 432.06 KB 432.06 KB 75.7 KB 75.7 KB FB_WWW_PROFILING
react-dom-server.browser.development.js -10.9% -10.8% 152.66 KB 136.01 KB 40.49 KB 36.13 KB NODE_DEV
react-dom.production.min.js 0.0% -0.0% 124.77 KB 124.77 KB 39.02 KB 39.02 KB NODE_PROD
react-dom-server.browser.production.min.js -13.3% -12.8% 23.29 KB 20.19 KB 8.59 KB 7.49 KB NODE_PROD

Size changes (experimental)

Generated by 🚫 dangerJS against 1134a11

@sizebot
Copy link

sizebot commented Apr 13, 2020

Details of bundled changes.

Comparing: 53ce0c3...1134a11

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom-test-utils.development.js 0.0% -0.0% 75.22 KB 75.22 KB 20.15 KB 20.15 KB UMD_DEV
ReactDOMTesting-profiling.js 0.0% -0.0% 390.12 KB 390.12 KB 71.12 KB 71.11 KB FB_WWW_PROFILING
react-dom-server.node.production.min.js -13.3% -12.7% 23.24 KB 20.15 KB 8.66 KB 7.56 KB NODE_PROD
react-dom.profiling.min.js 0.0% -0.0% 123.75 KB 123.75 KB 38.77 KB 38.77 KB NODE_PROFILING
ReactDOMServer-dev.js -10.3% -10.6% 163.77 KB 146.84 KB 41.74 KB 37.3 KB FB_WWW_DEV
ReactDOMServer-prod.js -10.4% -13.1% 52.92 KB 47.4 KB 12.72 KB 11.05 KB FB_WWW_PROD
ReactDOMForked-dev.js 0.0% -0.0% 1.01 MB 1.01 MB 229.96 KB 229.96 KB FB_WWW_DEV
ReactDOMForked-prod.js 0.0% -0.0% 433.18 KB 433.18 KB 75.98 KB 75.98 KB FB_WWW_PROD
react-dom.development.js 0.0% -0.0% 906.89 KB 906.89 KB 199.41 KB 199.41 KB UMD_DEV
react-dom.profiling.min.js 0.0% -0.0% 123.5 KB 123.5 KB 39.61 KB 39.61 KB UMD_PROFILING
ReactDOMTesting-dev.js 0.0% -0.0% 934.57 KB 934.57 KB 208.48 KB 208.48 KB FB_WWW_DEV
ReactDOMTesting-prod.js 0.0% -0.0% 390.12 KB 390.12 KB 71.12 KB 71.11 KB FB_WWW_PROD
react-dom-server.node.development.js -11.3% -11.1% 146.93 KB 130.34 KB 38.95 KB 34.63 KB NODE_DEV
ReactDOM-dev.js 0.0% -0.0% 1.01 MB 1.01 MB 231.35 KB 231.34 KB FB_WWW_DEV
ReactDOM-profiling.js 0.0% -0.0% 442.8 KB 442.8 KB 77.67 KB 77.67 KB FB_WWW_PROFILING
react-dom-test-utils.production.min.js 0.0% 0.0% 13.1 KB 13.1 KB 4.8 KB 4.8 KB NODE_PROD
react-dom-server.browser.development.js -12.1% -11.8% 154.86 KB 136.07 KB 39.47 KB 34.81 KB UMD_DEV
react-dom-server.browser.production.min.js -13.6% -13.0% 22.94 KB 19.81 KB 8.52 KB 7.42 KB UMD_PROD
react-dom-server.browser.development.js -11.4% -11.2% 145.71 KB 129.07 KB 38.7 KB 34.38 KB NODE_DEV
react-dom-server.browser.production.min.js -13.6% -12.9% 22.83 KB 19.73 KB 8.5 KB 7.4 KB NODE_PROD
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 5.25 KB 5.25 KB 1.76 KB 1.77 KB UMD_DEV

Size changes (stable)

Generated by 🚫 dangerJS against 1134a11

let serverId: number = 0;
export function makeServerId(): OpaqueIDType {
return 'R:' + (serverId++).toString(36);
export function makeServerId(prefix: ?string, serverId: number): OpaqueIDType {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is prefix optional? We might as well always pass a string from the outside.

Copy link
Contributor Author

@lunaruan lunaruan Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC we want to minimize the number of bytes we send, so we don't want to append a prefix unless necessary (ie for apps that render multiple subtrees on the server and then stitch them together).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flow types suggest "prefix" is optional but "serverId" is required. Is this the intent?

Putting an optional param before a required param seems wrong.

return new ReactMarkupReadableStream(element, false);
export function renderToNodeStream(
element,
options: PartialRendererOptions | void,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be a public API? I suppose people won't know to pass this. Can it be autogenerated or something?

Copy link
Contributor Author

@lunaruan lunaruan Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We won't know when to generate the prefix because we don't want to prepend one unless necessary on the server. Do you have a suggestion on how to autogenerate the prefix

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional. You typically don't need to pass it. You only need it if you're stitching together multiple React SSR in one document.

The Flow type should be options?: PartialRendererOptions. The argument itself is optional but if provided should be an options object. You had that on the other one.

@lunaruan lunaruan force-pushed the opaque_id_server_renderer branch 3 times, most recently from e8f6c8a to 0477f03 Compare April 16, 2020 00:25
@gaearon
Copy link
Collaborator

gaearon commented Apr 17, 2020

I'm not sure I care that much about the "stitching together" use case. Is it legit? I guess we use it.

I'm more concerned about clashes on the client. E.g. two React apps but only one of them uses server rendering. But their IDs clash. Do we have a plan for this?

return new ReactMarkupReadableStream(element, false);
export function renderToNodeStream(
element,
options: PartialRendererOptions | void,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional. You typically don't need to pass it. You only need it if you're stitching together multiple React SSR in one document.

The Flow type should be options?: PartialRendererOptions. The argument itself is optional but if provided should be an options object. You had that on the other one.

@@ -78,6 +82,10 @@ import {validateProperties as validateARIAProperties} from '../shared/ReactDOMIn
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';

export type PartialRendererOptions = {
prefix?: string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which prefix? Maybe call this something to correlate it with the API. E.g. identifierPrefix.

function useOpaqueIdentifier(): OpaqueIDType {
return makeServerId();
return makeServerId(uniqueIDPrefix, currentUniqueID++);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't import ReactDOMHostConfig in the server renderer. That's an implementation detail of the client. Just move that function in here in the partial renderer. Then remove makeServerId from all the other host configs.

The partial renderer doesn't have a host config since it's DOM specific but Fizz will have a host config so we can have server renderers for different environments but it'll be a different one than the client uses.

@@ -838,6 +858,10 @@ class ReactDOMServerRenderer {

const prevThreadID = currentThreadID;
setCurrentThreadID(this.threadID);
const prevUniqueID = currentUniqueID;
setCurrentUniqueID(this.uniqueID);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You pass this value and increment it but you don't actually ever update this.uniqueID. For the synchronous renderToString this won't show up (except maybe for nested calls which we have a open issue about). However for two streaming renders that read multiple times, it'll reset to zero between each read.

You might want to write a unit test that catches this as since it could be pretty bad if we regress on it.

As a solution I'd just pass this instead. setCurrentRenderer(this) and read both the uniqueID, threadID and prefix from the renderer directly.

@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Apr 22, 2020

@gaearon The plan for two React apps on the client is to use some kind of global on the client to coordinate this. Unlike the server, we actually have an opportunity to automatically detect that. But let's do it once it comes up?

@sebmarkbage
Copy link
Collaborator

E.g. two React apps but only one of them uses server rendering. But their IDs clash. Do we have a plan for this?

To be clear server IDs use a different prefix (uppercase) than the client (lowercase) so that they never collide. The more likely clash is just two React versions on the same document. They'll immediately clash from the first ID.

@sebmarkbage
Copy link
Collaborator

The client could also use a time or random based prefix. It's not as sensitive to long IDs as the server.

@lunaruan lunaruan force-pushed the opaque_id_server_renderer branch 3 times, most recently from 3eaf00f to a269eb7 Compare April 28, 2020 05:40
@@ -79,6 +79,10 @@ import {validateProperties as validateARIAProperties} from '../shared/ReactDOMIn
import {validateProperties as validateInputProperties} from '../shared/ReactDOMNullInputValuePropHook';
import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook';

export type PartialRendererOptions = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give this type a name that you'd expect the external API to refer to because it's now part of the public API. Flow/TS will likely just copy this name upstream so lets name it what it should have.

ServerOptions?


export function setCurrentThreadID(threadID: ThreadID) {
currentThreadID = threadID;
export let currentPartialRenderer = {};
Copy link
Collaborator

@sebmarkbage sebmarkbage May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type isn't valid. I think it just accidental works since it's an "open type".

Set this to null so it fails if something goes wrong. Let's fool Flow though so you don't need null checks. We want it to fail at runtime "for free".

export let currentPartialRenderer: PartialRenderer = (null: any);

@lunaruan lunaruan force-pushed the opaque_id_server_renderer branch from a269eb7 to 1134a11 Compare May 8, 2020 02:54
@lunaruan lunaruan merged commit df14b5b into facebook:master May 8, 2020
@gaearon gaearon mentioned this pull request Mar 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants