Search code examples
material-uiweb-componentpreact

use material ui with Preact


I have this web component created with Preact and I'm trying to use Material ui to style it. Reading Preacts documentation, https://preactjs.com/about/libraries-addons/, it claims to have support for material ui. However when Im running the app locally it gives an error as material ui has a dependency to react.

I installed mui using: npm install @mui/material @emotion/react @emotion/styled

Then I copied an empample from mui docs to make the moste basic slider:

import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Slider from "@mui/material/Slider";

export default function ContinuousSlider() {
  return (
    <Box sx={{ width: 200 }}>
      <Slider disabled defaultValue={30} aria-label="Disabled slider" />
    </Box>
  );
}

I get the html for the slider in my shadow-dom but it's commpletely unstyled so I can't see it without inspecting the code.

I also tried usint preact-material-components but the result was the same plus I'm a bit reluctant to use that since it hasn't benn updated in four years.

The project is set up with Vite and my vite.config.js looks like:

import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [preact()],
});

EDIT:

I tried to make a really simple reproduction of the files contained here:

I have this web component built with Preact and I'm tryng to add Mui styling to it. It works fine until I turn on the shadow root.

Basically I have the following files. 'index.html':

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="color-scheme" content="light dark" />
    <title>Slider</title>
</head>

<body>
    <script type="module" src="/index.jsx"></script>

    <slider-component></slider-component>
</body>

</html>

index.jsx:

import register from "preact-custom-element";
import Slider from "./src/Slider";

register(Slider, "slider-component", [], { shadow: true });

and Slider.jsx

import Slider from "@mui/material/Slider";

export default function ContinuousSlider() {
  return <Slider defaultValue={30} aria-label="Disabled slider" />;
}

Then I have jsconfig.json which looks like:

{
    "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "noEmit": true,
        "allowJs": true,
        "checkJs": true,

        /* Preact Config */
        "jsx": "react-jsx",
        "jsxImportSource": "preact",
        "skipLibCheck": true,
        "paths": {
            "react": ["./node_modules/preact/compat/"],
            "react-dom": ["./node_modules/preact/compat/"]
        }
    },
    "include": ["node_modules/vite/client.d.ts", "**/*"]
}

and package.json that looks like:

{
    "private": true,
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "preview": "vite preview"
    },
    "dependencies": {
        "@emotion/react": "^11.11.4",
        "@emotion/styled": "^11.11.5",
        "@mui/material": "^5.15.15",
        "preact": "^10.13.1",
        "preact-custom-element": "^4.3.0",
        "react": "npm:@preact/compat",
        "react-dom": "npm:@preact/compat"
    },
    "devDependencies": {
        "@preact/preset-vite": "^2.5.0",
        "eslint": "^8.56.0",
        "eslint-config-preact": "^1.3.0",
        "vite": "^4.3.2"
    },
    "eslintConfig": {
        "extends": "preact"
    }
}

plus vite.config.js that looks like:

import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [preact()],
});

The problem occurs when I change from { shadow: false } to { shadow: true } in index.jsx


Solution

  • I got it working following Material ui's document on Shadow DOM, useing CacheProvider and createCache from emotion.

    I also had to register the web-component with {shadow: false}and then attach a shadow to it that I could use in the cache. Finally I rendered my App inside the CashProvidertargeting the newly created shadow root element.