We have moved away from HostConfig since the name does not fully
describe the configs we customize per runtime like FlightClient,
FlightServer, Fizz, and Fiber. This commit generalizes $$$hostconfig to
$$$config
Part of https://github.com/facebook/react/pull/26571
Implements wiring for Flight to have it's own "HostConfig" from Fizz.
Historically the ServerFormatConfigs were supposed to be generic enough
to be used by Fizz and Flight. However with the addition of features
like Float the configs have evolved to be more specific to the renderer.
We may want to get back to a place where there is a pure FormatConfig
which can be shared but for now we are embracing the fact that these
runtimes need very different things and DCE cannot adequately remove the
unused stuff for Fizz when pulling this dep into Flight so we are going
to fork the configs and just maintain separate ones.
At first the Flight config will be almost empty but once Float support
in Flight lands it will have a more complex implementation
Additionally this commit normalizes the component files which make up
FlightServerConfig and FlightClientConfig. Now each file that
participates starts with ReactFlightServerConfig... and
ReactFlightClientConfig...
## Summary
With https://github.com/facebook/react/pull/26349 we now serialize
`undefined`. However, deserializing it on the client is currently
indistinguishable from the value missing entirely due to how
`JSON.parse` treats `undefined` return value of reviver functions.
This leads to inconsistent behavior of the `Object.hasOwn` or `in`
operator (used for narrowing in TypeScript). In TypeScript-speak, `{
prop: T | undefined}` will arrive as `{ prop?: T }`.
## How did you test this change?
- Added test that is expected to fail. Though ideally the implementation
of the component would not care whether it's used on the client or
server.
Added an explicit type to all $FlowFixMe suppressions to reduce
over-suppressions of new errors that might be caused on the same lines.
Also removes suppressions that aren't used (e.g. in a `@noflow` file as
they're purely misleading)
Test Plan:
yarn flow-ci
This adds `encodeReply` to the Flight Client and `decodeReply` to the
Flight Server.
Basically, it's a reverse Flight. It serializes values passed from the
client to the server. I call this a "Reply". The tradeoffs and
implementation details are a bit different so it requires its own
implementation but is basically a clone of the Flight Server/Client but
in reverse. Either through callServer or ServerContext.
The goal of this project is to provide the equivalent serialization as
passing props through RSC to client. Except React Elements and
Components and such. So that you can pass a value to the client and back
and it should have the same serialization constraints so when we add
features in one direction we should mostly add it in the other.
Browser support for streaming request bodies are currently very limited
in that only Chrome supports it. So this doesn't produce a
ReadableStream. Instead `encodeReply` produces either a JSON string or
FormData. It uses a JSON string if it's a simple enough payload. For
advanced features it uses FormData. This will also let the browser
stream things like File objects (even though they're not yet supported
since it follows the same rules as the other Flight).
On the server side, you can either consume this by blocking on
generating a FormData object or you can stream in the
`multipart/form-data`. Even if the client isn't streaming data, the
network does. On Node.js busboy seems to be the canonical library for
this, so I exposed a `decodeReplyFromBusboy` in the Node build. However,
if there's ever a web-standard way to stream form data, or if a library
wins in that space we can support it. We can also just build a multipart
parser that takes a ReadableStream built-in.
On the server, server references passed as arguments are loaded from
Node or Webpack just like the client or SSR does. This means that you
can create higher order functions on the client or server. This can be
tokenized when done from a server components but this is a security
implication as it might be tempting to think that these are not fungible
but you can swap one function for another on the client. So you have to
basically treat an incoming argument as insecure, even if it's a
function.
I'm not too happy with the naming parity:
Encode `server.renderToReadableStream` Decode: `client.createFromFetch`
Decode `client.encodeReply` Decode: `server.decodeReply`
This is mainly an implementation details of frameworks but it's annoying
nonetheless. This comes from that `renderToReadableStream` does do some
"rendering" by unwrapping server components etc. The `create` part comes
from the parity with Fizz/Fiber where you `render` on the server and
`create` a root on the client.
Open to bike-shedding this some more.
---------
Co-authored-by: Josh Story <josh.c.story@gmail.com>
## Summary
Adds support for returning `undefined` from Server Components.
Also fixes a bug where rendering an empty fragment would throw the same
error as returning undefined.
## How did you test this change?
- [x] test failed with same error message I got when returning undefined
from Server Components in a Next.js app
- [x] test passes after adding encoding for `undefined`
This is just moving some stuff around and renaming things.
This tuple is opaque to the Flight implementation and we should probably
encode it separately as a single string instead of a model object.
The term "Metadata" isn't the same as when used for ClientReferences so
it's not really the right term anyway.
I also made it optional since a bound function with no arguments bound
is technically different than a raw instance of that function (it's a
clone).
I also renamed the type ReactModel to ReactClientValue. This is the
generic serializable type for something that can pass through the
serializable boundary from server to client. There will be another one
for client to server.
I also filled in missing classes and ensure the serializable sub-types
are explicit. E.g. Array and Thenable.
Prior to #26347, our internal `act` API (not the public API) behaved
differently depending on whether the scope function returned a promise
(i.e. was an async function), for historical reasons that no longer
apply. Now that this is fixed, I've codemodded all async act scopes that
don't contain an await to be sync.
No pressing motivation other than it looks nicer and the codemod was
easy. Might help avoid confusion for new contributors who see async act
scopes with nothing async inside and infer it must be like that for a
reason.
This is not a public API. We only use it for our internal tests, the
ones in this repo. Let's move it to this private package. Practically
speaking this will also let us use async/await in the implementation.
(This only affects our own internal repo; it's not a public API.)
I think most of us agree this is a less confusing name. It's possible
someone will confuse it with `console.log`. If that becomes a problem we
can warn in dev or something.
We support any super type of anything that we can serialize. Meaning
that as long as the Type that's passed through is less precise, it means
that we can encoded it as any subtype and therefore the incoming type
doesn't have to be the subtype in that case. Basically, as long as
you're only passing through an `Iterable<T>` in TypeScript, then you can
pass any `Iterable<T>` and we'll treat it as an array.
For example we support Promises *and* Thenables but both are encoded as
Promises.
We support Arrays and since Arrays are also Iterables, we can support
Iterables.
For @wongmjane
We always look up these references in a map so it doesn't matter what
their value is. It could be a hash for example.
The loaders now encode a single $$id instead of filepath + name.
This changes the react-client-manifest to have a single level. The value
inside the map is still split into module id + export name because
that's what gets looked up in webpack.
The react-ssr-manifest is still two levels because that's a reverse
lookup.
This converts some of our test suite to use the `waitFor` test pattern,
instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of
these changes are automated with jscodeshift, with some slight manual
cleanup in certain cases.
See #26285 for full context.
This splits out the Edge and Node implementations of Flight Client into
their own implementations. The Node implementation now takes a Node
Stream as input.
I removed the bundler config from the Browser variant because you're
never supposed to use that in the browser since it's only for SSR.
Similarly, it's required on the server. This also enables generating a
SSR manifest from the Webpack plugin. This is necessary for SSR so that
you can reverse look up what a client module is called on the server.
I also removed the option to pass a callServer from the server. We might
want to add it back in the future but basically, we don't recommend
calling Server Functions from render for initial render because if that
happened client-side it would be a client-side waterfall. If it's never
called in initial render, then it also shouldn't ever happen during SSR.
This might be considered too restrictive.
~This also compiles the unbundled packages as ESM. This isn't strictly
necessary because we only need access to dynamic import to load the
modules but we don't have any other build options that leave
`import(...)` intact, and seems appropriate that this would also be an
ESM module.~ Went with `import(...)` in CJS instead.
We currently have an awkward set up because the server can be used in
two ways. Either you can have the server code prebundled using Webpack
(what Next.js does in practice) or you can use an unbundled Node.js
server (what the reference implementation does).
The `/client` part of RSC is actually also available on the server when
it's used as a consumer for SSR. This should also be specialized
depending on if that server is Node or Edge and if it's bundled or
unbundled.
Currently we still assume Edge will always be bundled since we don't
have an interceptor for modules there.
I don't think we'll want to support this many combinations of setups for
every bundler but this might be ok for the reference implementation.
This PR doesn't actually change anything yet. It just updates the
plumbing and the entry points that are built and exposed. In follow ups
I'll fork the implementation and add more features.
---------
Co-authored-by: dan <dan.abramov@me.com>
This is the first of a series of PRs, that let you pass functions, by
reference, to the client and back. E.g. through Server Context. It's
like client references but they're opaque on the client and resolved on
the server.
To do this, for security, you must opt-in to exposing these functions to
the client using the `"use server"` directive. The `"use client"`
directive lets you enter the client from the server. The `"use server"`
directive lets you enter the server from the client.
This works by tagging those functions as Server References. We could
potentially expand this to other non-serializable or stateful objects
too like classes.
This only implements server->server CJS imports and server->server ESM
imports. We really should add a loader to the webpack plug-in for
client->server imports too. I'll leave closures as an exercise for
integrators.
You can't "call" a client reference on the server, however, you can
"call" a server reference on the client. This invokes a callback on the
Flight client options called `callServer`. This lets a router implement
calling back to the server. Effectively creating an RPC. This is using
JSON for serializing those arguments but more utils coming from
client->server serialization.
We currently abuse the browser builds for Web streams derived
environments. We already have a special build for Bun but we should also
have one for [other "edge"
runtimes](https://runtime-keys.proposal.wintercg.org/) so that we can
maximally take advantage of the APIs that exist on each platform.
In practice, we currently check for a global property called
`AsyncLocalStorage` in the server browser builds which we shouldn't
really do since browsers likely won't ever have it. Additionally, this
should probably move to an import which we can't add to actual browser
builds where that will be an invalid import. So it has to be a separate
build. That's not done yet in this PR but Vercel will follow
Cloudflare's lead here.
The `deno` key still points to the browser build since there's no
AsyncLocalStorage there but it could use this same or a custom build if
support is added.
The "dom" configuration is actually the node specific configuration. It
just happened to be that this was the mainline variant before so it was
implied but with so many variants, this is less obvious now.
The "bun" configuration is specifically for "bun". There's no "native"
renderer for "bun" yet.
This lets you pass Promises from server components to client components
and `use()` them there.
We still don't support Promises as children on the client, so we need to
support both. This will be a lot simpler when we remove the need to
encode children as lazy since we don't need the lazy encoding anymore
then.
I noticed that this test failed because we don't synchronously resolve
instrumented Promises if they're lazy. The second fix calls `.then()`
early to ensure that this lazy initialization can happen eagerly. ~It
felt silly to do this with an empty function or something, so I just did
the attachment of ping listeners early here. It's also a little silly
since they will ping the currently running render for no reason if it's
synchronously available.~ EDIT: That didn't work because a ping might
interrupt the current render. Probably need a bigger refactor.
We could add another extension but we've already taken a lot of
liberties with the Promise protocol. At least this is one that doesn't
need extension of the protocol as much. Any sub-class of promises could
do this.
This is just shifting around some encoding strategies for Flight in
preparation for more types.
```
S1:"react.suspense"
J2:["$", "$1", {children: "@3"}]
J3:"Hello"
```
```
1:"$Sreact.suspense"
2:["$", "$1", {children: "$L3"}]
3:"Hello"
```
The old version of prettier we were using didn't support the Flow syntax
to access properties in a type using `SomeType['prop']`. This updates
`prettier` and `rollup-plugin-prettier` to the latest versions.
I added the prettier config `arrowParens: "avoid"` to reduce the diff
size as the default has changed in Prettier 2.0. The largest amount of
changes comes from function expressions now having a space. This doesn't
have an option to preserve the old behavior, so we have to update this.
This renames Module References to Client References, since they are in
the server->client direction.
I also changed the Proxies exposed from the `node-register` loader to
provide better error messages. Ideally, some of this should be
replicated in the ESM loader too but neither are the source of truth.
We'll replicate this in the static form in the Next.js loaders. cc
@huozhi @shuding
- All references are now functions so that when you call them on the
server, we can yield a better error message.
- References that are themselves already referring to an export name are
now proxies that error when you dot into them.
- `use(...)` can now be used on a client reference to unwrap it server
side and then pass a reference to the awaited value.
This enables the "exact_empty_objects" setting for Flow which makes
empty objects exact instead of building up the type as properties are
added in code below. This is in preparation to Flow 191 which makes this
the default and removes the config.
More about the change in the Flow blog
[here](https://medium.com/flow-type/improved-handling-of-the-empty-object-in-flow-ead91887e40c).
This setting is an incremental path to the next Flow version enforcing
type annotations on most functions (except some inline callbacks).
Used
```
node_modules/.bin/flow codemod annotate-functions-and-classes --write .
```
to add a majority of the types with some hand cleanup when for large
inferred objects that should just be `Fiber` or weird constructs
including `any`.
Suppressed the remaining issues.
Builds on #25918
Hermes parser is the preferred parser for Flow code going forward. We
need to upgrade to this parser to support new Flow syntax like function
`this` context type annotations or `ObjectType['prop']` syntax.
Unfortunately, there's quite a few upgrades here to make it work somehow
(dependencies between the changes)
- ~Upgrade `eslint` to `8.*`~ reverted this as the React eslint plugin
tests depend on the older version and there's a [yarn
bug](https://github.com/yarnpkg/yarn/issues/6285) that prevents
`devDependencies` and `peerDependencies` to different versions.
- Remove `eslint-config-fbjs` preset dependency and inline the rules,
imho this makes it a lot clearer what the rules are.
- Remove the turned off `jsx-a11y/*` rules and it's dependency instead
of inlining those from the `fbjs` config.
- Update parser and dependency from `babel-eslint` to `hermes-eslint`.
- `ft-flow/no-unused-expressions` rule replaces `no-unused-expressions`
which now allows standalone type asserts, e.g. `(foo: number);`
- Bunch of globals added to the eslint config
- Disabled `no-redeclare`, seems like the eslint upgrade started making
this more precise and warn against re-defined globals like
`__EXPERIMENTAL__` (in rollup scripts) or `fetch` (when importing fetch
from node-fetch).
- Minor lint fixes like duplicate keys in objects.
* Facebook -> Meta in copyright
rg --files | xargs sed -i 's#Copyright (c) Facebook, Inc. and its affiliates.#Copyright (c) Meta Platforms, Inc. and affiliates.#g'
* Manual tweaks
* Print built-in specific error message for toJSON
This is a better message for Date.
Also, format the message to highlight the affected prop.
* Describe error messages using JSX elements in DEV
We don't have access to the grand parent objects on the stack so we stash
them on weakmaps so we can access them while printing error messages.
Might be a bit slow.
* Capitalize Server/Client Component
* Special case errror messages for children of host components
These are likely meant to be text content if they're not a supported object.
* Update error messages
Similar to Fizz, Flight now supports a return value from the user provided onError option. If a value is returned from onError it will be serialized and provided to the client.
The digest is stashed on the constructed Error on the client as .digest
* [Flight] Move from suspensey readRoot() to use(thenable)
* Update noop tests
These are no longer sync so they need some more significant updating.
Some of these tests are written in a non-idiomatic form too which is not
great.
* Update Relay tests
I kept these as sync for now and just assume a sync Promise.
* Updated the main tests
* Gate tests
* We need to cast through any because Thenable doesn't support unknown strings
* [Flight] Align Chunks with Thenable used with experimental_use
Use the field names used by the Thenable data structure passed to use().
These are considered public in this model.
This adds another field since we use a separate field name for "reason".
* Implement Thenable Protocol on Chunks
This doesn't just ping but resolves/rejects with the value.
* Subclass Promises
* Pass key through JSON parsing
* Wait for preloadModules before resolving module chunks
* Initialize lazy resolved values before reading the result
* Block a model from initializing if its direct dependencies are pending
If a module is blocked, then we can't complete initializing a model.
However, we can still let it parse, and then fill in the missing pieces
later.
We need to block it from resolving until all dependencies have filled in
which we can do with a ref count.
* Treat blocked modules or models as a special status
We currently loop over all chunks at the end to error them if they're
still pending. We shouldn't do this if they're pending because they're
blocked on an external resource like a module because the module might not
resolve before the Flight connection closes and that's not an error.
In an alternative solution I had a set that tracked pending chunks and
removed one at a time. While the loop at the end is faster it's more
work as we go.
I figured the extra status might also help debugging.
For modules we can probably assume no forward references, and the first
async module we can just use the promise as the chunk.
So we could probably get away with this only on models that are blocked by
modules.
* Fix error handling when the Flight client itself errors
* Serialize references to errors in the error priority queue
It doesn't make sense to emit references to future values at higher pri
than the value that they're referencing.
This ensures that we don't emit hard forward references to values that
don't yet exist.
* Implements useId hook for Flight server.
The approach for ids for Flight is different from Fizz/Client where there is a need for determinancy. Flight rendered elements will not be rendered on the client and as such the ids generated in a request only need to be unique. However since FLight does support refetching subtrees it is possible a client will need to patch up a part of the tree rather than replacing the entire thing so it is not safe to use a simple incrementing counter. To solve for this we allow the caller to specify a prefix. On an initial fetch it is likely this will be empty but on refetches or subtrees we expect to have a client `useId` provide the prefix since it will guaranteed be unique for that subtree and thus for the entire tree. It is also possible that we will automatically provide prefixes based on a client/Fizz useId on refetches
in addition to the core change I also modified the structure of options for renderToReadableStream where `onError`, `context`, and the new `identifierPrefix` are properties of an Options object argument to avoid the clumsiness of a growing list of optional function arguments.
* defend against useId call outside of rendering
* switch to S from F for Server Component ids
* default to empty string identifier prefix
* Add a test demonstrating that there is no warning when double rendering on the client a server component that used useId
* lints and gates
On the server we have a similar translation map from the file path that the
loader uses to the refer to the original module and to the bundled module ID.
The Flight server is optimized to emit the smallest format for the client.
However during SSR, the same client component might go by a different
module ID since it's a different bundle than the client bundle.
This provides an option to add a translation map from client ID to SSR ID
when reading the Flight stream.
Ideally we should have a special SSR Flight Client that takes this option
but for now we only have one Client for both.
* [Flight] add support for Lazy components in Flight server
Lazy components suspend until resolved just like in Fizz. Add tests to confirm Lazy works with Shared Components and Client Component references.
* Support Lazy elements
React.Lazy can now return an element instead of a Component. This commit implements support for Lazy elements when server rendering.
* add lazy initialization to resolveModelToJson
adding lazying initialization toResolveModelToJson means we use attemptResolveElement's full logic on whatever the resolved type ends up being. This better aligns handling of misued Lazy types like a lazy element being used as a Component or a lazy Component being used as an element.
* Flight side of server context
* 1 more test
* rm unused function
* flow+prettier
* flow again =)
* duplicate ReactServerContext across packages
* store default value when lazily initializing server context
* .
* better comment
* derp... missing import
* rm optional chaining
* missed feature flag
* React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ??
* add warning if non ServerContext passed into useServerContext
* pass context in as array of arrays
* make importServerContext nott pollute the global context state
* merge main
* remove useServerContext
* dont rely on object getters in ReactServerContext and disallow JSX
* add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry
* gate test case as experimental
* feedback
* remove unions
* Lint
* fix oopsies (tests/lint/mismatching arguments/signatures
* lint again
* replace-fork
* remove extraneous change
* rebase
* 1 more test
* rm unused function
* flow+prettier
* flow again =)
* duplicate ReactServerContext across packages
* store default value when lazily initializing server context
* .
* better comment
* derp... missing import
* rm optional chaining
* missed feature flag
* React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ??
* add warning if non ServerContext passed into useServerContext
* pass context in as array of arrays
* make importServerContext nott pollute the global context state
* merge main
* remove useServerContext
* dont rely on object getters in ReactServerContext and disallow JSX
* add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry
* gate test case as experimental
* feedback
* remove unions
* Lint
* fix oopsies (tests/lint/mismatching arguments/signatures
* lint again
* replace-fork
* remove extraneous change
* rebase
* reinline
* rebase
* add back changes lost due to rebase being hard
* emit chunk for provider
* remove case for React provider type
* update type for SomeChunk
* enable flag with experimental
* add missing types
* fix flow type
* missing type
* t: any
* revert extraneous type change
* better type
* better type
* feedback
* change import to type import
* test?
* test?
* remove react-dom
* remove react-native-renderer from react-server-native-relay/package.json
* gate change in FiberNewContext, getComponentNameFromType, use switch statement in FlightServer
* getComponentNameFromTpe: server context type gated and use displayName if available
* fallthrough
* lint....
* POP
* lint