-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
How to use emotion with renderToPipeableStream #2800
Comments
We didn't implement integration with |
Hey there :) |
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.
|
Note that this won't work correctly with Suspense because you are only awaiting the shell. A more complete solution would append |
yeah and to figure out how to avoid the hydratation missmatch that force react to re render. facebook/react#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
I think that you could try to insert the appropriate <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? |
Remix released CSS-in-JS support with v1.11.0 does that help here? |
@Andarist to make sure I understand correctly, does the work you're doing on styled-components carry over here to emotion? |
@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. |
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? |
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
or e.g.
Or even CSS Modules or Tailwind. Also check Next.js docs - https://nextjs.org/docs/app/building-your-application/styling/css-in-js
|
I wrote a framework that uses renderToPipeableStream to render pages. You can check how to configure it here: https://github.com/rockyshi1993/nodestack?tab=readme-ov-file#how-to-perform-server-side-rendering-ssr |
|
I got something working for streaming Emotion 11 styles via React 18.3.1 First is to set up SSR using the "Advanced Approach" SSR docs. You'll need access to your server-side Emotion cache soon. For the app shell, use whatever you need to get the critical CSS style tags into the head (or wherever you put them). For the streamed chunks, I used a Node transform stream to modify the stream that
Here's broadly what the import { TransformStream } from 'node:stream';
import { HtmlValidate } from 'html-validate';
import { createCache } from '@emotion/cache';
const validator = new HtmlValidate({
rules: {
'close-order': 'error'
}
};
const emotionKey = 'my-key';
const emotionCache = createCache({ key: emotionKey });
class SomeTransformStream extends TransformStream {
alreadyInsertedKeys = new Set();
hasShellBeenSent = false;
chunkBuffer = '';
onShellComplete() {
this.hasShellBeenSent = true;
this.alreadyInsertedKeys = new Set(Object.keys(emotionCache.inserted));
}
_transform(chunk, encoding, cb) {
this.push(chunk);
if (!hasShellBeenSent) {
cb();
return;
}
const decodedChunk = Buffer.from(chunk).toString(encoding);
this.chunkBuffer += decodedChunk;
if (validator.validateStringSync(this.chunkBuffer).valid) {
this.chunkBuffer = '';
const currentInsertedKeys = new Set(Object.keys(emotionCache.inserted));
const newEmotionKeys = currentInsertedKeys.difference(this.alreadyInsertedKeys);
this.alreadyInsertedKeys = currentInsertedKeys;
const newEmotionChunks = [...newEmotionKeys].map(key => ({
name: key,
styles: emotionCache.registered[`${emotionKey}-${key}`],
});
this.push(`<script>${JSON.stringify(newEmotionChunks)}.forEach(c => window.cacheInsertFn.?(c))</script>`);
}
cb();
}
} Caveats
Footnotes
|
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
The text was updated successfully, but these errors were encountered: