mui / material-ui

Material UI: Ready-to-use foundational React components, free forever. It includes Material UI, which implements Google's Material Design.
https://mui.com/material-ui/
MIT License
92.47k stars 31.86k forks source link

cannot use default import in a component library #35535

Open smallet-microbiome opened 1 year ago

smallet-microbiome commented 1 year ago

Duplicates

Latest version

Steps to reproduce 🕹

Link to live example:

Steps:

  1. create a basic lib component with this dummy component (with a default import):
import Paper from  '@mui/material/Paper';
import React from 'react';

export interface DummyProps {
  message: string;
}

const Dummy = ({ message }: DummyProps) => {

  return (
    <Paper>{message}</Paper>
  );
};

export default Dummy;
  1. Set @mui and @emotion as a peer dependency (see config file below)

  2. rollup (see config below) and publish the lib

  3. Create a simple react application, with a component SuperDummy using Dummy from the lib

  4. Write a unit test using SuperDummy => the test should fail with an error like React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.

Current behavior 😯

On one side I have a component library using react, mui and emotion as peerDep.

package.json

 "peerDependencies": {
    "@emotion/react": "^11.10.5",
    "@emotion/styled": "^11.10.5",
    "@mui/icons-material": "^5.10.15",
    "@mui/material": "^5.10.15",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  }

rollup config (note the regexp for @emotion and @mui)

  external: [
    'react',
    'react-dom',
    /@emotion\/.*/,
    /@mui\/.*/,
  ],
  output: [
    {
      file: 'dist/index.cjs.min.js',
      format: 'cjs',
      sourcemap: true,
      plugins: [terser()],
    },
    {
      file: 'dist/index.esm.min.js',
      format: 'esm',
      sourcemap: true,
      plugins: [terser()],
    },
  ],
  plugins: [
    resolve({ browser: true }),
  ...
    typescript({ tsconfig: './tsconfig.json' }),
  ],
},

Lib is published with npm.

On the other side, I have a simple react application (storybook, webpack5) Nothing special on the config on this side.

When I run storybook, it works fine. When I run the app, it works fine too.

But when I run the jest unit tests, I get this error:

Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.

I managed to surface that the error occurs as soon as I have a default import in the component library. Like import Paper from '@mui/material/Paper instead of import { Paper } from '@mui/material';

For those basic imports, I can leave with the second import, but then, I need to use icons too (or styled from @emotion/styled)

And these imports are only default: import Circle from '@mui/icons-material/Circle';

As a workaround, I made this wrap and now it works but it's not ideal:

import Circle from '@mui/icons-material/Circle';
import { SvgIcon } from '@mui/material';

const normalizeIcon = (Icon: typeof SvgIcon) => {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  return ((Icon as any).default ? (Icon as any).default : Icon) as typeof SvgIcon;
};

  const CircleIcon = normalizeIcon(Circle)`

Expected behavior 🤔

I would like to be able to use default imports without any error.

For instance: import Circle from '@mui/icons-material/Circle';

Context 🔦

I will have several applications and I want to share theme and components. Hence the creation of the component library. I want to be able to use the component library from the apps whatever the way the component library makes it's imports.

Your environment 🌎

npx @mui/envinfo I used FireFox. ``` System: OS: Linux 6.0 Fedora Linux 36 (Workstation Edition) Binaries: Node: 18.1.0 - /usr/bin/node Yarn: Not Found npm: 8.8.0 - /usr/bin/npm Browsers: Chrome: Not Found Firefox: 107.0.1 npmPackages: @emotion/react: ^11.10.5 => 11.10.5 @emotion/styled: ^11.10.5 => 11.10.5 @mui/base: 5.0.0-alpha.110 @mui/core-downloads-tracker: 5.11.0 @mui/icons-material: ^5.10.15 => 5.11.0 @mui/material: ^5.10.15 => 5.11.0 @mui/private-theming: 5.11.0 @mui/styled-engine: 5.11.0 @mui/system: 5.11.0 @mui/types: 7.2.3 @mui/utils: 5.11.0 @types/react: ^18.0.3 => 18.0.26 react: ^18.2.0 => 18.2.0 react-dom: ^18.2.0 => 18.2.0 typescript: ^4.6.3 => 4.9.4 ```
mnajdova commented 1 year ago

It doesn't look like this bug report has enough info for one of us to reproduce it.

Please provide a CodeSandbox (https://material-ui.com/r/issue-template-latest), a link to a repository on GitHub, or provide a minimal code example that reproduces the problem.

Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve

github-actions[bot] commented 1 year ago

Since the issue is missing key information and has been inactive for 7 days, it has been automatically closed. If you wish to see the issue reopened, please provide the missing information.

forgetso commented 10 months ago

Related: https://stackoverflow.com/questions/76687404/component-throwing-error-type-is-invalid-when-imported-to-another-project/76908656#76908656

forgetso commented 10 months ago

@mnajdova - it's not possible to provide a minimal example with CodeSandbox. This error only appears when bundling with webpack, as the original poster mentioned.

smallet-microbiome commented 10 months ago

I confirm the normalizeIcon work around makes the job, but it would be great not to have to use it.

FunkMonkey commented 4 months ago

For people that only have this problem in their jest tests, you can use the moduleNameMapper trick from here to simply ignore the icon modules:

{
  "jest":{
     "moduleNameMapper": {
      "@mui/icons-material/.*": "<rootDir>/__mocks__/fileMock.js",
    },
  }
}

Beware: this may produce problems for screenshot and snapshot testing.