Skip to content
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

Open
0Lucifer0 opened this issue Jun 28, 2022 · 24 comments
Open

How to use emotion with renderToPipeableStream #2800

0Lucifer0 opened this issue Jun 28, 2022 · 24 comments

Comments

@0Lucifer0
Copy link

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

@Andarist
Copy link
Member

We didn't implement integration with renderToPipeableStream yet.

@Tjerk-Haaye-Henricus
Copy link

Hey there :)
Will this feature come in near future ?

@skriems
Copy link

skriems commented Jul 28, 2022

Is there any roadmap/plan when this is going to be integrated?

@willopez
Copy link

Hello looking forward to this feature, how can we help make its implementation?

@Andarist
Copy link
Member

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.

@0Lucifer0
Copy link
Author

0Lucifer0 commented Aug 20, 2022

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')}
...
`
          

@Andarist
Copy link
Member

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.

@0Lucifer0
Copy link
Author

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

@Andarist
Copy link
Member

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>

@waspeer
Copy link

waspeer commented Aug 24, 2022

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?

@sgabriel
Copy link

Remix released CSS-in-JS support with v1.11.0 does that help here?

@indeediansbrett
Copy link

@Andarist to make sure I understand correctly, does the work you're doing on styled-components carry over here to emotion?

styled-components/styled-components#3658

@zkuzmic zkuzmic mentioned this issue Feb 15, 2023
4 tasks
@LorenDorez
Copy link

@Andarist can you tell us where we stand here?

  1. Is Emotion just not going to be able to support SSR (renderToPipeableStream) anytime soon?
  2. Are there any solid work around? I saw in the linked 'styled-components' you have a potential one using a custom Writable, any updates there and/or an example usage would be awesome

Sorry to be a bother, this just holds up our usage of MUI and keeps us on React v17

@WesleyYue
Copy link

WesleyYue commented Jul 20, 2023

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

@LorenDorez
Copy link

LorenDorez commented Aug 9, 2023

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

@St2r
Copy link

St2r commented Aug 19, 2023

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.

@tats-u
Copy link

tats-u commented Sep 12, 2023

@Andarist

Hm, the quoted issue is somewhat weird - I don't get why React would be bothered by a script injected before . Hydration should only occur within the root

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).

@heath-freenome
Copy link

heath-freenome commented Oct 19, 2023

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.

@piotrblasiak
Copy link

Another year has gone. Are there any plans to support this?

@karol-f
Copy link

karol-f commented Jul 10, 2024

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

@piotrblasiak
Copy link

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?

@karol-f
Copy link

karol-f commented Jul 10, 2024

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)

@rockyshi1993
Copy link

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

@rockyshi1993
Copy link

又一年过去了。有什么计划支持这一点吗?

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests