6

The Problem:

I'm trying to create a website (web app) with React and Material UI, it's working just fine using npm. But when I try to make them as externals and import them through a CDN instead, I get an error with Material UI (React works fine).

My Code:

I linked CDNs in index.html like this:

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@material-ui/core/umd/material-ui.production.min.js"></script>
<script src="app.min.js"></script>

And in app.min.js, I imported them like this:

import { Component } from 'react';
import ReactDOM from 'react-dom';
import { Button } from '@material-ui/core';

My Attempt:

In the webpack.config.js, I tried the following (again, only Material UI causes an error):

  1. Using a string:

    externals: {
      'react': 'React',
      'react-dom': 'ReactDOM',
      '@material-ui/core': 'Button'
    }
    

    gives:

    Uncaught ReferenceError: Button is not defined

  2. Using an object:

    externals: {
      'react': 'React',
      'react-dom': 'ReactDOM',
      '@material-ui/core': {
        Button: '@material-ui/core'
      }
    }
    

    gives:

    TypeError: Cannot read property 'Button' of undefined

  3. Doing it manually, so Material UI isn't in externals:

    externals: {
      'react': 'React',
      'react-dom': 'ReactDOM'
    }
    

    Then removing minified Material UI code from app.min.js, this leaves the code incomplete and it doesn't run.

  4. Searched through GitHub issue and SO questions without any luck, some links:

Any idea how can I solve this?

Omar Einea
  • 2,478
  • 7
  • 23
  • 35

3 Answers3

7

Might be a little late to the party but I will add an answer which worked for me.

step 1:

add the script tag from unpkg. The difference between this and cdnjs is that unkpg have an option for umd. May or may not be an issue in your particular situation. It is for me. url: https://unpkg.com/@material-ui/core@4.11.0/umd/material-ui.production.min.js

script tag:

<script src="https://unpkg.com/@material-ui/core@4.11.0/umd/material-ui.production.min.js"></script>

step 1b:

add the font and font icon external resources as described in the material-ui docs:

material-ui getting started - installation guide

roboto font:

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

font icons:

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />

step 2:

destructure the elements you want to use from window.MaterialUI or use the square bracket notation (but unnecessary here since this package ditched the '-' char.

const { Button } = window['MaterialUI'];

step 3:

use the element as you 'normally' would

<Button variant="contained" color="primary">
  Primary
</Button>
cbutler
  • 833
  • 13
  • 36
5

Solution::

in webpack.config.js:

externals: {
  'react': 'React',
  'react-dom': 'ReactDOM',
  'material-ui': 'window["material-ui"]'
},

then in app.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Button } from 'material-ui';

Explanation:

If you check the cdn version of material ui js, you will find it exports its content in to material-ui namespace.

enter image description here

if you config webpack like:

'material-ui': 'material-ui'

webpack will compile it to:

enter image description here

which leads to the code looking for material and ui in global environment which does not exist. So we have to specify window["material-ui"] explicitly

loveky
  • 1,002
  • 1
  • 11
  • 18
0

I have just solved this issue after spending way too much time on this issue while trying to build an app using a micro-frontend architecture.

TL;DR;

The solution is to put the following in the webpack.config.js:

module.exports = {
  // rest of the config clipped for brevity
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM',
    'react-router-dom': 'ReactRouterDOM',
    '@material-ui/core': 'MaterialUI'
  }
};

Further details:

I was building a macro-frontend composed of multiple micro-frontends. Each of the micro-frontends was developed in React, exported as a web-component, and would be independently deployed such that each micro-frontend would be available via a URL like:

  • http://foo.example.com/main.js
  • http://bar.example.com/main.js
  • http://baz.example.com/main.js

These would be imported into the macro-app using a <script> tag.

The macro-app was hosted on a separate domain, e.g., http://example.com.

The issue I was facing was that Material UI (and possibly React as well) was being initialized multiple times in each of the micro-apps.

To avoid that, I had to externalize all these libraries using the webpack config block above.

I had to make 2 concessions.

  1. I did not use create-react-app and react-scripts to scaffold the macro-app because that setup would hide the webpack config. In order to expose the webpack config, I could either eject the CRA project, or use some other modules, such as react-app-rewired, etc. That felt like too much work. The downside of this was that I could not use BrowserRouter and had to accept using HashRouter for client-side routing.

  2. I could not use SvgIcon-based icons from @material-ui/icons, because I could not find a good way of externalizing Material UI Icons. Instead, I put in a link to Material UI Icons stylesheet, and opted to use Icon from @material-ui/core/Icon to render icons. using SvgIcon-based icons was causing Material UI to be initialized in the micro-apps too, which is what I was trying to avoid. One upside of the workaround is that Icon works with Font Awesome as well, so at least all icons would be written consistently in code.

Overall, I am happy with the end results.

Umar Farooq Khawaja
  • 3,925
  • 1
  • 31
  • 52