当使用Vite作为bundler时,我遇到了在shadow DOM中挂载MaterialUI样式的问题。我已经按照MaterialUI文档中概述的步骤进行了操作(
https://mui.com/material-ui/guides/shadow-dom/
)通过合并shadow DOM来解决CSS出血问题。在本地,在Vite的开发模式下,一切都按预期进行。
然而,当我构建web组件并将MaterialUI与其他资源一起外部绑定时,样式似乎安装在shadow DOM之外,导致了意外的行为。我参考了上的文档
https://emotion.sh/docs/@emotion/cache#container
,这建议为shadow DOM上下文中的样式提供一个容器。
正如文档中所建议的,我还调整了主题,为MaterialUI中的某些组件指定了一个新的安装点。
奇怪的是,在开发模式下,一切都能在本地按预期工作,但在构建web组件后,样式安装不正确。我怀疑这个问题可能与MaterialUI的外部绑定有关,因为将其从外部依赖项中删除并直接与我的web组件绑定可以解决这个问题。
如果您对如何在外部捆绑MaterialUI和Vite时确保阴影DOM中的正确样式安装有任何见解或建议,我们将不胜感激。非常感谢。
我能够制作一个最小的例子:
https://github.com/beuluis/MUI-Shadow-DOM-CacheProvider-Broken-Theme
vite.fig.ts
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import viteTsconfigPaths from 'vite-tsconfig-paths';
const environmentName = process.env.npm_package_name;
if (!environmentName) {
throw new Error('Missing package name in environment variables. Vue CLI should set this.');
}
const name = environmentName.split('/').pop();
export default defineConfig({
plugins: [react(), viteTsconfigPaths()],
build: {
sourcemap: true,
rollupOptions: {
external: ['@emotion/react', '@emotion/styled', '@mui/material', 'react', 'react-dom'],
output: {
format: 'iife',
assetFileNames: `${name}.[hash].[extname]`,
chunkFileNames: `${name}.[hash].chunk.js`,
entryFileNames: `${name}.min.js`,
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'@mui/material': 'MaterialUI',
'@emotion/react': 'emotionReact',
'@emotion/styled': 'emotionStyled',
},
},
},
outDir: 'dist',
},
});
应用.tsx
import { StrictMode, Suspense } from 'react';
import { Route, Routes } from 'react-router-dom';
import createCache from '@emotion/cache';
import { CacheProvider } from '@emotion/react';
import { createTheme, ThemeProvider } from '@mui/material';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Loading } from '@company/partner-ui'; // company component lib
import { theme } from '@company/partner-ui/dist/theme'; // company theme
import { NamespacedRouter } from '@company/react-namespaced-router'; // company router to support specific url handling
import { useWidget } from '@company/web-component-core'; // company core lib for web-components. takes over mounting and attribute watching etc
import { ApiContextProvider } from './contexts/api';
import { useLanguage } from './hooks/useLanguage';
import { PartnerData } from './pages/PartnerData/PartnerData';
export interface AppProperties {
readonly apiBaseUrl: string;
readonly locale: string;
readonly routerName?: string;
readonly basename?: string;
}
export const App = ({
apiBaseUrl,
basename,
locale,
routerName = 'partner-data-frontend',
}: AppProperties) => {
useLanguage(locale);
// This all is in out lib that provides also useWidget(); I wrote a rough example how it will create those containers.
// const root = this.shadowRoot ?? this;
// const containerElement = create('root');
// const stylesContainer = create('styles');
// const customStylesPoint = create('custom-styles');
// root.append(stylesContainer, containerElement);
// stylesContainer.append(customStylesPoint);
const { containerElement, stylesContainer } = useWidget();
const cache = createCache({
key: 'css',
prepend: true,
container: stylesContainer,
});
const shadowDomTheme = createTheme(theme, {
components: {
MuiPopover: {
defaultProps: {
container: containerElement,
},
},
MuiPopper: {
defaultProps: {
container: containerElement,
},
},
MuiModal: {
defaultProps: {
container: containerElement,
},
},
},
});
const queryClient = new QueryClient();
return (
<StrictMode>
<CacheProvider value={cache}>
<ThemeProvider theme={shadowDomTheme}>
<Suspense fallback={<Loading />}>
<QueryClientProvider client={queryClient}>
<NamespacedRouter name={routerName} basename={basename}>
<ApiContextProvider baseUrl={apiBaseUrl}>
<Routes>
<Route path="/">
<Route index element={<PartnerData />} />
</Route>
</Routes>
</ApiContextProvider>
</NamespacedRouter>
</QueryClientProvider>
</Suspense>
</ThemeProvider>
</CacheProvider>
</StrictMode>
);
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@mui/material@5/umd/material-ui.production.min.js"></script>
<script src="https://unpkg.com/@emotion/react@11/dist/emotion-react.umd.min.js"></script>
<script src="https://unpkg.com/@emotion/styled@11/dist/emotion-styled.umd.min.js"></script>
<title>demo page</title>
<script type="module" src="src/App.tsx"></script>
</head>
<body>
<!-- This gets mounted by our company web-component-core lib -->
<partner-data-frontend api-base-url="" locale="en-GB"></partner-data-frontend>
</body>
</html>
我试过:
-
测试不同的捆绑器。失败,因为此捆绑程序不支持所需的所有内容
-
尝试最小化代码。失败
-
尝试绑定MUI,但不将其外部化。有效,但不是真正的解决方案
-
尝试了不同的安装容器。失败
-
尝试过但没有使用备注