Material UI(MUI) + WebComponents でCSSが無効化されるのを回避する方法

（2年ほど前のメモが残っていたので記事化…）

# 前提

- 普通にReactで書いたコンポーネントを、[react-to-webcomponent](https://github.com/bitovi/react-to-web-component)でウェブコンポーネントに変換した。
- 親画面とスタイルのスコープを分離したいので、ShadowDOMにする。

# 環境

- React 18.1.0
- TypeScript 4.6.4
- Material UI(MUI) v5
- react-to-webcomponent

# 困ったこと

MUIを使用したウェブコンポーネントを親コンポーネントで表示させると、スタイルが無効化されてしまう（MUI が適用されない）

# なぜこのようなことが起きるか

- 親でウェブコンポーネントを表示させた際、MUIのスタイル定義は親側のstyle要素に挿入される。
- しかし、ウェブコンポーネントはShadowDOMであるため、親→子にスタイル適用ができずにMUIのスタイル定義が無効化されてしまう。

# だめだった実装例

以下のように書くと、Material UIのButtonコンポーネントを使っていても親画面からウェブコンポーネントを表示させた際にスタイルが無効化されてしまい、普通のボタンになってしまう。

```tsx
const SampleComponent: React.FC<{ data: string }> = ({ data }) => {
    return (
        <div>
            <MuiThemeProvider theme={createTheme()}>
                <Box>
                    // Material UI のButton コンポーネント
					<Button>クリック</Button>
                </Box>
            </MuiThemeProvider>
        </div>
    );
};

SampleComponent.propTypes = {
    data: PropTypes.string,
};
const sampleWebComponent = reactToWebComponent(
    SampleComponent,
    React,
    ReactDOM,
    {
        shadow: 'open',
    }
);
customElements.define('sample-wc', sampleWebComponent);
```

# 対応

以下のようなラッパーを定義して、この中で目的のコンポーネントを使う。

## Material UI v4での実装

当初はMaterial UI v4 で実装しており、後でアップグレードして後述するv5の実装にしました。

もう需要はあまり無いとは思いますが、v4では以下のような実装になります。

参考　https://stackoverflow.com/questions/54430119/how-to-mount-styles-inside-shadow-root-using-cssinjs-jss

```tsx
import { CssBaseline, ThemeProvider } from '@material-ui/core';
import { jssPreset, StylesProvider } from '@material-ui/styles';
import { create } from 'jss';
import React, { ReactNode, useState } from 'react';
// theme は MUI のcreateTheme で別途定義したもの
import theme from 'src/theme';

const WebComponentStyle: React.FC<{ children: ReactNode }> = ({
    children,
}) => {
    const [jss, setJss] = useState(null);

    function setRefAndCreateJss(headRef) {
        if (headRef && !jss) {
            const createdJssWithRef = create({
                ...jssPreset(),
                insertionPoint: headRef,
            });
            setJss(createdJssWithRef);
        }
    }

    return (
        <div>
            <style ref={setRefAndCreateJss}></style>
            {jss && (
                <>
                    <CssBaseline />
                    <ThemeProvider theme={theme}>
                        <StylesProvider jss={jss} sheetsManager={new Map()}>
                            {children}
                        </StylesProvider>
                    </ThemeProvider>
                </>
            )}
        </div>
    );
};
```

## MUI v5での実装

上記をv5向けに改修したものが以下になります。

emotionのCacheProviderを使用しています。

```tsx
import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import { CssBaseline, ThemeProvider } from '@mui/material';
import { jssPreset, StylesProvider } from '@mui/styles';
import { create } from 'jss';
import React, { ReactNode, useState } from 'react';
// theme は MUI のcreateTheme で別途定義したもの
import theme from 'src/theme';

const WebComponentStyle: React.FC<{ children: ReactNode }> = ({
    children,
}) => {
    const [jss, setJss] = useState(null);
    const [cache, setCache] = useState(null);

    function setRefAndCreateJss(headRef) {
        if (headRef && !jss) {
            const createdJssWithRef = create({
                ...jssPreset(),
                insertionPoint: headRef,
            });
            setJss(createdJssWithRef);

            const _cache = createCache({
                key: 'css',
                prepend: true,
                container: headRef,
            });
            setCache(_cache);
        }
    }

    return (
        <div>
            <style ref={setRefAndCreateJss}></style>
            {jss && (
                <>
                    <ThemeProvider theme={theme}>
                        <CssBaseline />
                        <StylesProvider jss={jss} sheetsManager={new Map()}>
                            <CacheProvider value={cache}>
                                {children}
                            </CacheProvider>
                        </StylesProvider>
                    </ThemeProvider>
                </>
            )}
        </div>
    );
};
```

# 補足

- StylesProviderに`sheetsManager={new Map()}`を指定しないと、親でWebコンポーネントを複数並べたときにCSSが無効化される。
参考：https://stackoverflow.com/questions/62883738/material-ui-inside-shadow-dom-is-missing-styles-for-subsequent-invocation-of-web