Commit Graph

13 Commits

Author SHA1 Message Date
Sebastian Markbåge
18212ca960
[Fizz] Outline if a boundary would add too many bytes to the next completion (#33029)
Follow up to #33027.

This enhances the heuristic so that we accumulate the size of the
currently written boundaries. Starting from the size of the root (minus
preamble) for the shell.

This ensures that if you have many small boundaries they don't all
continue to get inlined. For example, you can wrap each paragraph in a
document in a Suspense boundary to regain document streaming
capabilities if that's what you want.

However, one consideration is if it's worth producing a fallback at all.
Maybe if it's like `null` it's free but if it's like a whole alternative
page, then it's not. It's possible to have completely useless Suspense
boundaries such as when you nest several directly inside each other. So
this uses a limit of at least 500 bytes of the content itself for it to
be worth outlining at all. It also can't be too small because then for
example a long list of paragraphs can never be outlined.

In the fixture I straddle this limit so some paragraphs are too small to
be considered. An unfortunate effect of that is that you can end up with
some of them not being outlined which means that they appear out of
order. SuspenseList is supposed to address that but it's unfortunate.

The limit is still fairly high though so it's unlikely that by default
you'd start outlining anything within the viewport at all. I had to
reduce the `progressiveChunkSize` by an order of magnitude in my fixture
to try it out properly.
2025-04-29 19:13:28 -04:00
Sebastian Markbåge
143d3e1b89
[Fizz] Emit link rel="expect" to block render before the shell has fully loaded (#33016)
The semantics of React is that anything outside of Suspense boundaries
in a transition doesn't display until it has fully unsuspended. With SSR
streaming the intention is to preserve that.

We explicitly don't want to support the mode of document streaming
normally supported by the browser where it can paint content as tags
stream in since that leads to content popping in and thrashing in
unpredictable ways. This should instead be modeled explictly by nested
Suspense boundaries or something like SuspenseList.

After the first shell any nested Suspense boundaries are only revealed,
by script, once they're fully streamed in to the next boundary. So this
is already the case there. However, for the initial shell we have been
at the mercy of browser heuristics for how long it decides to stream
before the first paint.

Chromium now has [an API explicitly for this use
case](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#stabilizing_page_state_to_make_cross-document_transitions_consistent)
that lets us model the semantics that we want. This is always important
but especially so with MPA View Transitions.

After this a simple document looks like this:

```html
<!DOCTYPE html>
<html>
  <head>
     <link rel="expect" href="#«R»" blocking="render"/>
  </head>
  <body>
    <p>hello world</p>
    <script src="bootstrap.js" id="«R»" async=""></script>
    ...
  </body>
</html>
```

The `rel="expect"` tag indicates that we want to wait to paint until we
have streamed far enough to be able to paint the id `"«R»"` which
indicates the shell.

Ideally this `id` would be assigned to the root most HTML element in the
body. However, this is tricky in our implementation because there can be
multiple and we can render them out of order.

So instead, we assign the id to the first bootstrap script if there is
one since these are always added to the end of the shell. If there isn't
a bootstrap script then we emit an empty `<template
id="«R»"></template>` instead as a marker.

Since we currently put as much as possible in the shell if it's loaded
by the time we render, this can have some negative effects for very
large documents. We should instead apply the heuristic where very large
Suspense boundaries get outlined outside the shell even if they're
immediately available. This means that even prerenders can end up with
script tags.

We only emit the `rel="expect"` if you're rendering a whole document.
I.e. if you rendered either a `<html>` or `<head>` tag. If you're
rendering a partial document, then we don't really know where the
streaming parts are anyway and can't provide such guarantees. This does
apply whether you're streaming or not because we still want to block
rendering until the end, but in practice any serialized state that needs
hydrate should still be embedded after the completion id.
2025-04-25 11:52:28 -04:00
Sebastian Markbåge
14c2be8dac
Rename Node SSR Callbacks to onShellReady/onAllReady and Other Fixes (#24030)
* I forgot to call onFatalError

I can't figure out how to write a test for this because it only happens
when there is a bug in React itself which would then be fixed if we found
it.

We're also covered by the protection of ReadableStream which doesn't leak
other errors to us.

* Abort requests if the reader cancels

No need to continue computing at this point.

* Abort requests if node streams get destroyed

This is if the downstream cancels is for example.

* Rename Node APIs for Parity with allReady

The "Complete" terminology is a little misleading because not everything
has been written yet. It's just "Ready" to be written now.

onShellReady
onShellError
onAllReady

* 'close' should be enough
2022-03-04 14:38:46 -05:00
Sebastian Markbåge
5690932765
Add onErrorShell Callback (#23247)
This indicates that an error has happened before the shell completed and
there's no point in emitting the result of this stream.

This is not quite the same as other fatal errors that can happen even
after streaming as started.

It's also not quite the same as onError before onCompleteShell because
onError can be called for an error inside a Suspense boundary before the
shell completes.

Implement shell error handling in Node SSR fixtures

Instead of hanging indefinitely.

Update Browser Fixture

Expose onErrorShell to the Node build

This API is not Promisified so it's just a separate callback instead.

Promisify the Browser Fizz API

It's now a Promise of a readable stream. The Promise resolves when the
shell completes. If the shell errors, the Promise is rejected.
2022-02-08 22:38:14 -05:00
Sebastian Markbåge
cdb8a1d19d
[Fizz] Add option to inject bootstrapping script tags after the shell is injected (#22594)
* Add option to inject bootstrap scripts

These are emitted right after the shell as flushed.

* Update ssr fixtures to use bootstrapScripts instead of manual script tag

* Add option to FB renderer too
2021-10-19 19:36:10 -07:00
Sebastian Markbåge
579c008a75
[Fizz/Flight] pipeToNodeWritable(..., writable).startWriting() -> renderToPipeableStream(...).pipe(writable) (#22450)
* Rename pipeToNodeWritable to renderToNodePipe

* Add startWriting API to Flight

We don't really need it in this case because there's way less reason to
delay the stream in Flight.

* Pass the destination to startWriting instead of renderToNode

* Rename startWriting to pipe

This mirrors the ReadableStream API in Node

* Error codes

* Rename to renderToPipeableStream

This mimics the renderToReadableStream API for the browser.
2021-10-06 00:31:06 -04:00
Sebastian Markbåge
2cc6d79c98
Rename onReadyToStream to onCompleteShell (#22443) 2021-09-27 12:38:22 -07:00
Sebastian Markbåge
9212d994ba
Merge /unstable-fizz entry point into /server (#21684) 2021-06-14 18:37:44 -07:00
Sebastian Markbåge
bd45ad05dc
Add a DOCTYPE to the stream if the <html> tag is rendered (#21680)
This makes it a lot easier to render the whole document using React without
needing to patch into the stream.

We expect that currently people will still have to patch into the stream
to do advanced things but eventually the goal is that you shouldn't
need to.
2021-06-14 13:57:17 -07:00
Sebastian Markbåge
af5037a7a8
[Fizz] Wire up the Fixture (#21273)
* Wire up fizz to fixture

* Fixed typo conditional
2021-04-22 15:00:35 -07:00
Brandon Dail
d04618b28b Run all fixtures through Prettier (#10157)
* Include fixtures in prettier default pattern

* Run all fixtures through Prettier
2017-07-12 11:19:24 -05:00
Sebastian Markbage
50d6f9656d Don't try to load asset manifest in dev mode
We're not going to use it so don't try to load it since it may not have
been built yet.
2017-04-27 21:57:01 -07:00
Sebastian Markbåge
b9da1741cb SSR fixture (#9547)
Adds a server-rendering fixture based on create-react-app without ejecting.

This is using the full-page strategy because I wanted to flush out any issues with it. Turns out there's a lot of little things that are fixable and some non-fixable. This actually surfaced some differences between the current strategy and the one I had in mind.

This uses the asset manifest from webpack to pass the final URLs to the client. This ensures that we can render the same exact mark up on the client - including the URL to our own script that we're running in.

Doing full document renders work with 15.x as long as the checksum matches. However, with the patch up reviving strategy I had in mind it would end up removing the CSS tags that webpack injects before running our render call. This is a behavior change.

In dev mode the server runs a proxy in front of the normal CRA webpack server so that we can replace the HTML request for the root page.

I don't know what the best way to link in the react and react-dom packages. Each fixture has a different strategy so here's another one. Just add NODE_PATH=../../build/packages in front of all commands.
2017-04-27 21:03:32 -07:00