Closed jmelendez-cbs closed 2 years ago
It could be because you're not specifying a version requirement for the @material-ui/core
dependency, and you're getting a different copy which introduces the styling discrepancies.
When Webpack tries to determine which copy of that library to use, it will find the highest version available (unless told otherwise). If @material-ui/lab
or @material-ui/pickers
have the @material-ui/core
package listed as a peerDependency
, then you could be getting a version of the library that causes the problem you're seeing.
@jmelendez-cbs I am having the same issue, even when I specify the shared dependencies with their version!
@jmelendez-cbs I am having the same issue, even when I specify the shared dependencies with their version!
As mentioned in #548 sharing "@material-ui/" as singleton solve the issue, but how could we know which library has to be singleton !?
It could be because you're not specifying a version requirement for the
@material-ui/core
dependency, and you're getting a different copy which introduces the styling discrepancies.When Webpack tries to determine which copy of that library to use, it will find the highest version available (unless told otherwise). If
@material-ui/lab
or@material-ui/pickers
have the@material-ui/core
package listed as apeerDependency
, then you could be getting a version of the library that causes the problem you're seeing.
I went through and made sure the peerDependencies were shared that material/lab required but still no luck.
@jmelendez-cbs I am having the same issue, even when I specify the shared dependencies with their version!
As mentioned in #548 sharing "@material-ui/" as singleton solve the issue, but how could we know which library has to be singleton !?
Yeah, from what I understand, the libraries that use a context should be shared as singletons. I think the issue here could be the way that material-ui imports and exports some components for the lab. In particular the Button component. I traced my issue back to the Button.
An extra styled sheet for ButtonBase was being added to the
of my index.html doc which was over-writing the styles.I have not solved this issue yet.
@jmelendez-cbs have same issue with Floating Action Button from Mui-v4.10.0 that use the same ButtonBase component, more deep dive on how Module Federation deal with large Css Framework like Mui is needed !( it's beyond my scope :( )
How to know if a lib needs to be a singleton. Mainly you have to see what happens. In general anything that uses react context needs to be a singleton. Things like redux
As more and more libraries rely on react context, it will cause the list of singletons to be quite long. Is there some kind of plan to deal with it more elegantly?
Not the concern of Webpack. We compile code that's it.
Ways to work around the problem tho, ship library as remote themselves. So it's centrally owned.
I could write a plugin that finds any use of react context in its parser and set those as singleton. However that's beyond the scope of my time and capacity. But it's possible to traverse the module graph automatically
Although this has been marked as closed, i would like to continue and try to understand the issue. Is this material-ui specific thing?? We don't notice this with other libraries that we're using.
The effects of this issue are understood. It seems to download and load the same component twice (For example, Button or Typography) and if this happens at various times of the page, it will cause the component to create styles once more and add them to
. The big question is why it does that.To my understanding, the solution of using material-ui/core/
(With a Trailing Slash) will make everything under this as a shared component individually, which in turn creates a whole bunch of files (As they are all shared individually).
Although this should not make an issue since the browser can cache them, and initially if it uses http2 it will download a bunch of them together, it still creates a bunch more files.
BUT, this does solve the issue since webpack notices the file paths are exactly the same..?
Can it be that, due to different tree-share combinations of those components, it bundles Button in different files between the remotes and therefore forces the loading of that component once more?
Although this has been marked as closed, i would like to continue and try to understand the issue.
Is this material-ui specific thing?? We don't notice this with other libraries that we're using.
The effects of this issue are understood. It seems to download and load the same component twice (For example, Button or Typography) and if this happens at various times of the page, it will cause the component to create styles once more and add them to
.The big question is why it does that.
To my understanding, the solution of using
material-ui/core/
(With a Trailing Slash) will make everything under this as a shared component individually, which in turn creates a whole bunch of files (As they are all shared individually).Although this should not make an issue since the browser can cache them, and initially if it uses http2 it will download a bunch of them together, it still creates a bunch more files.
BUT, this does solve the issue since webpack notices the file paths are exactly the same..?
Can it be that, due to different tree-share combinations of those components, it bundles Button in different files between the remotes and therefore forces the loading of that component once more?
Happy to continue this. From a comprehensive standpoint.
Here's how I understand the issue. That package is like a highly interconnected monorepo of other packages. So /lab and /core most likely import something like /context-bus or /themes as well.
And it's those that need to be singletons - it's not a normal package where everything is just in one package. You're installing many material sub dependencies when using one of the higher level ones.
So the problem ends up as, there's still something both those packages rely on, another node module from materials repository organization.
While you're federating the high level, one, there are likely internals that also need to be shared, but are not necessarily immediately obvious because they're mostly used internally. By higher level packages you import from. Hence why my usual suggestion is if you don't know what all is actually shared. Or used. Sharing "@mui/" is going to share any requires that starts with mui, regardless of where or how deep the import is.
Then you could console log webpack share scopes. And see all the packages actually being used by the apps - manually add them one by one till you find which one actually needs to be shared as a singleton. But doing the trailing slash is a great way to flush out all the things actually imported. And can choose.
Like sentry, similar deal. I can "@sentry/" and see that share scope contains /hub /tracing /browser /client /core.
This is a similar issue some big npm packages have. So In a really tough case I can just use the trailing slash and that'll probably work but can make builds a little larger. So using it just to flush out what are all the actual dependencies that are used and console log, then process of elimination
I know it's not great, and I will try to implement a "hinting" capability into Medusa so it can show you this stuff in like a GUI app. So I know it's not super straightforward to just find something - but you can get the info. I'm hoping to improve the tools to help give you insights about it
const federationConfig = {
name: "stores_mfe",
remotes: {
coco: 'coco'
},
shared: {
"@mui/": {}
}
};
// in app code
console.log(__webpack_share_scopes__.default)
Thanks for the detailed explanation, it totally makes sense.
Do i understand correctly, that the singleton indication should be the same for all remotes? Meaning that its not possible for one remote to use @mui/core
and the other @mui/
. Since it would mean that one remote has the "root" and the other has all the "sub packages", and it would create a "conflict". __webpack_share_scopes__.default
also shows both of versions.
In any case, i've tried adding all dependencies from the material ui package.json files as singleton - just in case. But the only way the issue seems to go away is when i use the specific component inside material-ui.
Since the only working solution (At least for me, and for now) is using the @mui/
. Is there anyway to make them go into some shared chunks? Meaning instead of 1 file per component, somehow create chunks of components based on.. something? ex. Amount of appearances throughout the build in each remote or..?
Thanks for the detailed explanation, it totally makes sense.
Do i understand correctly, that the singleton indication should be the same for all remotes? Meaning that its not possible for one remote to use
@mui/core
and the other@mui/
. Since it would mean that one remote has the "root" and the other has all the "sub packages", and it would create a "conflict".__webpack_share_scopes__.default
also shows both of versions.In any case, i've tried adding all dependencies from the material ui package.json files as singleton - just in case. But the only way the issue seems to go away is when i use the specific component inside material-ui.
Since the only working solution (At least for me, and for now) is using the
@mui/
. Is there anyway to make them go into some shared chunks? Meaning instead of 1 file per component, somehow create chunks of components based on.. something? ex. Amount of appearances throughout the build in each remote or..?
Singleton should be across all remotes. Since you don't want state to tear.
Just want to update here that i believe I've found the "culprit" in this whole material-ui situation.
tldr; Define shared with the following configuration. Look bellow for variations and explanations
'@material-ui/core': {
singleton: true,
},
'@material-ui/core/styles': {
singleton: true,
},
'@material-ui/core/utils': {
singleton: true,
},
'@material-ui/lab': {
singleton: true,
},
'@material-ui/styles': {
singleton: true,
},
'@material-ui/pickers': {
singleton: true,
}
What does this create? It will create 1 file for the whole material-ui/core package with 2 smaller files for the core/styles and core/utils which should be marked as singletons. Why? Because they seem to affect styling indirectly. Same goes for material-ui/styles itself.
Variation: If you want to split the material-ui/core components into 1 file per component, you could do:
'@material-ui/core/': { singleton: true },
But I did not want to go that way to not increase the overall file and bundlesize (While taking into account that users will have to download the whole core package even if they use only 1 component)
Same goes for the lab and/or icons package.
Note: In our config, we originally had the following section in Babel config (Now removed):
[
'babel-plugin-import',
{
libraryName: '@material-ui/core',
libraryDirectory: 'esm',
camel2DashComponentName: false,
},
'core',
]
This is following the guide here. However, what this actually does, is converts the import systax in the output. So exposing the core package as a whole is not possible after this, and you're force to use the variation option that i've mentioned above.
I assume similar things can be done for v5.
Send pr for example of material ui if you think it would be useful to have in my repo
Thanks for all of the detail @slavab89 and @ScriptedAlchemy.
We're experiencing the same issue in our app. No matter how much we try and massage the federation shared config we get duplicate styles being loaded in certain situations (when MUI is being instantiated twice). We have tried both the "catch-all" variation with @material-ui/
and targeting individual/all packages to no avail.
It looks as though the @material-ui/lab
package is the culprit in our case (not that that is the concern of Webpack). Is there anything else you can suggest in terms of debugging? I've been using __webpack_share_scopes__
that confirms only 1 version of each package is getting loaded. Perhaps this is just an incompatibility with certain configurations of Material UI & Module Federation?
The main thing to take into account is that the imports are the same in the output.
For example, if there is an import in the form of import Button from '@material-ui/core/Button'
and another import { Button } from '@material-ui/core'
, those are different and Module Federation will load those components twice (As well as the styles probably)
The Lab library uses the '@material-ui/core/Button'
format, so it kinda forces you to also use this format in your code.
How does your code import material components? If you use the @material-ui/
and print out __webpack_share_scopes__.default
. Do you have both "material-ui/core" and the specific components in the list? If you do then you probably have more than 1 variation of import formats
What i did was use @material-ui/
(which solved the issue for us) and printed out __webpack_share_scopes__.default
. Then I've put this as part of the "Shared" config, and started commenting out things to find the specific component that causes the issue.
@slavab89, Hello! Bro, can you show us how you've configured the shared config?
We were able to finally solve our conundrum by setting eager: true
on all of our @material-ui
and corresponding dependencies. This was the only thing we've tried that actually worked with our config and usage of MUI packages. This works for us currently as we only have a single host with multiple remotes, I figure this would no longer be a solution if we had an omnidirectional setup.
@slavab89 thanks for the recommendation. We actually have a linting rule set up to ensure that only named imports from @material-ui
can be used and to block imports from @material-ui/core/styles
(in-case anyone finds it helpful):
module.exports = {
...
rules: {
'no-restricted-imports': [
'error',
{
patterns: ['@material-ui/core/*', '@material-ui/lab/*', '!@material-ui/core/styles'],
},
],
}
}
Last updated 26th of june, 2023
Alright, so I've came across this problem in the weirdest way, and tried everything i've found in the web with no solution. Eventually, I've came up with a solution that works for me, which i'm not aware of it's drawbacks as of time of writing this comment. So buckle up.
I'm developing a shell app that contains all the shared state, themes etc, and part of the shared state and context is Material UI.
My shared packages setup is exactly this, across the shell/host and the remote containers:
{
'@material-ui/core': {
singleton: true,
requiredVersion: deps['@material-ui/core'],
},
'@material-ui/pickers': {
singleton: true,
requiredVersion: deps['@material-ui/pickers'],
},
'@material-ui/lab': {
singleton: true,
requiredVersion: deps['@material-ui/lab'],
},
'@material-ui/styles': {
singleton: true,
requiredVersion: deps['@material-ui/styles'],
},
}
When loading different components from any material ui package, they would dynamically apply some styles and append them inside the html <head />
tag, you can verify thiss yourself in when you inspect your page.
Example head styles:
<style data-jss data-meta="MuiButtonBase">/* ... */</style>
<style data-jss data-meta="MuiTouchRipple">/* ... */</style>
<style data-jss data-meta="MuiAutoComplete">/* ... */</style>
The problem happens is when you have multiple styles injected, containing the same className, but with different style values, and this is where the ordering of the injected styles matters, causing styles overriding, which breaks the UI.
For example, you might have a custom styling for the MuiButtonBase, and material ui has it's own MuiButtonBase styling.
If for some reason the styles were injected in this order:
MuiButtonBase (default material ui styles)
MuiButtonBase (your button base)
Then you wouldn't encounter any issues.
But if the styles happen to be in any different order that results in overriding your custom styles, then you'd end with some problems.
Example problematic ordering:
<!-- Default Material UI styles -->
<style data-jss data-meta="MuiButtonBase">/* ... */</style>
<!-- Your Button Material UI styles -->
<style data-jss data-meta="MuiButtonBase">/* ... */</style>
<!-- THE PROBLEMATIC STYLES: Default Material UI styles, AGAIN -->
<style data-jss data-meta="MuiButtonBase">/* ... */</style>
This would override your custom styles, and break the UI and other components.
I modified the package name of my remote app in the package.json
, which for some reason, made material-ui apply the jss styles in different order, i have no idea why or how, which is a different story, but yeah, that happened.
As @ScriptedAlchemy mentioned in this thread, material ui is composed of multiple packages, with some shared internal packages that maintain an internal state. Figuring out those packages is a rabbit hole and no one so far have posted a complete solution for this as of writing this comment. You could use the console.log(__webpack_share_scopes__.default);
method as mentioned earlier in this thread to divide and conquer the packages one by one, until you find the problematic package that needs to be marked as shared, but I've noticed no matter what i do, there will be always a package breaking some other stuff (I'm looking at you, @material-ui/lab
)
The solution to me was to isolate the classNames per micro-frontend, so no micro-frontend can inject jss styles that can override the others.
That means, each micro-frontend can have it's own jss styles party, so even if i have multiple instances of MuiButtonBase styles they won't override each other.
The solution is composed of two steps.
To do that with material ui, you need to use the <StylesProvider />
component.
For step #1 the code looks something like this
import { StylesProvider, createGenerateClassName } from '@material-ui/core';
import { Route } from 'react-router-dom';
// Global Variable, independent from react state
const classNameGenerators: Record<string, any> = {};
const ComponentWithStyles = (props: any) => {
// The prefix can be your micro-frontend key for example
const classNamePrefix = 'prefix_here'
let classNameGenerator;
if (!classNameGenerators[classNamePrefix]) {
classNameGenerator = createGenerateClassName({
disableGlobal: true,
productionPrefix: classNamePrefix,
seed: classNamePrefix,
});
classNameGenerators[classNamePrefix] = classNameGenerator;
} else {
classNameGenerator = classNameGenerators[classNamePrefix];
}
return (
<StylesProvider generateClassName={classNameGenerator}>
<FederatedComponent {...props} />
</StylesProvider>
);
};
Then inside every component, i don't care about what styles or packages they use, the styles will always be self contained, but also using the global theme. Note the usage of useMemo, since this is an HOC for the route render, we don't want to create a different generator for each route/micro-frontend to avoid unmounting the component when, for example, the route changes.
For step #2, in the entry point of your shell app, you will need to do create a simple classNameGenerator disabling the deterministic styles
import { StylesProvider, createGenerateClassName } from '@material-ui/core';
const classNameGenerator = createGenerateClassName({
disableGlobal: true, // THIS IS VERY IMPORTANT!!
productionPrefix: 'shell',
seed: 'shell',
});
// Where you define your providers
return (
<StylesProvider generateClassName={classNameGenerator}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</StylesProvider>
);
With this, you don't need to eagerly load the dependencies as mentioned in this comment
Assuming the prefix for the shell app is 'shell', and the micro frontend is 'admin', then the end result of the stylesheets would be this:
<style data-jss="" data-meta="MuiButtonBase">
/* Note the 'shell' prefix */
.shell-MuiButtonBase-root-187 {
color: inherit;
border: 0;
/* ... extra css */
}
</style>
<style data-jss="" data-meta="MuiButton">
/* Note the 'Admin' prefix */
.Admin-MuiButton-root-22 {
color: #343235;
padding: 6px 16px;
/* ... extra css */
}
</style>
<head/>
, since each component will have it's own stylesheet even if they are duplicates.I will update this if i come up with any problems using this method.
We are using webpack 5 and a design system that hosts federated modules from material ui. As of now we have the following setup for the ModuleFederationPlugin.
This is how a page on the app displays:
However, when we use a component from material-ui/lab or material-ui/pickers it seems the styles get messed up.
Notice the buttons are now unstyled/overwritten
Does anyone have an idea on why this is happening?
Note: I have also tried adding
to the shared list and it still does not work.