MichalZalecki / react-portal-universal

Generic wrapper for React's createPortal allowing for rendering portals on the server
ISC License
67 stars 13 forks source link

Support third party dom manipulations #13

Open JPeer264 opened 3 years ago

JPeer264 commented 3 years ago

@jesstelford I mentioned you here, because I cannot create an issue in your fork.

I saw in https://github.com/jesstelford/react-portal-universal/blob/43e9ba1e8e2ad84d55de6a43562d869c63fabafe/src/server.tsx#L26, that there is only support for renderToStaticMarkup. This works totally fine with stream when I put this in stream.on('end', () => {}).

However, I am using styled-components to load stylesheets within my stream and using this function here https://github.com/styled-components/styled-components/blob/93a00472e3b9bf2974149e9d767e69e56659fbbb/packages/styled-components/src/models/ServerStyleSheet.js#L74.

My guess now is that interleaveWithNodeStream from styled-components never reaches the dom created by react-portal-universal and therefore it will never append the styles to it. I am not really sure if this following would be the solution to this problem.

My current setup:

Note: with this setup the markup is generated, but the styles are not appended. So basically the library works as expected. But not in combination with streams + styled-components

router.use('^/', (_, res) => {
  const portals = new ServerPortal();
  const sheet = new ServerStyleSheet();
  const jsx = sheet.collectStyles((
    portals.collectPortals((
      <App />
    ))
  ));
  const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx));

  stream.pipe(res, { end: false });

  stream.on('end', () => {
    const markupPortals = portals.appendUniversalPortals(''); // render statically
    const plainPortals = markupPortals.replace(/^[\s\S]*<body>/, '');

    res.end(plainPortals);
  });
});

Possible solution:

router.use('^/', (_, res) => {
  const portals = new ServerPortal();
  const sheet = new ServerStyleSheet();
  const jsx = sheet.collectStyles((
    portals.collectPortals((
      <App />
    ))
  ));
  const stream = sheet.interleaveWithNodeStream((
    portals.interleaveWithNodeStream(( // <-- only this here
      renderToNodeStream(jsx)
    ))
  ));

  stream.pipe(res, { end: false });

  stream.on('end', () => res.end('</body></html>'));
});
JPeer264 commented 3 years ago

A little follow up on this one (and solution). It seems that streams should work perfectly (tried everything with renderToString). The thing is that this does not output the children (which makes total sense, as this should not be printed here).

I figured that there is a way to add third party support. In here we just need to add something in to interpolate (or any better name) with the children within React.renderToStaticMarkup like so:

// interpolate has a default: (input) => input
ReactDOMServer.renderToStaticMarkup(interpolate(children))

And in the usage it would look like (now without streams):

portals.appendUniversalPortals('', (children) => sheet.collectStyles(children));