I'm building a library which bases on Material UI and want to publish components without bundling material-ui's code. I've tried adding the lib to externals but that seems to bundle it anyway. Here's my [email protected] config excerpt:
externals: {
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react',
},
'material-ui': {
root: 'MaterialUI',
commonjs2: 'material-ui',
commonjs: 'material-ui',
amd: 'material-ui',
},
},
React does not get bundled in, but Material UI does. The component that I'm trying to publish has the following import statement:
import Typography from 'material-ui/Typography';
One ugly way to solve it is to provide a config similar to:
'material-ui/Typography': {
root: 'material-ui/Typography',
commonjs2: 'material-ui/Typography',
commonjs: 'material-ui/Typography',
amd: 'material-ui/Typography',
},
But that's just not maintainable. Another option would be to follow https://stackoverflow.com/a/41823893/2066118 but I'm not sure whether that's a recommended way, or whether it would work with v1
@maciej-gurban We use Rollup to bundle the umd package now:
https://github.com/mui-org/material-ui/blob/4e4dbb7aed901b523fcc24d6cfd2d8e3369e3a6b/packages/material-ui/scripts/rollup.config.js#L28-L39
But we used to use webpack:
https://github.com/mui-org/material-ui/blob/7b120407ee7467c177ecd38f64ec232a2fd09cec/packages/material-ui/scripts/umd.webpack.config.js#L1-L75
But I'm asking how to prevent material-ui from being bundled by webpack into a final bundle, not how to roll out my own component library build. I don't see that anywhere in the code you posted. What am I missing?
@maciej-gurban Use the same pattern that we use to prevent react
& react-dom
from being bundled. I don't think the question is specific to Material-UI.
鈿狅笍 The material-ui
package name is deprecated, use @material-ui/core
.
Anyway, as far as I understand your problem, it's a webpack related issue. I'm using Next.js, they seem to be able to build an efficient vendor bundle with sub components of Material-UI.
I already know how to prevent react
and react-dom
from being bundled. I'm asking how to prevent material-ui
from being bundled into my own custom library. The way to write imports suggested by the docs, like import Button from 'material-ui/Button
, will NOT work with externals config that specifies material-ui
as an external. To make it work, I'd need to create material-ui/Button
external, then material-ui/Table
external and so on.
Since your library is quite popular I was hoping somebody else arrived at the same problem
Posted my comment before refreshing and seeing your other comments. I'll checkout Next.js. Thank you
I've gone and tried using rollup to package my component library (by using the exact same code material-ui
uses package the lib). Had some issues with UMD builds, but other than that it worked fine.
With fresh mind in the morning I tried to resolve the issue still using webpack. In case somebody stumbles upon the same issues, the fix is trivially simple. You can use a regexp to filter out your externals. In my case, the config looks like so:
externals: [
{
react: {
root: 'React',
commonjs2: 'react',
commonjs: ['react'],
amd: 'react',
},
},
/@material-ui\/core\/.*/,
],
The original issue which sent me on this spiral of madness was trying to use storybook to render components of my application which use my external component library, packaged with webpack. This will work if you're importing them from node_modules (as will be the in most cases), but I wanted to load them from disk - to verify whether packaged component behaves correctly before publishing the component library.
This won't work because webpack will try to parse their code (and you'll end up with errors about module
being undefined, or similar module-related error). The same would happen if your babel-loader didn't ignore node_modules. However, since storybook has their own webpack config, you'd need to find a way to tell storybook's babel-loader not to parse your on-disk packages components. This seems to be possible only by passing a full webpack config from your app to storybook, but in my case that's not an option because atm storybook is at webpack 2, while my app at webpack 4.
EDIT: changed the regexp from /@material-ui\/core\/*./,
to /@material-ui\/core\/.*/,
(switch .the places of *
and .
) to match the whole path rather than jus the beginning
Thanks for sharing the solution. Hopefully, it will help other :).
Works like a charm -- Thanks !
Tried with above regex, but it does not work for me.
Also tried
json
'@material-ui/core': {
root: '@material-ui/core',
commonjs2: '@material-ui/core',
commonjs: '@material-ui/core',
amd: '@material-ui/core',
},
Versions using
json
"@material-ui/core": "^1.3.0",
"webpack": "^4.11.1",
Anyone else got this to work?
This works for me, add all these to externals:
"@material-ui/core",
"@material-ui/icons",
/@material-ui\/core\/*./,
/@material-ui\/icons\/*./,
I add /@material-ui/core/*./ into webpack.config.js, but I have to modify from "import TextField from '@material-ui/core/TextField';" to "import {TextField} from '@material-ui/core';" at the same time.
But use "import {TextField} from '@material-ui/core';" will import all library, so result is output file size is almost no decrease.
Please tell me what did I miss?
I add /@material-ui/core/*./ into webpack.config.js, but I have to modify from "import TextField from '@material-ui/core/TextField';" to "import {TextField} from '@material-ui/core';" at the same time.
But use "import {TextField} from '@material-ui/core';" will import all library, so result is output file size is almost no decrease.
Please tell me what did I miss?
have you solved it锛烮 am not sure how to import UI components in js files. It always cracks.
It's tough to help without seeing any webpack config code. You could setup some repo to showcase the issue.
No import changes should be necessary. All you need to do is to ignore import paths that match MUI on webpack externals
It's tough to help without seeing any webpack config code. You could setup some repo to showcase the issue.
No import changes should be necessary. All you need to do is to ignore import paths that match MUI on webpack externals
Thanks for reminding me. I use the same way to config webpack as yours. Maybe there is something wrong with my html file. I post some sections of this file.
<head>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@material-ui/[email protected]/umd/material-ui.development.js" crossorigin="anonymous"></script>
</body>
And I have tried the following four ways to import UI components:
1. import AppBar from '@material-ui/core';
2. import {AppBar} from '@material-ui/core'
3.import AppBar from '@material-ui/core/AppBar';
4.import {AppBar} from '@material-ui/core/AppBar'
they all throw errors that indicate AppBar is undefined.
@Marckon I think you're having an issue different from the rest of guys here. The issue others are having is to produce a build that would not contain MUI code.
It seems you're trying to load MUI from CDN which is discouraged. How setup is organized, and how I'd expect most people to be using MUI is to use npm or yarn to install dependencies (and keep them in package.json) in which case the imports you listed would work.
Working with libraries loaded from CDN is different. You need to import your modules from window['material-ui']
. Here's how: https://github.com/mui-org/material-ui/blob/master/examples/cdn/index.html
@maciej-gurban Thank you. It's so nice of you馃榿
I had trouble getting regex /@material-ui\/core\/.*/
to work with import TextField from '@material-ui/core/TextField';
. Getting error
Failed to minify the code from this file:
external "@material-ui/core/Typography":1
To fix this, I used a function:
webpackConfig.externals = [
{
'react': 'React',
'react-dom': 'ReactDOM',
},
externalMaterialUI
];
/** Callbacks with global UMD-name of material-ui imports */
function externalMaterialUI (_, module, callback) {
var isMaterialUIComponent = /^@material-ui\/core\/([^/]+)$/;
var match = isMaterialUIComponent.exec(module);
if (match !== null) {
var component = match[1];
return callback(null, `window["material-ui"].${component}`);
}
callback();
}
Webpack documentation on function externals.
BTW, \/*.
matches
\/*
: slash zero to n times, then.
: any character exactly one time,which is probably not what one wants.
Edit: Had to use window["material-ui"].component
, or else webpack will try to set external module equal to module.exports = material-ui.component
(material minus ui.component).
@maciej-gurban THANK YOU for posting the solution. You probably saved me hours of work
Note for anyone wrestling with a similar issue with Rollup: the external
option unfortunately will not accept regexps like Webpack, though it does allow you to pass a function, so you can implement whatever sort of logic you want:
external: (moduleName) => {
return isModuleExternal(moduleName);
},
Inside this same function you can dynamically construct your globals
to pass to the output
- e.g. a function to convert the module name '@material-ui/core/Tooltip' to 'MaterialUiCoreTooltip', then add the latter to the globals
object - e.g.
let globals = {};
...
output: [
{
file: './dist/components-umd.js',
format: 'umd',
name: 'components',
globals
},
{
file: './dist/components-esm.mjs',
format: 'esm',
globals
}
],
external: (moduleName) => {
const isExternal = isModuleExternal(moduleName);
if (isExternal) {
// dynamically adds module name to globals
// e.g. { '@material-ui/core/Tooltip': 'MaterialUiCoreTooltip' }
globals[moduleName] = moduleNameToSanitizedGlobalName(moduleName);
}
return isExternal;
},
@davidcalhoun do you have more details for how to validate is an external isModuleExternal
and for moduleNameToSanitizedGlobalName
,
Material UI umd only exposes MaterialUI globaly how do you access to the specific imports?
The following worked for me by combining ideas from @maciej-gurban and @felixknox
externals: [
/@material-ui\/.*/,
]
I only needed to externalize @material-ui/core
for some testing. The following worked well for UMD
build with Webpack. Notice the similarity with react and react-dom.
externals: {
react: {
root: "React",
commonjs: "react",
commonjs2: "react",
amd: "react",
},
"react-dom": {
root: "ReactDOM",
commonjs: "react-dom",
commonjs2: "react-dom",
amd: "react-dom",
},
"@material-ui/core": {
root: "MaterialUI",
commonjs: "@material-ui/core",
commonjs2: "@material-ui/core",
amd: "@material-ui/core",
},
},
Why
root: "MaterialUI"
?
Because it was in the CDN links posted here.
Most helpful comment
I've gone and tried using rollup to package my component library (by using the exact same code
material-ui
uses package the lib). Had some issues with UMD builds, but other than that it worked fine.With fresh mind in the morning I tried to resolve the issue still using webpack. In case somebody stumbles upon the same issues, the fix is trivially simple. You can use a regexp to filter out your externals. In my case, the config looks like so:
The original issue which sent me on this spiral of madness was trying to use storybook to render components of my application which use my external component library, packaged with webpack. This will work if you're importing them from node_modules (as will be the in most cases), but I wanted to load them from disk - to verify whether packaged component behaves correctly before publishing the component library.
This won't work because webpack will try to parse their code (and you'll end up with errors about
module
being undefined, or similar module-related error). The same would happen if your babel-loader didn't ignore node_modules. However, since storybook has their own webpack config, you'd need to find a way to tell storybook's babel-loader not to parse your on-disk packages components. This seems to be possible only by passing a full webpack config from your app to storybook, but in my case that's not an option because atm storybook is at webpack 2, while my app at webpack 4.EDIT: changed the regexp from
/@material-ui\/core\/*./,
to/@material-ui\/core\/.*/,
(switch .the places of*
and.
) to match the whole path rather than jus the beginning