Open 0Lucifer0 opened 2 years ago
We didn't implement integration with renderToPipeableStream
yet.
Hey there :) Will this feature come in near future ?
Is there any roadmap/plan when this is going to be integrated?
Hello looking forward to this feature, how can we help make its implementation?
You could try implementing PoC for this. We could hop on a call to discuss some challenges and stuff before that - if you would be interested in this sort of a thing.
kind of hacky but it works for the server side.
const stylesPipeableStream =
(res: Response, cache: EmotionCache, nonceString: string) => {
let content = '';
const inlineStream = new Writable({
write(chunk, _encoding, cb) {
let match;
content = content.concat(chunk.toString());
let regex = new RegExp(`<style data-emotion="${cache.key} ([a-zA-Z0-9-_ ]+)">(.+)<\\/style>`, 'gm');
regex.lastIndex = 0;
while ((match = regex.exec(content)) !== null) {
const id = match[1];
const css = match[2];
cache.nonce = nonceString;
cache.inserted[id] = css;
}
res.write(chunk, cb);
},
final() {
res.end();
},
});
return inlineStream;
};
onShellReady() {
...
stream.pipe(stylesPipeableStream(res, cache, nonce));
},
bootstrapScriptContent: `
${Object.keys(cache.inserted).map(id => `
if(document.querySelectorAll('[data-emotion="${cache.key} ${id}"]').length === 0) {
document.head.innerHTML += '<style data-emotion="${cache.key} ${id}"${!cache.nonce ? '' : ` nonce="${cache.nonce}"`}>${cache.inserted[id].toString()}</style>';
}`).join('\n')}
...
`
Note that this won't work correctly with Suspense because you are only awaiting the shell. A more complete solution would append <script/>
to our <style/>
s that would move the preceding <style>
to document.head
. On top of that, you'd have to figure out how to properly rehydrate all the streamed <style/>
s.
yeah and to figure out how to avoid the hydratation missmatch that force react to re render. https://github.com/facebook/react/issues/24430
This solution is far from ideal that is why I didn't open a pull request with this code but at least it give some short term fix in my case to the unstyled page flashing on loading
Hm, the quoted issue is somewhat weird - I don't get why React would be bothered by a script injected before <head/>
. Hydration should only occur within the root (as far as I know) and that's usually part of the <body/>
. Note thought that I didn't read the whole thread there.
This solution is far from ideal that is why I didn't open a pull request with this code but at least it give some short term fix in my case to the unstyled page flashing on loading
I think that you could try to insert the appropriate <script/>
within write
into the chunk
before calling res.write
with it. The simplest approach, for now, would be to insert:
<script>
var s = document.currentScript;
document.head.appendChild(s.previousSibling);
s.remove();
</script>
A lot of metaframeworks like next.js and remix render the whole document afaik. Would it be possible to create a suspense component that renders a style tag with all the aggregated styles that resolves after the rest of the document has resolved. That might keep react happy?
@Andarist to make sure I understand correctly, does the work you're doing on styled-components carry over here to emotion?
https://github.com/styled-components/styled-components/issues/3658
@Andarist can you tell us where we stand here?
Sorry to be a bother, this just holds up our usage of MUI and keeps us on React v17
For Remix users, you can follow this example. The example is with Chakra but I believe you can use the same approach for any Emotion js setup
That solution basically readers the entire app and defeats the purpose of streaming.
Im working on a POC to address this with Andraist code he provided on the Linked Styled-Components issue. However, he posted that it wasnt his latest version and would try to find it but never posted a follow-up there
Once i have my POC in a good spot ill post it here
I just found a small work-around for this situation.
const {pipe, abort} = renderToPipeableStream(
<CacheProvider value={cache}>
<YourAPP />
</CacheProvider>,
{
onShellReady() {
const body = new PassThrough();
let oldContent = '';
body.on('data', (chunk) => {
const chunkStr = chunk.toString()
if (chunkStr.endsWith('<!-- emotion ssr markup -->')) {
return;
}
if (chunkStr.endsWith('</html>') || chunkStr.endsWith('</script>')) {
const keys = Object.keys(cache.inserted);
const content = keys.map(key => cache.inserted[key]).join(' ');
if (content === oldContent) {
return;
}
body.write(`<style data-emotion-streaming>${content}</style><!-- emotion ssr markup -->`);
oldContent = content;
}
});
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
}
);
After each chunk is spit out, a NEW STYLE TAG is appended to the html according to the contents of the emotion cache.
(A little trick is used here - I determine the end of each valid chunk of react 18 just by simple string matching.)
From my testing, this guarantees that the styles are correct when Server Side Rendering.
@Andarist
. Hydration should only occur within the rootHm, the quoted issue is somewhat weird - I don't get why React would be bothered by a script injected before
I stopped using Emotion due to this and lack of support for RSC. I'll recommend everyone to stop using it and migrate to SCSS Modules or vanilla-extract (or somethig better zero-runtime solutions).
Over 1 year later and emotion still hasn't figure this out? I waited a very long time to upgrade to React 18 and I'm still stuck with rendering the whole document because of this one issue. And I'm only using styled-components with @mui.
Another year has gone. Are there any plans to support this?
Another year has gone. Are there any plans to support this?
At this time, everyone is moving to solutions that works fine with React Server Components and Next.js App Router
Another year has gone. Are there any plans to support this?
At this time, everyone is moving to solutions that works fine with React Server Components and Next.js App Router
You mean other libraries? Like?
Multiple solutions like zero-runtime CSS-in-JS libraries
https://mui.com/blog/introducing-pigment-css/ , https://github.com/mui/pigment-css?tab=readme-ov-file#how-to-guides
In the era of React Server Components and the Next.js App Router, component libraries like Material UI must make some paradigm-shifting changes to reap the potential performance gains by moving more of the work of rendering UIs from client to server.
Trouble is, the "traditional" CSS-in-JS solutions we rely on aren't able to come along with us because the React context API only works on the client.
or e.g.
Panda.css - CSS-in-JS can make it easy to build dynamic UI components, it usually comes with some pretty expensive performance tradeoffs. And since most CSS-in-JS libraries don’t work reliably with server components, it’s clear that the whole paradigm needs to evolve, or risk becoming irrelevant. That’s where Panda comes in. It’s a new styling engine that aims to combine the DX of CSS-in-JS with the performance of atomic CSS, in a way that’s both type-safe and RSC compatible.
Or even CSS Modules or Tailwind.
Also check Next.js docs - https://nextjs.org/docs/app/building-your-application/styling/css-in-js
The following libraries are supported in Client Components in the app directory (alphabetical)
I wrote a framework that uses renderToPipeableStream to render pages.
It only needs to add the following two lines of code in the entry file, but I am not sure this method is suitable for all frameworks.
import createCache from '@emotion/cache'; createCache({ key:'css' });
You can check how to configure it here: https://github.com/rockyshi1993/nodestack?tab=readme-ov-file#how-to-perform-server-side-rendering-ssr
又一年过去了。有什么计划支持这一点吗?
React Server Componen You can see if this framework is suitable for you, it uses renderToPipeableStream to render pages https://github.com/rockyshi1993/nodestack?tab=readme-ov-file#how-to-perform-server-side-rendering-ssr
I'm looking at how to implement SSR emotion with react 18. React18 made renderToPipeableStream method the new recommended way and deprecated the renderToNodeStream method.
The documentation only seems to deal with the now deprecated renderToNodeStream and I can't figure out how to make it works with the pipeable stream.
cf: https://reactjs.org/docs/react-dom-server.html#rendertonodestream https://emotion.sh/docs/ssr#renderstylestonodestream
Documentation links: https://emotion.sh/docs/ssr