stitches icon indicating copy to clipboard operation
stitches copied to clipboard

Stitches 0.2 - insertionMethod alternative for dynamically created <iframe> and shadow DOM

Open cpakken opened this issue 3 years ago ā€¢ 9 comments

In Stitches 0.2, the insertionMethod is removed because of the transition to CSSOM. With that change, I propose some alternative API's that would grant the ability to initialize the root dom node dynamically / lazily.

  • Use stitches styled components within dynamically created iframes and shadow doms
  • can isolate styles using separate createCss() for different iframes / shadow doms

Taking a look at the source code for Stitches 0.2, It seems that createCss({root: DOCUMENT}) : is an undocumented api for applying a custom root. However, this does not apply when the root is lazily / dynamically created.

Use Case

Building a chrome extension with UI overlay. A shadowdom/ iframe is dynamically created to separate css / dom implementation to avoid clashing.

Here are some API

Method 1: Similar to emotion's CacheProvider

//Emotion API for inspiration
<CacheProvider value={createCache({container: RootNode})}>
const { styled, RootProvider } = createCss({})

const Box = styled('div', {})

const IFrameExample = () => {
  const ref = useRef()

  return (
    <iframe ref={ref}>
      <RootProvider root={ref.current.contentWindow.document}>
        {/* Simplified for example, uses portals to insert components into body */}
        <Box>Example</Box>
      </RootProvider>
    </iframe>
  )
}

Method 2: Dynamically call initRoot

const { styled, initRoot } = createCss({})

const Box = styled('div', {})

const IFrameExample = () => {
  const ref = useRef()
  useEffect(() => initRoot(ref.current.contentWindow.document), [])

  return (
    <iframe ref={ref}>
      {/* Simplified for example, uses portals to insert components into body */}
      <Box>Example</Box>
    </iframe>
  )
}

cpakken avatar Jun 14 '21 22:06 cpakken

This seems related to #395.

If you could scope a style sheet to a custom root (a given element in the react tree), then you'd be able to solve scoping issue for iframes and SSR (one sheet per request) at the same time.

CraigCav avatar Jun 22 '21 18:06 CraigCav

While I like being explicit as in these two api proposals, perhaps we could just detect if we're in a shadow DOM or iframe and apply styles to the root there, adding :host, injecting the style tag into the shadow root, etc.

hennessyevan avatar Jul 15 '21 17:07 hennessyevan

Just in case it can give some inspiration, I'm not proud of this (I hoped it's temporary), but basically I provide an "env variable" when building my extension with the id of the element to insert the styles into: https://github.com/LexSwed/fxtrot-ui/blob/master/src/lib/stitches.config.ts#L201

LexSwed avatar Jul 23 '21 17:07 LexSwed

Any updates if this will be implemented in future iterations of v1?

cpakken avatar Aug 30 '21 20:08 cpakken

Any updates if this will be implemented in future iterations of v1?

It was mentioned on Discord that it will be one of the next features to work on after v1 is released, to get more context and understand how this is used, to provide the best solution. I'm sure they gonna publish a roadmap here when possible issues with v1 are mitigated

LexSwed avatar Sep 01 '21 11:09 LexSwed

Iā€™m very interested in this feature, as it would also answer our web components discussion, which also happens to be the highest voted discussion.

jonathantneal avatar Sep 02 '21 14:09 jonathantneal

Adding one of my own workaround here as we ended up encountering this issue at Tango when attempting to integrate our Stitches based Design System to our chrome extension:

  • First issue: Our extension runs in an iframe and we first ended up with unstyled component. The reason behind this was that stitches "didn't know" where to inject the styles. We noticed that by calling getCssText and injecting the output in a dedicated

So we created this hook, it's not too ugly, not the most performant piece of code I'm proud of, but hey, it works šŸ˜„:

import { getCssText } from 'stitches.config';
import { useLayoutEffect } from 'react';

export const useInjectStyles = () => {
  useLayoutEffect(() => {
    const styleTag = document.getElementById('iframe-id').contentWindow.document.getElementById('stitches');

    styleTag!.innerHTML = getCssText();
  });
};
  • Second issue: That on its own worked well. However, we have a specific use case: our extension can be injected on any webpage, and the solution fell apart when we tried to run our extension on pages of websites that were build with Stitches. getCssText failed to output the styles when ran in this specific use case, and the reason behind that (I think) was because it was conflicting with some of the website's own stitches dependency (not sure exactly what, but that's what I deducted)

To fix this, I dug a bit on the Stitches codebase and noticed the mention of a root option for the createStitches function: https://github.com/modulz/stitches/blob/0ebaf9f988871ac0d8d5f2b72f2a8042e0d1b56f/packages/core/src/createStitches.js#L25

I went in and tried it out on my Design System's stitches config by setting the root to the <style id="stitches"/> tag:

export const { styled } = createStitches({
   //@ts-ignore
  root: document.getElementById('stitches')!,
})

This option is not defined in the type declaration of the package but it's definitely there (I just ts-ignored it for now) and that made everything work on our extension and web app alike. No real impact, although this is pretty hacky and I'd rather have an official support than having to make sure this doesn't break during the next update šŸ˜„

BONUS: For Next.js support you'll have to make sure to handle SSR by setting the root option as follow:

export const { styled } = createStitches({
   //@ts-ignore
  root: typeof window !== 'undefined' ? document.getElementById('stitches')! : null,
})

MaximeHeckel avatar Mar 21 '22 19:03 MaximeHeckel

Pending the merge and release of this PR: https://github.com/modulz/stitches/pull/1004

Here is an example using @stitches with web components and ShadowDOM. https://codesandbox.io/s/circular-json-reproduction-forked-gy01cx?file=/src/index.tsx

jlalmes avatar Apr 13 '22 02:04 jlalmes

any updates on this PR ?

ngprnk avatar Oct 29 '22 14:10 ngprnk