styled-components: [Micro Frontend] Duplicate class name if there is multiple styled-components

I use qiankun to make my micro-frontend app. I use styled-components for css-in-js.

If I mount multiple sub apps at the same time, and these sub apps use styled-components respectivly, the generated class name would be conflict.

Is there a way to config different seed to generate class name?

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 30
  • Comments: 40 (5 by maintainers)

Most upvoted comments

We have the same problem in production but not in development. In our case the two components have the same version of styled-components.

One is referencing a styled.div and the other a styled.svg: Skärmavbild 2020-09-09 kl  07 05 23

Skärmavbild 2020-09-09 kl  07 05 42

Duplicate entries for the className “cVmQYF” appear in the inspector: Skärmavbild 2020-09-09 kl  07 05 54

The styled-components are on the same version: Skärmavbild 2020-09-09 kl  07 07 55

We’re using ts-loader so the babel-plugin isn’t an option. Are there any other work-arorunds? All our individual components are built at the same time but are mounted individually on the same page.

Yup, I can confirm mentioned issue also. For me it’s possible to regenerate it when I have two microfrontends build with webpack. It seems like it generates same class names, but only in built apps. When I debug it locally, it works like a charm.

EDIT: For me the fix was to simply add babel-plugin-styled-components. It automatically creates some nicer class naming, thus solving the issue.

I just had a class name collision. My scenario is a custom React component library used by a CRA frontend.

  • The component library uses styled-components and is built with tsc (TypeScript).
  • The frontend is built with create-react-app.

Interestingly:

  • The collision only happens after I build the static app (react-scripts build). No problem in dev (react-scripts start).
  • Using the macro (styled-components/macro) fixed the problem, though I suspect / worry this only sidestepped the problem by changing the algorithm.
duplicate

@wdfinch Yep, that’s still intended. However, this is a hash based on component IDs and the contents of the CSS string, so collisions will be extremely unlikely given the namespace option, read: as unlikely as any other hash collision with 32 bits if no mistake is made in our hashing logic.

We only have two microfrontends and this has happened the moment we updated styled-components in one of them. To fix it, we downgraded styled-components to version 4 in one microfrontend and the bug went away.

When this bug happened, it happened in two of our components: Inputs and Selects. So maybe it’s not extremely unlikely? Or we were very unlucky 😭

This bug also only happens when building for production, so catching during development is almost impossible. I understand that this might be extremely unlikely, but we need a better guarantee than that. Having no guarantee that any class can break at any minute for any change in another app is… well… not ideal…

I though namespace was meant to do that, but looks like something merely aesthetical… So… I guess that means the documentation is misleading? Since it states that namespace can avoid this issue, but it can’t? (I’m refering to this documentation https://styled-components.com/docs/tooling#namespace and I’m assuming it’s not working based on @wdfinch’s comment: https://github.com/styled-components/styled-components/issues/2974#issuecomment-895339520)

Update: I realize why we have so many className collisions in our app. Maybe it can be useful as a use case to support this feature.

Basically both the host app and the microfrontend are using the same component library, but the host app is overwriting some styles. When the microfrontend loads, the “base” styles are applied on top of the overwriten styles. So of course the classNames match because they’re literally the same component with the same styles, but the problem is in the order in which they’re loaded.

Screenshot 2022-10-07 at 08 43 20 In the image we can see three classnames, the one that’s higher is the base styles from the microfrontend (with the highest priority), then the style overwrites from the host app, and then the base styles from the host app

The thing is, we believed releasing a new version of the component library that uses namespace would fix this. We are using different versions of the component library in the host and in the microfrontend, so having a unique namespace per version should guarantee no collisions up to some point. Something like:

   namespace: `toolkit-select-${new Date()}`,

But this setting has no effect in the final classname applied, so it’s not useful to solve our issues.

Styled components 4 seem to handle the order insertion of stylesheets different, and that’s why our build passes when it’s installed. In the future we’re moving towards loading microfrontends using ShadowDOM and that might be a fix as well.

@wdfinch Yep, that’s still intended. However, this is a hash based on component IDs and the contents of the CSS string, so collisions will be extremely unlikely given the namespace option, read: as unlikely as any other hash collision with 32 bits if no mistake is made in our hashing logic.

I think the issue is sometimes we can’t ensure all our dependencies are on the same version. For example, let’s say we have a micro frontend with many apps developed by many separate teams. Team A might be used styled-components v5 for their app while team B might be using v4 in their app. It might not be feasible for team B to upgrade immediately and development cannot stop until they upgrade.

Another similar situation is when the next major version comes out someday. If there are 50 frontend apps all using styled-components, then it would be a big feat to upgrade all of them to the next major version immediately (assuming there are breaking changes). Instead, a gradual approach would be better.

For small teams working on single apps none of this matters. But for large teams with big frontend apps developed over long periods of time, these issue start to become very relevant. 50 apps are not some crazy number either. It’s reasonable for large products to have this depending on how finely you break up your application.

Just went through this issue. After spending many hours on it here are my findings and solutions:

The root cause for me was having multiple instances of styled-components loaded. This can easily be confirmed by seeing if there are multiple style tags in the head that say data-styled-version.

If so you have a few options. The best option is to not load multiple instances of styled-components. Some sources of multiple loads are:

  1. Having styled-components inlined by a module bundler (rollup in my case). This happened for me. I had multiple packages used by various micro-frontend apps and rollup was inlining styled-components and therefore duplicating it. If so, look for an external option or plugin to keep the module from being inlined.

  2. Multiple loads between webpack apps. If you’re using module federation, use the singleton option to avoid multiple instances being loaded.

If you cannot avoid loading multiple instances then you should use the babel-plugin-styled-components plugin with the namespace and/or display name settings. This will namespace the various generated class name hashes and therefore eliminate the problem. If you are having issues using the plugin in either webpack or rollup, make sure babel is the first tool to transpile the code. In my case, compiling the code with typescript first resulted in the plugin not applying the transformations. I found the fork-ts-checker-webpack-plugin and rollup-plugin-fork-ts-checker useful to still get type checking while using babel as my primary transpile tool.

If anyone is still struggling with this issue:

We encountered the same problem, facing duplicate class names from different apps in Module Federation due to multiple versions of styled-components. Here’s a breakdown:

  • An internal package was transitioned to an ESM build using rollup.
  • This led to this error.
  • We attempted to resolve it by configuring the output-interop to “compact”. However, this wasn’t possible because NX utilizes rollup 2.
  • To address the error, we had to remove styled-components from MF’s shared dependencies, which led to the issue of duplicate classes.

Ultimately, the resolution was using different versions of styled-components across the MF apps. No other approaches proved effective (including Webpack aliasing and babel-plugin-styled-components). This approach was inspired by a comment found here: link.

Just went through this issue myself, and for people who have made sure that they only have one instance of styled-components but still are experiencing issues, this comment helped me: https://github.com/styled-components/styled-components/issues/2170#issuecomment-434294742. My issue was having two entrypoints, both using styled-component and using the same vendor file, but styled-components was initialized twice at runtime.

https://styled-components.com/docs/faqs#running-multiple-applications-on-one-page this hasn’t been updated with the splitChunks plugin way of doing optimization, and there was a pull request made to update the docs: https://github.com/styled-components/styled-components-website/pull/378 but it didn’t merge.

So long story short, i added the runtimeChunck to my optimization like this:

optimization: {
		splitChunks: {
			cacheGroups: {
				defaultVendors: {
					test: /[\\/]node_modules[\\/]/,
					chunks: "all",
					name: "libs",
					enforce: true,
					minChunks: 2,
				},
			},
		},
		runtimeChunk: { name: "libs" },
	},

I’m not really caught up on this, and I don’t really understand what is found to be so attractive for some in duplicating loads of dependencies, instead of having a monolithic build process, that ensures shared library code as far as possible… unless maybe Preact is used instead of React due to the reduced size… Anyway!

So, I will stand by this, but either you share styled-components like many microfrontend builds share react/react-dom across a given page.

Or, you can use multiple versions of styled-components on the same page by altering process.env.SC_ATTR per build. I haven’t tested this in a while, but it should still work just fine.

Also, to summarise, the babel-plugin namespace option can now also be used to prevent hash collisions: https://styled-components.com/docs/tooling#namespace

Since in all cases a combination of these three solutions work, I’ll only leave this issue open for a bit and will lock it after, since it’s gotten too long. I appreciate if any other issues are highlighted here, but I’m fairly certain at this point that hunting for “the big fix” isn’t really a replacement for the above.

Did you find anything about it ?

@jfairley I had exactly the same problem, and switching to styled-components/macro solved it. Yet, I am having problems with webpack as styled-components/macro dependes on ‘fs’ and ‘modules’.

@pgangwani : I can create PR for the original babel plugin.

EDIT: PR opened. Please support this PR so we can have added namespace to the official babel plugin.

We are using a micro front setup with single-spa mostly consisting of react micro front ends and we are also having this issue. It’s not a common issue for us though.

Using babel-plugin-styled-components-css-namespace did not work since they seem to have an issue with newer node versions. However just implementing babel-plugin-styled-components seems to have done the trick. Thanks @pdrvsky for the tip.