如何在 React 自定义元素中为 MUI material v5 创建插入点以在阴影 dom 中安装样式

How to create insertion point to mount styles in shadow dom for MUI material v5 in React custom element

使用@material-ui/core V4(确切地说是4.12.3)我有一个使用webpack和babel成功创建的自定义元素。我曾经使用@material-ui/core makeStyles 来设计它的样式。现在我正在升级到 @mui/material v5 并希望使用来自 @mui/material 的内置组件,但它们不会在自定义元素中显示样式。 请注意,我需要这是一个自定义元素,因为它将集成到另一个托管应用程序中。

index.tsx 在 v4 之前

import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import { create } from 'jss';

class MyWebComponent extends HTMLElement {
    connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'open' });
        const mountPoint = document.createElement('custom-jss-insertion-point');
        const reactRoot = shadowRoot.appendChild(mountPoint);
        const jss = create({
            ...jssPreset(),
            insertionPoint: reactRoot,
        });

        render(
            <StylesProvider jss={jss}>
                <AppComponent />
            </StylesProvider>,
            mountPoint
        );
    }
}
customElements.define('my-element', MyWebComponent);

升级到@mui/material v5(准确地说是 v5.0.4),首先我尝试使用 StyledEngineProvider 来安装样式。然后我尝试使用@mui/styles jssPreset。无论哪种方式都行不通。我的意思是不起作用的是 AppComponent 引用的 DataContainer 有 @mui/material 组件,它们都在加载时没有任何样式(例如 Grid、Button、InputLabel、Select,等等).

首先尝试使用 StyledEngineProvider

import AppComponent from './App';
import { ThemeProvider, createTheme, StyledEngineProvider } from '@mui/material/styles';
import { render } from 'react-dom';
    

const theme = createTheme();

class MyWebComponent extends HTMLElement {
    connectedCallback() {
        // can't use jss in mui v5
        const shadowRoot = this.attachShadow({ mode: 'open' });
        const mountPoint = document.createElement('custom-insertion-point');
        const reactRoot = shadowRoot.appendChild(mountPoint);

        render(
            <StyledEngineProvider injectFirst>
            <ThemeProvider theme={theme}>
                <AppComponent />
            </ThemeProvider>
            </StyledEngineProvider>,
            mountPoint //I have also used reactRoot here instead and got same result
        );
    }
}
customElements.define('my-element', MyWebComponent);

第二次尝试@mui/styles jssPreset

import AppComponent from './App';
import { render } from 'react-dom';
import { StylesProvider, jssPreset } from '@mui/styles';
import { create } from 'jss';

class MyWebComponent extends HTMLElement {
    connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'open' });
        const mountPoint = document.getElementById('jss-insertion-point');
        const reactRoot = shadowRoot.appendChild(mountPoint);
        const jss = create({
            ...jssPreset(),
            insertionPoint: reactRoot,
        });

        render(
            <StylesProvider jss={jss}>
                <AppComponent />
            </StylesProvider>,
            mountPoint
        );       
    }
}
customElements.define('my-element', MyWebComponent);

应用组件

import React from 'react';
import { Suspense } from 'react';
import DataContainer from './components/DataContainer';

class AppComponent extends React.Component<any> {
    
    render() {
        return (
            <Suspense fallback='Loading...'>
                <div className='AppComponent'>
                    <DataContainer />
                </div>
            </Suspense>
        );
    }
}
export default AppComponent;

数据容器

import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Grid from '@mui/material/Grid';
import Button from '@mui/material/Button';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';

const Item = styled(Paper)(({ theme }) => ({
  ...theme.typography.body2,
  padding: theme.spacing(1),
  textAlign: 'center',
  color: theme.palette.text.secondary,
}));

export default function FullWidthGrid() {
  return (
    <Box sx={{ flexGrow: 1 }}>
      <Grid container spacing={2}>
        <Grid item xs={6} md={8}>
          <Button variant="contained">xs=6 md=8</Button>
        </Grid>
        <Grid item xs={6} md={4}>
          <Item>xs=6 md=4</Item>
        </Grid>
        <Grid item xs={6} md={4}>
          <Item>xs=6 md=4</Item>
        </Grid>
        <Grid item xs={6} md={8}>
          <Item>xs=6 md=8</Item>
        </Grid>
      </Grid>
      <div>
        <FormControl sx={{ m: 1, minWidth: 180 }}>
          <Select autoWidth>
            <MenuItem value="">
              <em>None</em>
            </MenuItem>
            <MenuItem value={10}>Twenty</MenuItem>
            <MenuItem value={21}>Twenty one</MenuItem>
            <MenuItem value={22}>Twenty one and a half</MenuItem>
          </Select>
        </FormControl>
      </div>
    </Box>
  );
}

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link rel="icon" href="favicon.ico" />
        <link rel="apple-touch-icon" href="logo192.png" />
        <link rel="manifest" href="manifest.json" />
        <title>React Custom Element</title>
    </head>
    <body>
        <my-element id="elem"> </my-element>
        </body>
</html>

这是我看到的:

这就是我应该看到的,如本次 stackblitz 所示。 (请注意,不幸的是我无法创建带有自定义元素的 stackblitz) https://stackblitz.com/edit/react-d8xtdu?file=index.js

以下是我的做法:

您需要创建 style 标签。这将是情感(material ui 5 样式解决方案)插入范围阴影 DOM 样式的切入点。

下一步是配置jss和情感缓存

const jss = create({
    ...jssPreset(),
    insertionPoint: reactRoot,
});

const cache = createCache({
    key: 'css',
    prepend: true,
    container: emotionRoot,
 });

最后要做的是将我们的树包装在提供者中

render(
   <StylesProvider jss={jss}>
      <CacheProvider value={cache}>
         <ThemeProvider theme={theme}>
            <Demo />
         </ThemeProvider>
      </CacheProvider>
   </StylesProvider>,
   mountPoint
);

完整示例:

    import React from 'react';
    import Demo from './demo';
    import { ThemeProvider, createTheme } from '@mui/material/styles';
    import { StylesProvider, jssPreset } from '@mui/styles';
    import { CacheProvider } from '@emotion/react';
    import createCache from '@emotion/cache';
    import { create } from 'jss';
    import { render } from 'react-dom';
    
    const theme = createTheme();
    
    class MyWebComponent extends HTMLElement {
      connectedCallback() {
        const shadowRoot = this.attachShadow({ mode: 'open' });
        const emotionRoot = document.createElement('style');
        const mountPoint = document.createElement('div');
        shadowRoot.appendChild(emotionRoot);
        const reactRoot = shadowRoot.appendChild(mountPoint);
    
        const jss = create({
          ...jssPreset(),
          insertionPoint: reactRoot,
        });
    
        const cache = createCache({
          key: 'css',
          prepend: true,
          container: emotionRoot,
        });
    
        render(
          <StylesProvider jss={jss}>
            <CacheProvider value={cache}>
              <ThemeProvider theme={theme}>
                <Demo />
              </ThemeProvider>
            </CacheProvider>
          </StylesProvider>,
          mountPoint
        );
      }
    }
    if (!customElements.get('my-element')) {
      customElements.define('my-element', MyWebComponent);
    }