-
Notifications
You must be signed in to change notification settings - Fork 325
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
feat: support streaming in Workers runtimes where streaming is supported #326
Conversation
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.
🚀 amazing work! 🚀
response, | ||
head, | ||
state, | ||
dev, |
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.
Nit picky, but maybe explicitly pass isReactHydrationRequest: false
here, just to make it more readable.
writer.drain(); | ||
const stream = renderToReadableStream(app, { | ||
onReadyToStream() { | ||
canStream = !isReactHydrationRequest && componentResponse.canStream(); |
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.
Will the hydration request ever be streamable? (down the road)
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.
Yes, it will, but we need to call a different renderToReadableStream
(like this one).
console.error(error); | ||
async function maybeSendResponse() { | ||
if (!canStream) { | ||
setTimeout(maybeSendResponse, 20); |
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.
In the case where isReactHydrationRequest === true
, will it indefinitely keep setting the timeout?
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.
Do we need to handle the case when error
is not undefined (i.e. when the onError
callback fires?).
200; | ||
|
||
/** | ||
* Don't allow custom statuses for Hydration responses. |
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 comment is confusing to me, it looks like the if block is changing the status for hydration requests
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.
I think the comment maybe should be moved to line 225?
); | ||
}, | ||
onCompleteAll() { | ||
if (!isReactHydrationRequest) { |
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.
Just a suggestion but, since we will need to use a different pipeToNodeWritable
and renderToReadableStream
for RSC response at some point, perhaps we can extract all of these "isReactHydrationRequest" branches already in separate functions? To make the refactoring easier later I mean 🤔
onError(error: any) { | ||
didError = error; | ||
console.error(error); | ||
async function maybeSendResponse() { |
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.
Despite the explanation above, I personally find the pattern used here to be quite confusing. There's no clear indication of what makes the magic "work", that may complicate code maintenance.
Could we try using waitUntil
if it's supported instead? or maybe abstract this code as a CF-specific implementation? In either case, if this code is inspired by the need to find a workaround for a weird runtime behaviour, it probably shouldn't be the default since it may trigger other corner cases in other environments. And, afaik, CF can't currently instantiate ReadableStreams, so this may only run in CF once that support is added, which may also change the underlying implementation and remove the need for the hack.
@jplhomer Oxygen currently reads |
@@ -42,3 +42,12 @@ export function runDelayedFunction(fn: () => Promise<any>) { | |||
|
|||
return context.waitUntil(fn()); | |||
} | |||
|
|||
export function supportsReadableStream() { |
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 must avoid the situation where Hydrogen ships streaming support assuming that Oxygen reads responses using non-streaming APIs, and Oxygen ships its streaming support assuming that Hydrogen never streams. Even if it works correctly on both sides, there might be performance implications. Let's connect to discuss putting this behind a feature-flag in Oxygen (now that we have Oxygen
namespace)?
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.
Closing this in favor of Shopify/hydrogen#498 |
…in-cli-dist Copy templates into CLI dist folder
Description
This PR updates
handle-event
andentry-server
logic to support streaming in Workers runtimes whereReadableStream
is supported.Previously, we only streamed responses back to Node.js runtimes where we had a pipeable
response
object.Fixes #101
Additional context
A lot going on here! Here's a breakdown of what's happening:
isStreamable
logic inhandle-event
to account for supportingReadableStream
return
the values ofstream
andhydrate
entry-server
, we refactor bodies ofstream
andhydrate
to callstreamToWorkerResponse
andstreamToNodeResponse
streamToWorkerResponse
andstreamToNodeResponse
, we add various checks and forks to account for variability in response types. For example, we need to allow a developer to prevent streaming in order to return a custom page response. And for/react
hydration requests, we need to buffer the responses and then pipe them through our wire syntax converter before responding.Note: There is an open React bug which prevents proper streaming in workers runtimes. We'll want to hold off on shipping this until that is resolved. facebook/react#22772
Note: Oxygen technically supports
ReadableStream
via polyfill, but official streaming is not yet supported. We need to find out if shipping this before official support lands will cause issues. E.g. will it just buffer and return the response like normal? cc @maxshirshin UPDATE: I tested it withoxygen-run
and It works just fine 🔥 😎🎩 Tophat instructions
cd packages/playground/server-components-worker
yarn build && yarn dev
Notice that the contents stream in real fast 🔥
To spice things up, you should update the
index.server.jsx
component to make a query and witness the Suspense fallback on the page as the content streams in:Before submitting the PR, please make sure you do the following:
Unreleased
heading in the package'sCHANGELOG.md
fixes #123
)