使 renderToNodeStream 与异步头盔版本一起工作

Making renderToNodeStream work with an async Helmet version

我正在尝试将我的工作 SSR 代码从 renderToString 转换为 renderToNodeStream,后者在 React.JS 中可用,以缩短生成第一个字节的时间。我知道 react-helmet 是同步的,不能与 renderToNodeStream() 一起使用,但人们使用一些“异步”分支使头盔与 async/stream 渲染互操作。我为我的实验选择了 react-helmet-async 库,并将我的代码转换为以下内容:

  const helmetData = new HelmetData({});
  store
    .runSaga(rootSaga)
    .toPromise()
    .then(() => {
      frontloadServerRender(() =>
        // to support AMP, use renderToStaticMarkup()
        // we no longer care for AMP
        {
          const routeMarkupNodeStream = renderToNodeStream(
            <Capture report={m => modules.push(m)}>
              <Helmet helmetData={helmetData}>
                <Provider store={store}>
                  <StaticRouter location={req.url} context={context}>
                    <Frontload isServer={true}>
                      <App />
                    </Frontload>
                  </StaticRouter>
                </Provider>
              </Helmet>
            </Capture>
          );
          if (context.url) {
            // If context has a url property, then we need to handle a redirection in Redux Router
            res.writeHead(302, {
              Location: context.url
            });
            res.end();
            return;
          }

          // Otherwise, we carry on...
          const state = store.getState();

          const { helmet } = helmetData.context;

          // Let's format those assets into pretty <script> tags
          const extraChunks = extractAssets(manifest, modules).map(
            c =>
              `<script type="text/javascript" src="/${c.replace(
                /^\//,
                ''
              )}"></script>`
          );

          console.log('starting to write node-stream');
          console.log('html:', helmet.htmlAttributes.toString());
          console.log('title:', helmet.title.toString());

          res.write(
            injectHeaderHTML(cachedHtmlData, {
              html: helmet.htmlAttributes.toString(),
              title: helmet.title.toString(),
              meta: helmet.meta.toString(),
              headScript: helmet.script.toString(),
              link: helmet.link.toString()
            })
          );

          console.log('wrote head');

          // On fastify, we got to set the content to html
          // res.header('Content-Type', 'text/html');

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

          routeMarkupNodeStream.on('pipe', () => {
            console.log('Piped!');
          });

          routeMarkupNodeStream.on('end', () => {
            res.end(
              injectFooterHTML(cachedHtmlData, {
                scripts: extraChunks,
                state: JSON.stringify(state).replace(/</g, '\u003c')
              })
            );
            console.log('finished writing');
          });
        }
      );
    })
    .catch(e => {
      res.status(500).send(e.message);
    });
  // Let's do dispatches to fetch category and event info, as necessary
  const { dispatch } = store;

当我运行上面的代码时,它崩溃了:

starting to write node-stream
html: 
title: <title data-rh="true"></title>
wrote head
events.js:292
      throw er; // Unhandled 'error' event
      ^

Invariant Violation: You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.
    at Object.invariant [as default] (/.../node_modules/invariant/invariant.js:40:15)
    at e.warnOnInvalidChildren (/.../node_modules/react-helmet-async/src/index.js:147:5)
    at /.../node_modules/react-helmet-async/src/index.js:188:14
    at /.../node_modules/react/cjs/react.development.js:1104:17
    at /.../node_modules/react/cjs/react.development.js:1067:17
    at mapIntoArray (/.../node_modules/react/cjs/react.development.js:964:23)
    at mapChildren (/.../node_modules/react/cjs/react.development.js:1066:3)
    at Object.forEach (/.../node_modules/react/cjs/react.development.js:1103:3)
    at e.mapChildrenToProps (/.../node_modules/react-helmet-async/src/index.js:172:20)
    at e.r.render (/.../node_modules/react-helmet-async/src/index.js:229:23)
Emitted 'error' event on ReactMarkupReadableStream instance at:
    at emitErrorNT (internal/streams/destroy.js:106:8)
    at emitErrorCloseNT (internal/streams/destroy.js:74:3)
    at processTicksAndRejections (internal/process/task_queues.js:80:21) {
  framesToPop: 1
}

我也尝试 let helmetContext = {}; 并用 <HelmetProvider context={helmetContext}> 包装我的应用程序。在那里,我的 helmetContext 保持设置为 {},所以 const { helmet } = helmetContext; returns undefined.

在我看来,react-helmet-async 需要多次渲染,就我而言。具体来说,为了让它工作,在 const { helmet } = helmetData.context; 之前,我们需要做一些类似 renderToString(app)renderToStaticMarkup(app) 的事情,否则上下文中什么也没有。这意味着要使用异步 renderToNodeStream,我们需要先进行同步渲染。

如果以上是正确的,我不明白将 react-helmet-asyncrenderToNodeStream() 结合使用如何比 react-helmet 提供更好的 time-to-first-byte (TTFB) renderToString().