preactjs / preact

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.
https://preactjs.com
MIT License
36.83k stars 1.95k forks source link

MUI component wrapped in a custom element passes invalid type to `document.createElement` #3703

Closed valdemarrolfsen closed 2 years ago

valdemarrolfsen commented 2 years ago

Describe the bug I am trying to build a simple web component using preact and MUI based on the provided example from:

https://github.com/mui/material-ui/tree/master/examples/preact

I am able to build the web component successfully but when I render it in the browser it crashes due to:

Failed to execute 'createElement' on 'Document': The tag name provided ('[object Object]') is not a valid name. at: https://github.com/preactjs/preact/blob/e70238f2d9572688d4a8fc2de1892e837e6e9122/src/diff/index.js#L362

Preact assumes that newNode.type is always a string but when using MUI the node type is actually an object.

Not sure if using third party libraries in preact is supported when building web-components, but thought it was worth it to report it :)

To Reproduce

Try building a simple web component using MUI and load it in the browser:

import Button from "@mui/material/Button";
import register from "preact-custom-element";

export interface ButtonProps {
  label: string;
  testId: string;
  onClick: () => void;
}

function UIButton({ onClick, label, testId }: ButtonProps) {
  return (
    <Button data-testid={testId} onClick={onClick}>
      {label}
    </Button>
  );
}

register(UIButton, "ui-button", ["label", "onClick"], { shadow: true });

Expected behavior The MUI button should load as expected inside the web-component

marvinhagemeister commented 2 years ago

I'm unable to reproduce the described error with the code provided in the original comment. The button renders as expected, regardless of the build tool I use (tried the CRA setup in the link and vite).

@valdemarrolfsen can you share an example of how your using your web component?

valdemarrolfsen commented 2 years ago

Hmm, that is wierd. I build with tsup (esbuild) with the following configuration:

import { defineConfig } from "tsup";
import alias from "esbuild-plugin-alias";

export default defineConfig({
  minify: false,
  dts: true,
  sourcemap: true,
  target: "esnext",
  esbuildPlugins: [
    alias({
      react: require.resolve("preact/compat"),
      "react-dom": require.resolve("preact/compat"),
      "react/jsx-runtime": require.resolve("preact/jsx-runtime"),
      "@mui/styled-engine": require.resolve("@mui/styled-engine-sc"),
    }),
  ],
  format: ["esm", "cjs", "iife"],
});
valdemarrolfsen commented 2 years ago

I render the component using a standard create-react-app:

import "./App.css";
import "@ignite-analytics/ui-kit";

function App() {
  return (
    <div className="App">
      <ui-button label="Button" onClick={() => console.log("Test")} />
    </div>
  );
}

export default App;
marvinhagemeister commented 2 years ago

@valdemarrolfsen rather than supplying more information piece by piece, can you share a small repository where the problem is reproducible? That way we can ensure that we're running the same setup as you on your machine.

valdemarrolfsen commented 2 years ago

Sure, sorry about that. Here is a repo where the issue can be reproduced:

https://github.com/valdemarrolfsen/preact-button

rschristian commented 2 years ago

In your repo you're not setting up aliases in CRA as the original example does. This results in the unexpected data shape being given to Preact. If you set up your aliases everything should work fine.

valdemarrolfsen commented 2 years ago

Hmm, but in ts-config I add:

esbuildPlugins: [
    alias({
      react: require.resolve("preact/compat"),
      "react-dom": require.resolve("preact/compat"),
      "react/jsx-runtime": require.resolve("preact/jsx-runtime"),
    }),
],

This should be the equivalent of the original example using esbuild?

rschristian commented 2 years ago

That's your component.

Your app is React. You haven't set up the Preact aliases in CRA.

Edit: To be clear, preact/compat allows you to use React components in Preact. What you're trying to do is go the other way and use a Preact component in React.

valdemarrolfsen commented 2 years ago

I see, then I understand the issue, thank you very much :)

In general, I think that web components should be framework agnostic and work regardless of which web framework they are used with. But I know there are still many issues with React and web components in general.

Anyway, thanks for the help, then I will find another way of doing this 😄

rschristian commented 2 years ago

If you're not meaning to use Preact as your app framework then you should alternatively be able to bundle the MUI component into your custom element. I had assumed that's what you intended to do since you linked to an example doing just that.

If you don't inline or alias in your app then the MUI component is still a React component, and you can't give that to Preact. Inlining isn't without trade offs but is the other option.

valdemarrolfsen commented 2 years ago

Yes, that makes sense. It will make the bundle quite big, but it seems it's the only option. Thanks for all the help :)