Emotion: Can the styling work out what document.head to insert itself into?

Created on 7 Feb 2018  路  13Comments  路  Source: emotion-js/emotion

Edit: You might want to look at this. However, I've looked at this PR and my understanding is that they will still have exactly the same problem with keyframes and injectGlobal, however they will not have this problem for css since their version of this has different semantics than yours: it is not interpolated and injected into the header until it is used within a styled component.


First of all, thanks for the excellent work on this project! :)

I have an interesting use-case which from what I can see doesn't seem to be easily supported.

I have an enterprise React application which may or may not be dynamically rendered into either an <iframe> or a separate window (via openfin#Window). I do not know this environment until applications are loaded at runtime, and I want to write components which are not aware of this at all.

My understanding is that, the styling will not work unless the <style> tags are placed into the correct window. I can see that it's possible to pass in a container, however I also see that by default createEmotion is called at import by emotion and react-emotion. This is highly likely to cause my components to be rendered without styles.

In order to load the styles into the correct window, I would need to not use those modules but instead use the raw create-emotion and to create my own wrappers that pass in the correct container. I might be able to get this to work by setting the document.head via openfin.Window#getCurrent, the document or the the <iframe>. However, I'm not in control of when these documents are created or how they will be configured and this happens at runtime.


I'm wondering whether you can think of any patterns which would help me to get this to work without too much boilerplate or complication? I would like to be able to write normal shareable components which do not need to know about my environment. Ideally I want to be able to create something which I combine with normal react-emotion components to get them to insert their styles into the correct DOM. I want to be able to use all of Emotion's syntax (e.g. keyframes, css, etc.) and not just styled components.

What I normally do is use a ref within my React code to get a DOM element and then check this for the element#ownerDocument to find out the real document. However, this pattern will not work in this case since the sheet will have already been created at import-time, and any styles created with the css template literal will have already added their respective rules to the wrong document.head.

Is there be a way of configuring the underlying sheet which the styles are attached to and potentially choosing to assign styles to a new document element after they were originally inserted? Perhaps by splitting insertion of styles into a registering phase and an insertion phase? In that case I could call this method and pass in a refElement.ownerDocument.head at runtime. Potentially then some kind of EmotionProvider could manage emotion instances? I guess my other option is just to entirely ignore this problem and to use stylesheet-observer to splat stylesheets from a parent frame/window into its children, or to entirely change the architecture of our application so it doesn't run code in this way...

Sorry for the rambling message. Hopefully you understand the question/feature request.

feature request stale

Most helpful comment

Agreed, that's a whole lot of integration code in the linked sandbox. @mitchellhamilton is it fair to say this issue should remain open until emotion handles most/all of this? Definitely awesome that v10 opens the door! Would just like to see built in support.

All 13 comments

Doing something like this isn't really possible with emotion's current architecture. I'm currently working on something that would make it possible to solve this problem though in https://github.com/emotion-js/next.

For reference, I have a similar problem: https://github.com/interactivethings/catalog/issues/376

I came up with an :sparkles: awful hack :sparkles: for my use-case.

import React, { Component } from 'react';
import { sheet } from 'emotion';

export class EmotionSheetProvider extends Component {

  setContainer = (ref) => (this.container = ref);

  componentDidMount() {
    // This method is called after a render when we know the container has been set.
    // At this point we have a <div> element that we can get the owner document off,
    // in order that the code runs on the correct document window.
    if (this.container) {
      const ownerDocument = this.container.ownerDocument;
      const head = ownerDocument.getElementsByTagName('head')[0];

      // This is a hack.
      // If emotion is rendered into an <iframe> or external window, it needs
      // its container to be the <head> of the frame which it is rendering into,
      // instead of the parent frame in which the JavaScript is executed.
      // See: https://github.com/emotion-js/emotion/issues/560
      const emotionSheet = sheet;
      Object.defineProperty(emotionSheet.opts, 'container', {
        value: head
      });
      if ('__SECRET_EMOTION__' in window) {
        const secretEmotionSheet = window.__SECRET_EMOTION__.sheet;
        Object.defineProperty(secretEmotionSheet.opts, 'container', {
          value: head
        });
      }
    }
  }

  render() {
    const { children } = this.props;
    return (
      <div className="EmotionSheetProvider" ref={this.setContainer}>
        {children}
      </div>
    );
  }
}

export default EmotionSheetProvider;

It's not possible for me to use createEmotion directly, because the container my application will be placed into is only known at runtime. The parent application is OpenFin/Electron and can place it into new windows or new frames at any point. I need for it to just work and would like for the application and component libraries to not have custom logic in them because of this.

Doing something like this isn't really possible with emotion's current architecture. I'm currently working on something that would make it possible to solve this problem though in https://github.com/emotion-js/next.

@mitchellhamilton Glad to see you're working on this.

I think my big remaining worry, is what I will do when there is a single instance of emotion in a parent window which runs all of the JavaScript, but 10s of child windows opened up which should each have their own <style> tags within their own <head> tags. I'm worried that this won't be possible, because there is only one container and one window will get all of the styling for all of the other widgets...

Just to add, I think the only css-in-js framework to have handled this correctly so far is jss.

They have a <JssProvider jss={jssInstance} /> component, as well as a jssInstance#insertionPoint.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

Would be really cool to see this solved in Emotion. Styled Components has a similar approach to the one JSS uses, a StylesheetManager component.

@erquhart it's possible with custom emotion instances (you can check out how to create 1 here)

And it will become even easier in upcoming emotion@10 - see this

The rough part with instances is making them available to other packages within our monorepo. We can create it in the core package, just no clean way to pass it around. I do have my eyes on v10, is it even remotely safe to use yet? I'm okay with early adoption :)

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

Commenting to stop stale bot from closing.

Closing since v10 is out and allows you to do this, https://codesandbox.io/s/p98y9o7jz0.

@mitchellhamilton hey there, i'm quite new to emotion, but isn't there a less low level way to do this?
Currently with SC, I have:

<StyleSheetManager target={frameContext.document.head} ...>

Thanks!

Agreed, that's a whole lot of integration code in the linked sandbox. @mitchellhamilton is it fair to say this issue should remain open until emotion handles most/all of this? Definitely awesome that v10 opens the door! Would just like to see built in support.

Was this page helpful?
0 / 5 - 0 ratings