mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
pr33145
13 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea05b750a5 |
Allow Passing Blob/File/MediaSource/MediaStream to src of <img>, <video> and <audio> (#32828)
Behind the `enableSrcObject` flag. This is revisiting a variant of what was discussed in #11163. Instead of supporting the [`srcObject` property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject) as a separate name, this adds an overload of `src` to allow objects to be passed. The DOM needs to add separate properties for the object forms since you read back but it doesn't make sense for React's write-only API to do that. Similar to how we'll like add an overload for `popoverTarget` instead of calling it `popoverTargetElement` and how `style` accepts an object and it's not `styleObject={{...}}`. There are a number of reason to revisit this. - It's just way more convenient to have this built-in and it makes conceptual sense. We typically support declarative APIs and polyfill them when necessary. - RSC supports Blobs and by having it built-in you don't need a Client Component wrapper to render it where as doing it with effects would require more complex wrappers. By picking Blobs over base64, client-navigations can use the more optimized binary encoding in the RSC protocol. - The timing aspect of coordinating it with Suspensey images and image decoding is a bit tricky to get right because if you set it in an effect it's too late because you've already rendered it. - SSR gets complicated when done in user space because you have to handle both branches. Likely with `useSyncExternalStore`. - By having it built-in we could optimize the payloads shared between RSC payloads embedded in the HTML and data URLs. This does not support objects for `<source src>` nor `<img srcset>`. Those don't really have equivalents in the DOM neither. They're mainly for picking an option when you don't know programmatically. However, for this use case you're really better off picking a variant before generating the blobs. We may support Response objects in the future too as per https://github.com/whatwg/fetch/issues/49 |
||
|
|
1e1e5cd252 |
[Flight] Schedule work in a microtask (#29491)
Stacked on #29551 Flight pings much more often than Fizz because async function components will always take at least a microtask to resolve . Rather than scheduling this work as a new macrotask Flight now schedules pings in a microtask. This allows more microtasks to ping before actually doing a work flush but doesn't force the vm to spin up a new task which is quite common give n the nature of Server Components |
||
|
|
b526a0a419 |
[Flight][Fizz] schedule work async (#29551)
While most builds of Flight and Fizz schedule work in new tasks some do execute work synchronously. While this is necessary for legacy APIs like renderToString for modern APIs there really isn't a great reason to do this synchronously. We could schedule works as microtasks but we actually want to yield so the runtime can run events and other things that will unblock additional work before starting the next work loop. This change updates all non-legacy uses to be async using the best availalble macrotask scheduler. Browser now uses postMessage Bun uses setTimeout because while it also supports setImmediate the scheduling is not as eager as the same API in node the FB build also uses setTimeout This change required a number of changes to tests which were utilizing the sync nature of work in the Browser builds to avoid having to manage timers and tasks. I added a patch to install MessageChannel which is required by the browser builds and made this patched version integrate with the Scheduler mock. This way we can effectively use `act` to flush flight and fizz work similar to how we do this on the client. |
||
|
|
c113503ad1 |
Flush direct streams in Bun (#28837)
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn test --debug --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> The ReadableStreamController for [direct streams](https://bun.sh/docs/api/streams#direct-readablestream) in Bun supports a flush() method to flush all buffered items to its underlying sink. Without manually calling flush(), all buffered items are only flushed to the underlying sink when the stream is closed. This behavior causes the shell rendered against Suspense boundaries never to be flushed to the underlying sink. ## How did you test this change? <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. --> A lot of changes to the test runner will need to be made in order to support the Bun runtime. A separate test was manually run in order to ensure that the changes made are correct. The test works by sanity-checking that the shell rendered against Suspense boundaries are emitted first in the stream. This test was written and run on Bun v1.1.3. ```ts import { Suspense } from "react"; import { renderToReadableStream } from "react-dom/server"; if (!import.meta.resolveSync("react-dom/server").endsWith("server.bun.js")) { throw new Error("react-dom/server is not the correct version:\n " + import.meta.resolveSync("react-dom/server")); } const A = async () => { await new Promise(resolve => setImmediate(resolve)); return <div>hi</div>; }; const B = async () => { return ( <Suspense fallback={<div>loading</div>}> <A /> </Suspense> ); }; const stream = await renderToReadableStream(<B />); let text = ""; let count = 0; for await (const chunk of stream) { text += new TextDecoder().decode(chunk); count++; } if ( text !== `<!--$?--><template id="B:0"></template><div>loading</div><!--/$--><div hidden id="S:0"><div>hi</div></div><script>$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};$RC("B:0","S:0")</script>` ) { throw new Error("unexpected output"); } if (count !== 2) { throw new Error("expected 2 chunks from react ssr stream"); } ``` |
||
|
|
b09e102ff1 |
[Fizz] Prevent uncloned large precomputed chunks without relying on render-time assertions (#28568)
A while back we implemented a heuristic that if a chunk was large it was assumed to be produced by the render and thus was safe to stream which results in transferring the underlying object memory. Later we ran into an issue where a precomputed chunk grew large enough to trigger this hueristic and it started causing renders to fail because once a second render had occurred the precomputed chunk would not have an underlying buffer of bytes to send and these bytes would be omitted from the stream. We implemented a technique to detect large precomputed chunks and we enforced that these always be cloned before writing. Unfortunately our test coverage was not perfect and there has been for a very long time now a usage pattern where if you complete a boundary in one flush and then complete a boundary that has stylehsheet dependencies in another flush you can get a large precomputed chunk that was not being cloned to be sent twice causing streaming errors. I've thought about why we even went with this solution in the first place and I think it was a mistake. It relies on a dev only check to catch paired with potentially version specific order of operations on the streaming side. This is too unreliable. Additionally the low limit of view size for Edge is not used in Node.js but there is not real justification for this. In this change I updated the view size for edge streaming to match Node at 2048 bytes which is still relatively small and we have no data one way or another to preference 512 over this. Then I updated the assertion logic to error anytime a precomputed chunk exceeds the size. This eliminates the need to clone these chunks by just making sure our view size is always larger than the largest precomputed chunk we can possibly write. I'm generally in favor of this for a few reasons. First, we'll always know during testing whether we've violated the limit as long as we exercise each stream config because the precomputed chunks are created in module scope. Second, we can always split up large chunks so making sure the precomptued chunk is smaller than whatever view size we actually desire is relatively trivial. |
||
|
|
2b3d582683 |
useFormState: Hash the component key path for more compact output (#27397)
To support MPA-style form submissions, useFormState sends down a key that represents the identity of the hook on the page. It's based on the key path of the component within the React tree; for deeply nested hooks, this keypath can become very long. We can hash the key to make it shorter. Adds a method called createFastHash to the Stream Config interface. We're not using this for security or obfuscation, only to generate a more compact key without sacrificing too much collision resistance. - In Node.js builds, createFastHash uses the built-in crypto module. - In Bun builds, createFastHash uses Bun.hash. See: https://bun.sh/docs/api/hashing#bun-hash I have not yet implemented createFastHash in the Edge, Browser, or FB (Hermes) stream configs because those environments do not have a built-in hashing function that meets our requirements. (We can't use the web standard `crypto` API because those methods are async, and yielding to the main thread is too costly to be worth it for this particular use case.) We'll likely use a pure JS implementation in those environments; for now, they just return the original string without hashing it. I'll address this in separate PRs. |
||
|
|
d9c333199e |
[Flight] Add Serialization of Typed Arrays / ArrayBuffer / DataView (#26954)
This uses the same mechanism as [large strings](https://github.com/facebook/react/pull/26932) to encode chunks of length based binary data in the RSC payload behind a flag. I introduce a new BinaryChunk type that's specific to each stream and ways to convert into it. That's because we sometimes need all chunks to be Uint8Array for the output, even if the source is another array buffer view, and sometimes we need to clone it before transferring. Each type of typed array is its own row tag. This lets us ensure that the instance is directly in the right format in the cached entry instead of creating a wrapper at each reference. Ideally this is also how Map/Set should work but those are lazy which complicates that approach a bit. We assume both server and client use little-endian for now. If we want to support other modes, we'd convert it to/from little-endian so that the transfer protocol is always little-endian. That way the common clients can be the fastest possible. So far this only implements Server to Client. Still need to implement Client to Server for parity. NOTE: This is the first time we make RSC effectively a binary format. This is not compatible with existing SSR techniques which serialize the stream as unicode in the HTML. To be compatible, those implementations would have to use base64 or something like that. Which is what we'll do when we move this technique to be built-in to Fizz. |
||
|
|
db50164dba |
[Flight] Optimize Large Strings by Not Escaping Them (#26932)
This introduces a Text row (T) which is essentially a string blob and refactors the parsing to now happen at the binary level. ``` RowID + ":" + "T" + ByteLengthInHex + "," + Text ``` Today, we encode all row data in JSON, which conveniently never has newline characters and so we use newline as the line terminator. We can't do that if we pass arbitrary unicode without escaping it. Instead, we pass the byte length (in hexadecimal) in the leading header for this row tag followed by a comma. We could be clever and use fixed or variable-length binary integers for the row id and length but it's not worth the more difficult debuggability so we keep these human readable in text. Before this PR, we used to decode the binary stream into UTF-8 strings before parsing them. This is inefficient because sometimes the slices end up having to be copied so it's better to decode it directly into the format. The follow up to this is also to add support for binary data and then we can't assume the entire payload is UTF-8 anyway. So this refactors the parser to parse the rows in binary and then decode the result into UTF-8. It does add some overhead to decoding on a per row basis though. Since we do this, we need to encode the byte length that we want decode - not the string length. Therefore, this requires clients to receive binary data and why I had to delete the string option. It also means that I had to add a way to get the byteLength from a chunk since they're not always binary. For Web streams it's easy since they're always typed arrays. For Node streams it's trickier so we use the byteLength helper which may not be very efficient. Might be worth eagerly encoding them to UTF8 - perhaps only for this case. |
||
|
|
36e4cbe2e9 |
[Float][Flight] Flight support for Float (#26502)
Stacked on #26557 Supporting Float methods such as ReactDOM.preload() are challenging for flight because it does not have an easy means to convey direct executions in other environments. Because the flight wire format is a JSON-like serialization that is expected to be rendered it currently only describes renderable elements. We need a way to convey a function invocation that gets run in the context of the client environment whether that is Fizz or Fiber. Fiber is somewhat straightforward because the HostDispatcher is always active and we can just have the FlightClient dispatch the serialized directive. Fizz is much more challenging becaue the dispatcher is always scoped but the specific request the dispatch belongs to is not readily available. Environments that support AsyncLocalStorage (or in the future AsyncContext) we will use this to be able to resolve directives in Fizz to the appropriate Request. For other environments directives will be elided. Right now this is pragmatic and non-breaking because all directives are opportunistic and non-critical. If this changes in the future we will need to reconsider how widespread support for async context tracking is. For Flight, if AsyncLocalStorage is available Float methods can be called before and after await points and be expected to work. If AsyncLocalStorage is not available float methods called in the sync phase of a component render will be captured but anything after an await point will be a noop. If a float call is dropped in this manner a DEV warning should help you realize your code may need to be modified. This PR also introduces a way for resources (Fizz) and hints (Flight) to flush even if there is not active task being worked on. This will help when Float methods are called in between async points within a function execution but the task is blocked on the entire function finishing. This PR also introduces deduping of Hints in Flight using the same resource keys used in Fizz. This will help shrink payload sizes when the same hint is attempted to emit over and over again |
||
|
|
afea1d0c53 |
[flow] make Flow suppressions explicit on the error (#26487)
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 |
||
|
|
c49131669b |
Remove unused Flow suppressions (#25977)
These suppressions are no longer required. Generated using: ```sh flow/tool update-suppressions . ``` followed by adding back 1 or 2 suppressions that were only triggered in some configurations. |
||
|
|
2655c9354d |
Fizz Browser: fix precomputed chunk being cleared on Node 18 (#25645)
## Edit Went for another approach after talking with @gnoff. The approach is now: - add a dev-only error when a precomputed chunk is too big to be written - suggest to copy it before passing it to `writeChunk` This PR also includes porting the React Float tests to use the browser build of Fizz so that we can test it out on that environment (which is the one used by next). <!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary Someone reported [a bug](https://github.com/vercel/next.js/issues/42466) in Next.js that pointed to an issue with Node 18 in the streaming renderer when using importing a CSS module where it only returned a malformed bootstraping script only after loading the page once. After investigating a bit, here's what I found: - when using a CSS module in Next, we go into this code path, which writes the aforementioned bootstrapping script https://github.com/facebook/react/blob/5f7ef8c4cbe824ef126a947b7ae0e1c07b143357/packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js#L2443-L2447 - the reason for the malformed script is that `completeBoundaryWithStylesScript1FullBoth` is emptied after the call to `writeChunk` - it gets emptied in `writeChunk` because we stream the chunk directly without copying it in this codepath https://github.com/facebook/react/blob/a438590144d2ad40865b58e0c0e69595fc1aa377/packages/react-server/src/ReactServerStreamConfigBrowser.js#L63 - the reason why it only happens from Node 18 is because the Webstreams APIs are available natively from that version and in their implementation, [`enqueue` transfers the array buffer ownership](https://github.com/nodejs/node/blob/9454ba6138d11e8a4d18b073de25781cad4bd2c8/lib/internal/webstreams/readablestream.js#L2641), thus making it unavailable/empty for subsequent calls. In older Node versions, we don't encounter the bug because we are using a polyfill in Next.js, [which does not implement properly the array buffer transfer behaviour](https://cs.github.com/MattiasBuelens/web-streams-polyfill/blob/d354a7457ca8a24030dbd0a135ee40baed7c774d/src/lib/abstract-ops/ecmascript.ts#L16). I think the proper fix for this is to clone the array buffer before enqueuing it. (we do this in the other code paths in the function later on, see ```((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes);``` ## How did you test this change? Manually tested by applying the change in the compiled Next.js version. <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. --> Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu> |
||
|
|
56ffca8b9e |
Add Bun streaming server renderer (#25597)
Add support for Bun server renderer |