如何在Gatsby网站的iframe中渲染使用Emotion样式的React组件?

时间:2019-11-29 04:25:18

标签: reactjs webpack iframe gatsby emotion

我在组件库和演示站点上工作。

使用Emotion为组件设置样式,并使用Gatsby构建演示站点。

出于预览目的,我想在iframe中渲染组件。这样可以确保网站中的样式不会层叠到各个组件上,并使其更易于处理响应式布局等。

我还想保留iframe中的热重装。

Here,您可以看到一个示例,该示例显示了网站上的line-height是如何级联到Button组件上的,从而使其变得很高。

enter image description here

如何在iframe中渲染Button及其所有样式?

2 个答案:

答案 0 :(得分:2)

我认为这里的问题是将emotion生成的样式应用于iframe内的按钮。

我发现Mitchell(情感核心团队)的这个出色示例完全满足您的需求:github

这是您的代码箱的一个分支,上面复制了代码,并带有一个基本的自制<Iframe>元素:codesandbox

result

以下是相关代码:

// src/components/Iframe.js

import React, { useRef, useEffect, useState } from 'react'
import { createPortal } from 'react-dom'

import { CacheProvider } from '@emotion/core'
import createCache from '@emotion/cache'
import weakMemoize from '@emotion/weak-memoize'

// literally copied from Mitchell's codesandbox
// https://github.com/emotion-js/emotion/issues/760#issuecomment-404353706
let memoizedCreateCacheWithContainer = weakMemoize(container => {
  let newCache = createCache({ container });
  return newCache;
});


/* render Emotion style to iframe's head element */
function EmotionProvider({ children, $head }) {
  return (
    <CacheProvider value={memoizedCreateCacheWithContainer($head)}>
      {children}
    </CacheProvider>
  )
}

/* hack-ish: force iframe to update */
function useForceUpdate(){
  const [_, setValue] = useState()
  return () => setValue(0)
}

/* rudimentary Iframe component with Portal */
export function Iframe({ children, ...props }) {
  const iFrameRef = useRef(null)
  const [$iFrameBody, setIframeBody] = useState(null)
  const [$iFrameHead, setIframeHead] = useState(null)
  const forceUpdate = useForceUpdate()

  useEffect(function(){
    if (!iFrameRef.current) return

    const $iframe = iFrameRef.current
    $iframe.addEventListener('load', onLoad)

    function onLoad() {
      // TODO can probably attach these to ref itself?
      setIframeBody($iframe.contentDocument.body)
      setIframeHead($iframe.contentDocument.head)

      // force update, otherwise portal children won't show up
      forceUpdate()
    }

    return function() {
      // eslint-disable-next-line no-restricted-globals
      $iframe.removeEventListener('load', onload)
    }
  })

  return (<iframe {...props} title="s" ref={iFrameRef}>
      {$iFrameBody && $iFrameHead && createPortal((
        <EmotionProvider $head={$iFrameHead}>{children}</EmotionProvider>
      ), $iFrameBody)}
    </iframe>)
}

如果您希望在gatsby build期间预渲染iFrame,则需要做更多的工作。

对于styled-components用户,我发现Stephen Haney的这段代码比emotion更优雅:

  

[...] styled-components包含一个StyleSheetManager组件,该组件   可以拿一个目标道具。目标需要一个DOM节点,它将   将其动态创建的样式表附加到该节点。

     

react-frame-component使用React的Context API新版本来   公开FrameContextProvider。它包括IFrame文档和   上下文中的窗口。

     

您可以按以下方式结合使用这两个API来使用styled-components   在您的IFrame中:

    {
      frameContext => (
        <StyleSheetManager target={frameContext.document.head}>
          <React.Fragment>
            {/* your children here */}
          </React.Fragment>
        </StyleSheetManager>
      )
    }   </FrameContextConsumer> </Frame> 

     

这与react v16.4.1,样式组件v3.3.3和react-frame-component v4.0.0完美配合。

答案 1 :(得分:0)

i forked您的沙盒以显示解决方案。
步骤:

  1. 使用或编写iframe组件。在沙箱中,我使用react-frame-componenthttps://github.com/ryanseddon/react-frame-component)。它将为我们呈现任何传递的内容的iframe。
  2. 找到一种通过情感创造风格的方法。 Emotion只会创建style个节点,因此我们将对其进行复制。在沙箱中,我编写了非常原始的代码来检查该想法,它是否有效,但是在生产中,我应该编写一些更高级的内容:
        <Frame>
          <FrameContextConsumer>
            {// Callback is invoked with iframe's window and document instances
            ({ document }) => {
              if (isFirstRender) {
                setTimeout(() => {
                  // find styles in main document
                  const styles = Array.from(
                    window.document.head.querySelectorAll("style[data-emotion]")
                  )
                  // and add it to the child
                  styles.forEach(s =>
                    document.head.appendChild(s.cloneNode(true))
                  )
                  isFirstRender = false
                }, 100)
              }
              // Render Children
              return <Button>Primary</Button>
            }}
          </FrameContextConsumer>
        </Frame>

注意:我不熟悉emotion,但是我认为它不会在生产中(通过webpack ofc)创建style节点,但是会创建一个文件,例如styles.css。 然后,您应该将链接添加到子文档:

              if (isFirstRender) {
                setTimeout(() => {
                  const link = document.createElement("link");
                  link.href = "styles.scss";
                  link.rel = "stylesheet";

                  document.head.appendChild(link);

                  isFirstRender = false
                }, 100)
              }