'How to create a shared component library with MUI 5

I have a custom MUI 5 component library NPM package that exports some basic components built with MUI 5, exports theme file and wraps the theme provider into reusable component like so:

import { ThemeProvider } from 'styled-components';
import { StyledEngineProvider } from '@mui/material/styles';
import { theme as defaultTheme } from './theme';

function CustomThemeProvider(props: {
  theme: any;
  children: JSX.Element[];
}): JSX.Element {
  const { theme = defaultTheme, children } = props;
  return (
    <StyledEngineProvider injectFirst>
      <ThemeProvider theme={theme}>{children}</ThemeProvider>
    </StyledEngineProvider>
  );
}

export default CustomThemeProvider;

However when I try using this custom theme provider and the components in the project that uses this shared library, theme is not applied at all. This approach did work with MUI 4, but there was no Emotion layer at that point and setup was much simpler.

I have added all of these in peer dependencies for the shared component package to keep the references intact.

  "peerDependencies": {
    "@emotion/react": "^11.7.1",
    "@emotion/styled": "^11.6.0",
    "@mui/icons-material": "^5.2.4",
    "@mui/lab": "^5.0.0-alpha.60",
    "@mui/styles": "^5.2.3",
    "@mui/material": "^5.2.4",
    "@mui/styled-engine-sc": "^5.1.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "styled-components": "^5.3.3"
  },

But it does not make any difference.

Any ideas how to make it work, or any other approach that allows for a shared MUI 5 component library / theme to work, are welcome!



Solution 1:[1]

Not sure if this is the same situation, but in my case this was a problem of using npm link for local development.

NOTE: You could also run into this problem if @mui is not listed as a peerDependency of your component package.

TLDR: If your shared component library package is installed with its own copy of @mui (i.e. /node_modules/pkg/node_modules/@mui) then your shared ThemeProvider won’t work.


I have a very similar setup, with a shared npm package (@my/custom-components) with MUI as a peer dependency. In the consuming app, I used npm link @my/custom-components.

The problem with using link is it creates a symlink to your local package, which isn’t representative of the final build. When @my/custom-components is published, it only includes the package.json and a dist/ folder. But on my local machine, it includes all the source files, including the node_modules folder.

Normally, a package with peerDependencies won’t have its own sub node_modules folder, to ensure it uses the host app’s dependencies. But with this local setup, @my/custom-components was configuring its own copy of MUI, instead of the host app’s MUI:

// When using npm link

host-app
??? node_modules
?   ??? @my/custom-components
?   ?   ??? dist
?   ?   ??? node_modules
?   ?   ?   ??? @mui/material-ui  <== conflicting copy
?   ?   ??? package.json
?   ??? @mui/material-ui          <== host copy
??? src
??? package.json
// When installed normally

host-app
??? node_modules
?   ??? @my/custom-components
?   ?   ??? dist
?   ?   ??? package.json
?   ??? @mui/material-ui          <== only copy
??? src
??? package.json

To fix the problem, I:

  1. Removed the sym link (npm install in the host app)
  2. Manually built @my/custom-components (npm pack)
  3. Replaced host-app/node_modules/@my/custom-components with the built package.

Solution 2:[2]

I found a solution, what i did was,

create the theme project with custom build configurations with rollup. at their I added @mui/ as external packages. then export the mui5 theme provider.

then consumes it in the consumer project. consumer project need to have mui5 as dependencies. simple we are sharing the mui5.

sample rollup config ==>

import resolve from '@rollup/plugin-node-resolve';

import commonjs from '@rollup/plugin-commonjs'; import typescript from 'rollup-plugin-typescript2';

const packageJson = require('./package.json'); // eslint-disable-line @typescript-eslint/no-var-requires

export default {
  input: 'src/index.ts',
  external: [
    'react',
    'react-dom',
    'prop-types',
    '@emotion/react',
    '@emotion/styled',
    '@mui/lab',
    '@mui/icons-material',
    '@mui/material',
    '@mui/material/styles',
    '@mui/styles',
  ],
  output: [
    {
      file: packageJson.main,
      format: 'cjs',
      sourcemap: true,
    },
    {
      file: packageJson.module,
      format: 'esm',
      sourcemap: true,
    },
  ],
  plugins: [
    resolve(),
    commonjs(),
    typescript({ useTsconfigDeclarationDir: true }),
  ],
};

Solution 3:[3]

I have found a solution. Adding external theme provider was an overkill that I converted from MUI4.

In MUI 5 only thing that is needed is shared theme file. So place the theme file in the shared component library and import it in the projects that use it, but keep the theme providers local in all three codebases.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Vimukthi Jayasinghe
Solution 3 Dave Anders