Khan / aphrodite

Framework-agnostic CSS-in-JS with support for server-side rendering, browser prefixing, and minimum CSS generation
5.34k stars 188 forks source link

Rendering within an iframe from outside the iframe #130

Open lencioni opened 8 years ago

lencioni commented 8 years ago

I have recently run into an issue when rendering some React components that use Aphrodite in an iframe. Specifically, when the the JavaScript that renders the components is running in the iframe's parent context. The component is rendered correctly with the Aphrodite className, but the styles are added to the head of the parent document instead of the iframe document.

I've hacked around this by finding the aphrodite styles via document.querySelector('style[data-aphrodite]') and copying them into the iframe.

Do you see a reasonable way to make this just work without having to worry about it?

xymostech commented 8 years ago

Huh. Interesting problem! How are you rendering the react components? Can you pass the actual React components between the frames, or do you generate HTML (e.g. with ReactDOMServer.renderToString) and pass that?

Jujhaar commented 8 years ago

It's a bit of a roundabout way - we render the iframe with html elements with specific class names, and then replace those elements in the iframe with React elements. Does that answer your question?

sophiebits commented 8 years ago

React DOM does (mostly) support rendering into a container in another window.

lencioni commented 8 years ago

I don't have the code in front of me right now, but IIRC, it was something along the lines of this (simplified):

<html>
  <head>
    <script src="my-app.js"></script>
  </head>
  <body>
    <iframe id="my-iframe">
      <div id="react-thing"></div>
    </iframe>
  </body>
</html>

The JS in my-app.js renders React into the react-thing div which is inside of the iframe. Since the JS is executed in the context of the parent document, and Aphrodite puts the styles in the head of document, the styles end up not styling the rendered components.

xymostech commented 8 years ago

Interesting! I didn't know that React supported that. Unfortunately, there's no way for Aphrodite to know implicitly which frame you are trying to render to, because it isn't connected to React. Maybe we could expose an API like StyleSheet.renderInFrame(frame, () => { ... }) and then it would put the styles in that given frame?

lnmunhoz commented 7 years ago

@lencioni I am facing the same issue working with iframe, but in my case, sometimes the style tag is not appended at all. I don't now what is happening... Depending on the screen, the style is not appended andI even if I change "views", doesn't get new css from components.

lnmunhoz commented 7 years ago

I am debugging inject.js and seems that this line inject.js#168 is causing my bug. Its sending a empty string to injectStyle method and its not injecting anything. If I remove this line, it works just fine.

xymostech commented 7 years ago

@lnmunhoz Can you provide a more complete example of what you're trying to do? I seriously doubt that removing that line is the solution to your problem.

lnmunhoz commented 7 years ago

Hi @xymostech, I am rendering an App inside a iframe. What basically happens is when the page loads, aphrodite randomically don't append the <style> tag inside the iframe (neither outside, on the host website). Its a very strange behaviour and I can't figure out why this is happening.

I use react-storybook to manage my components and all of them are rendering fine and the <styles> tag is being appended on the <head>, but when I use them in the iframe, sometimes the styles are applied and other times not. The only way that makes 100% guarantee to append is removing that line but I know is not the right solution.

The App stack that runs inside the iframe is:

I don't know if it helps, but I am looking for help to figure out what is going on.

This is one of the components, I am doing something wrong to bug aphrodite?

import React, { PropTypes } from 'react';
import { StyleSheet, css } from 'aphrodite';

const styles = StyleSheet.create({
  root: {
    borderRadius: '4px',
    border: 'none',
    color: 'white',
    backgroundColor: '#1088cc',
    boxShadow: '0px 2px 0px 0px #005888',
    fontSize: '15px',
    letterSpacing: '0.02em',
    padding: '10px 20px',
    outline: '0',
    transition: 'all 0.2s linear',
    ':hover': {
      backgroundColor: '#005888 !important',
      boxShadow: '0px 2px 0px 0px #011d4f !important',
    },
  },
  iconRightContainer: {
    width: '16px',
    display: 'inline-block',
  },
  iconRightContainerMarginLeft: {
    marginLeft: 10,
  },
  iconRight: {
    maxWidth: '100%',
  },
});

const Button = ({ label, iconRight, style, ...props }) => (
  <button
    className={css(styles.root)}
    style={style}
    {...props}
  >
    {label}
    {iconRight ? (
      <div
        className={css(
          styles.iconRightContainer,
          label && styles.iconRightContainerMarginLeft
        )}
      >
        {React.cloneElement(iconRight, {
          className: css(styles.iconRight),
        })}
      </div>
    ) : null}
  </button>
);

Button.propTypes = {
  label: PropTypes.string,
  iconRight: PropTypes.node,
  style: PropTypes.object,
};

export default Button;
jameswlane commented 6 years ago

I am working on a new component library and I am using Storybook to display the component. I also ran into this issue. Its been almost a year on this issue, any progress on nailing this bug down?

IgnusG commented 5 years ago

Interesting! I didn't know that React supported that. Unfortunately, there's no way for Aphrodite to know implicitly which frame you are trying to render to, because it isn't connected to React. Maybe we could expose an API like StyleSheet.renderInFrame(frame, () => { ... }) and then it would put the styles in that given frame?

This would be great for styling components inside shadow root containers 👍