-
Notifications
You must be signed in to change notification settings - Fork 49.3k
[Flight]: Client-side registerServerReference
must not break .bind()
#32565
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
[Flight]: Client-side registerServerReference
must not break .bind()
#32565
Conversation
As a follow-up for facebook#32534 this ensures that calling `registerServerReference` from the client builds with a server reference does not overwrite the `bind` method that was previously defined by the Flight Server. As was already the case with facebook#32534, the compiler must ensure that the client-side `registerServerReference` is called after the server-side `registerServerReference` is called.
Function, | ||
{id: ServerReferenceId, bound: null | Thenable<Array<any>>}, | ||
> = new WeakMap(); | ||
type ServerReferenceMetaData = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The terminology here is a bit unclear but ClientReferenceMetadata
is actually the equivalent of ServerReferenceId
. I.e. excluding the bound arguments. We should really have bound client references too.
Originally I intended to refer to the Metadata + Bound arguments as a "Reference" inside the payload itself because it's overloaded both in terms of the first class object exposed to user space and references to various IDs in the payload format.
This pairing of bound arguments + the id/metadata should probably have its own name. Maybe ServerReferenceClosure?
// 'react-server/src/ReactFlightServerConfig', but that can only be imported | ||
// in a react-server environment. | ||
function isServerReference(reference: Object): boolean { | ||
return reference.$$typeof === Symbol.for('react.server.reference'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't good enough to support environments that have custom server reference first class types (like Meta does and all bundlers ideally should have).
But I doubt brand checking is actually the right strategy anyway.
} | ||
// Expose encoder for use by SSR, as well as a special bind that can be used to | ||
// keep server capabilities. | ||
if (usedWithSSR) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$$FORM_ACTION
and $$IS_SIGNATURE_EQUAL
should never be included in a non-SSR build and we definitely don't want to include the extra code in browser builds sent to clients.
In fact, we really need to build a separate version of the Flight Clients for react-server
and non-react-server
environments because these should really never be in any react-server
environment.
We cheat in other places like:
This is going to get us in trouble eventually.
registerBoundServerReference(reference, id, null, encodeFormAction); | ||
const bound = | ||
isServerReference(reference) && reference.$$bound | ||
? Promise.resolve(reference.$$bound) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't make these assumptions about the structure of a Server Reference here. It's up to the bundler.
In fact, these should not even be expandos. I've been meaning to move all the fields on Server/Client References onto WeakMaps since they're internal state owned by the server and should be opaque to the surrounding environment - including Flight's Client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if it is bound already then it's used incorrectly and should be an error (if it was even possible to check).
Because registerServerReference
should always be called on the original function, not the closure. It represents the raw function and not the closure.
I.e. to create a bound function you need to pass the original function to both both registerServerReference
s before binding it.
ReactFlightClient.registerServerReference(ReactFlightServer.registerServerReferenace(fn, ...), ...).bind(...)
function defineBind<T: Function>(reference: T) { | ||
// TODO: Instead of checking if it's a server reference, we could also use | ||
// `reference.hasOwnProperty('bind')`. | ||
const originalBind = isServerReference(reference) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't even have to check. We can just assume that it has a bind
since it's a function. Otherwise something has gone terribly wrong and if not we probably shouldn't add one.
The principle here is this should be opaque to the environment and as long as we just call through it should work as usual.
In fact, you should really be able to have the same function in multiple server environments too which would be possible if we moved the expandos to WeakMaps.
So this could be bound for the Flight Client, Flight Server ESM and Flight Webpack all at once.
? reference.bind | ||
: FunctionBind; | ||
|
||
function bind(): Function { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't use a closure. You should be able to have referenceA.bind === referenceB.bind
and eventually this should just go on the Function.prototype
. Conceptually it's on the Function.prototype
and the more native it moves into bundlers and then in the browser it will be.
It's also important semantically because you should be getting all information about the reference from this
and not anything from the closure. Native functions aren't bound to anything.
[diff facebook/react@0ca3deeb...6aa8254b](facebook/react@0ca3dee...6aa8254) <details> <summary>React upstream changes</summary> - facebook/react#32465 - facebook/react#32540 - facebook/react#32565 </details>
This is a follow-up for #32534 to ensure that calling
registerServerReference
from the client builds with a server reference does not overwrite thebind
method that was previously defined by the Flight Server.As was already the case with #32534, the compiler must ensure that the client-side
registerServerReference
is called after the server-sideregisterServerReference
is called.