bitovi / react-to-web-component

Convert react components to native Web Components. Works with Preact too!
https://www.bitovi.com/open-source/react-to-web-component
MIT License
720 stars 43 forks source link

Shadow DOM doesn't work for MUI #21

Closed darius-khll closed 1 year ago

darius-khll commented 2 years ago

I have been using react version "17.0.2" and react-to-webcomponent version "1.5.1" and mui/material version "5.4.4"

and whenever I build the app without shadow dom everything works properly however as soon as I convert it to enable the shadow dom and subsequent complication, material ui will be messed up and I'm having doubt what the actual problem might be since I'd rather have the separation for my custom component in order to prevent kind of collision amongst my components! and the problem is there is no error message, whatsoever and I couldn't determine the problem and I suspect you may be able to solve this issue somehow!

Thanks

justinbmeyer commented 2 years ago

We will look into this!

Steph-Yu commented 2 years ago

curious if theres an indicator as to when this fix might be resolved as i'm encountering the same problem too

christopherjbaker commented 2 years ago

Could one of you provide a minimal error case? I'm not sure how to recreate this.

darius-khll commented 2 years ago

@christopherjbaker yes ofc, please take a look at this repo in which particularly referred to the issue

https://github.com/Alikhll/react-to-web-shadow/blob/main/src/App.js

only if I remove shadow: true would end up properly working (in terms of mui implementation and so forth!)

darius-khll commented 2 years ago
image

I don't get any specific error message to demonstrate here, it just looks this way instead of actual mui design which is like the below one while I build it with shadow dom

image

NOTE: build the app using this command "npm run build" and apply the "" component there to see how it will be managed!

darius-khll commented 2 years ago

I believe shadow dom would be really crucial part in terms of managing webcomponents properly and I hope it can raise your prioritization to be able to tackle this issue sooner rather than later :) I wouldn't mind to help if you can lead me how it's supposed to be fixed!

darius-khll commented 2 years ago

@christopherjbaker isn't that sufficient for ya to reproduce the error? let me know if more information is required in order to discover the issue potentially please!

Pantalaimon commented 2 years ago

We had similar issues with material-ui 4 and react webcomponents created with direflow. It was caused by material-ui trying to put its CSS in style tags at the top of the HTML tree, instead of inside the shadow DOM. With elements and style living in different DOM, styling was totally broken, like in your screenshot.

If it is the same issue in material-ui 5, it is fixable by using a RootRef at the top of your react app, so that material-ui know where to put its CSS. See https://direflow.io/guides-and-tips#tips-for-material-ui for more info.

Some components (popovers, etc.) also need a container prop to put their DOM elements under the shadow DOM. Our solution was to:

Hope that helps.

chriszrc commented 1 year ago

@Pantalaimon RootRef is removed in MUI 5, any luck getting this to work in 5?

https://mui.com/material-ui/migration/v5-component-changes/#rootref

chriszrc commented 1 year ago

Ok, update, I did get this to work for MUI5. Not sure if there is a better way to be getting the style ref though?

const App: FC<{}> = () => {
  const [domRef, setDomRef] = useState<HTMLStyleElement>();

  const cache = useMemo(() => {
    if (domRef) {
      return createCache({
        key: "css",
        prepend: true,
        container: domRef,
      });
    } else {
      return null;
    }
  }, [domRef]);

  const getRef = useCallback((ref: HTMLStyleElement | null) => {
    if (ref) {
      setDomRef(ref);
    }
  }, []);

  return (
    <RecoilRoot>
      <RecoilURLSyncJSON location={{ part: "queryParams" }}>
        <DebugObserver />
        <Box>
          <style ref={getRef}></style>
          {cache !== null ? (
            <CacheProvider value={cache}>
              <ThemeProvider theme={theme}>
                <AppApp />
              </ThemeProvider>
            </CacheProvider>
          ) : null}
        </Box>
      </RecoilURLSyncJSON>
    </RecoilRoot>
  );
};

export default App;

const MyApp = reactToWebComponent(App, React as any, ReactDOM as any, {
  shadow: "open",
});

customElements.define("my-app", MyApp as any);

Now MUI will inject the emotion css inside the shadowroot. But how do we attach other styles, like css modules to the shadow root?

christopherjbaker commented 1 year ago

@chriszrc Glad you found a solution for MUI5. It looks unpleasant, but I can't see any way to simplify it. Another option: you could create the style root in a ref, that way you could create the cache and pass it to the cache provider on first render. Then have a getRef on the parent element to add the style ref when that one mounts.

We've been looking into this in general, but every approach to styling needs its own custom solution, so I'm sure you can imagine how it's both a research and maintenance nightmare. As far as CSS modules, I've been unable to find anything that looks like it will work with CSS modules; the webpack and vite modules just don't seem to expose an option to control where the styles mount. I'd recommend opening an issue with them.

Subramanian8 commented 6 months ago

@darius-khll Have you found any solution for this issue? Facing same issue while using MUI in shadow DOM.

ToinePK commented 5 months ago

You can most definately control where CSS modules are injected using style-loader. For example with webpack:

webpack.config.js

...
{
  loader: "style-loader",
  options: {
    insert: require.resolve('./utils/style-loader')
  },
}
...

The style loader script (./utils/style-loader.js in this example):

export const styleTags = [];
export const getStyleTags = () => styleTags.map(node => node.cloneNode(true));

export default function (linkTag) {
  styleTags.push(linkTag);
}

Then wherever you want the styles to be injected, for example inside the shadow root of a webcomponent:

import { styleTags } from './utils/style-loader';

class MyWebcomponent extends window.HTMLElement {
  shadow;

  constructor() {
    super();
    this.shadow = this.attachShadow({mode: 'closed'});
  }

  connectedCallback() {
    ...
    this.shadow.append(...styleTags);
    ...
  }
}