-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
React 18 Streaming SSR #3658
Description
Hi folks,
React 18 will be rolled out in the near future and currently has an RC release that anyone can try out. I thought I would try the new streaming SSR mode with one of the projects I'm working on that uses styled-components and while working on an implementation I realised that styled-components doesn't have a compatible API yet.
Here's a link to the sandbox with the demonstration of the new streaming API by Dan Abramov. React 18 exposes a new function for rendering on the server called renderToPipeableStream. As part of it's API it exposes a pipe function that expects a target stream (typically a response stream). The existing styled-components API interleaveWithNodeStream expects a stream exposed by React which makes the whole thing incompatible with each other.
I tinkered with the whole thing a bit and the solution seems to be simple - expose a new API that wraps pipe function provided by React, returns a new function with the same API that uses the same internal Transformer stream logic used in interleaveWithNodeStream. The version I came up with looks something like this:
type PipeFn = (destination: streamInternal.Writable) => void;
export default class ServerStyleSheet {
// existing code here...
interleaveWithPipe(pipe: PipeFn): PipeFn {
this._guard();
this.seal();
return (destination) => {
const transformStream = this._getNodeTransformStream();
transformStream.pipe(destination);
pipe(transformStream);
};
}
}_getNodeTransformStream creates the same stream as currently seen in interleaveWithNodeStream.
I got the whole thing working together and would be glad to contribute this as a PR, however I run into an interesting issue while working on it.
It seems that there's currently a difference in handling SSR on the main branch and on the legacy-v5 branch which I believe is the latest stable release. The difference pretty much comes down to this particular commit. React seems to be emitting rendered HTML in very small chunks, sometimes even without closing the current tag. Here's what I am talking about:
<div
data-kit
="
progress
"
role
="
progressbar
"
<!-- the rest goes on -->Every line in the code sample above is a separate chunk emitted by React. Even attributes on tags are split between multiple chunks. Naturally this does not play well with the current implementation in main branch since ServerStyleSheet will insert a style tag after every chunk emitted by React which breaks the HTML and leads to garbage data being rendered to user. Interestingly, the implementation in legacy-v5 works since it does not insert a new style tag if there's no css that needs to be added to stream but this seems like a coincidence rather than something planned.
I wonder if it makes sense instead of copying the current logic from legacy-v5 branch to instead buffer the data emitted by React until it reaches a more reasonable size and then emit it alongside it's style tag if needed.
Would love to discuss it with someone with a deeper understanding of the codebase. I hope I got everything right, I'll happily answer any questions you may have. Any help with this one is much appreciated