Current behavior:
We've microservice architecture so each team can have their own emotion instance, in the same time they can even import other libraries that also use emotion. All apps getting loaded asynchronously. And when the next app is loaded, it creates own emotion instance that overwrites previous (at least it looks like so, but old tags still exist in DOM).

Yep, In dev mode we're getting the warn, but in production build all sheets getting overwritten.
To reproduce:
Main repo
npm i to install pseudolibraries and depsnpm run start to see the warnnrm run build to see how one library overwrites another after 2 seconds (public folder)Expected behavior:
Allow multiple instances of emotion
Environment information:
In this situation, you really should use separate caches with unique keys - unfortunately <style/> elements are shared resources in the global document and we can't easily distinguish which belong to each of those microfrontends.
The problematic part though is that you are using hard dependencies for @emotion/react in each one and thus you load multiple instances of Emotion 11 so their context identities won't be shared. I guess that because you are in this microfrontend architecture this is a hard requirement for you so I would advise you to just export CacheProvider + createCache from each of your libraries~ and just wrap each React subtree in those CacheProviders.
So in the end you would handle things like this:
diff --git a/src/index.js b/src/index.js
index b55fafe..c350d6a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,8 @@ import React from "react"
import ReactDOM from "react-dom"
import styled from "@emotion/styled"
import css from "@emotion/css"
-
+import { CacheProvider } from "@emotion/core"
+import createCache from "@emotion/cache"
const getEmotion11Library1Chunk = async () => {
return await import("emotion-11-library-1")
@@ -14,13 +15,23 @@ const getEmotion11Library2Chunk = async () => {
setTimeout(() => {
getEmotion11Library1Chunk().then((chunk) => {
- ReactDOM.render(<chunk.Library1 />, document.getElementById(`placeholder1`))
+ ReactDOM.render(
+ <chunk.CacheProvider value={chunk.createCache({ key: "lib1" })}>
+ <chunk.Library1 />
+ </chunk.CacheProvider>,
+ document.getElementById(`placeholder1`)
+ )
})
}, 2000)
setTimeout(() => {
getEmotion11Library2Chunk().then((chunk) => {
- ReactDOM.render(<chunk.Library2 />, document.getElementById(`placeholder2`))
+ ReactDOM.render(
+ <chunk.CacheProvider value={chunk.createCache({ key: "lib2" })}>
+ <chunk.Library2 />
+ </chunk.CacheProvider>,
+ document.getElementById(`placeholder2`)
+ )
})
}, 4000)
@@ -34,6 +45,9 @@ const Title = styled.h1`
`
ReactDOM.render(
- <Title>Welcome!</Title>,
+ <CacheProvider value={createCache({ key: "app" })}>
+ {" "}
+ <Title>Welcome!</Title>
+ </CacheProvider>,
document.getElementById(`app`)
)
Note that this doesn't fix your problem with Emotion 11 hijacking Emotion 10 styles - we've ensured that we don't try to hijack Emotion 10 SSR styles but we have missed the case with dynamically created Emotion 10 styles (or assumed that it will just work thanks to the same thing we've done for SSRed styles).
You can workaround this by doing this:
const emotion10Styles = document.querySelectorAll(
`style[data-emotion]:not([data-s])`
);
Array.prototype.forEach.call(emotion10Styles, (node) => {
node.setAttribute("data-s", "");
});
before you fetch a bundle containing Emotion 11 code.
We can't do much for the first thing but we probably should try to handle the second problem and I will give this a thought.
Thanks for your reply. We're planning to migrate to emotion 11 eventually and use cache 馃殌
Note that the cache stuff is available in Emotion 10 - those are just different caches (just like React 16 is not exactly the same as React 17 etc). What I've recommended is taking into account those mismatches and it should allow you to run "mixed" application with success.
@Andarist I'm running into the same issue with our micro-frontends and I just discovered the cause of it: moving a <style> element in the DOM somehow erases all of its sheet's cssRules, even if that <style> element is moved to exactly where it currently exists.
The most minimal example of this behavior would be the following, which you can plug directly into a browser console to test:
var styleEl = document.createElement('style');
document.head.appendChild(styleEl);
styleEl.sheet.insertRule('.my-class { color: #FFF; }', 0);
console.log(styleEl.sheet.cssRules.length); // 1 rule - good
// append again
document.head.appendChild(styleEl);
console.log(styleEl.sheet.cssRules.length); // 0 rules - yikes! gone
In Emotion, this happens when the second emotion instance's createCache() function calls StyleSheet.hydrate() with all of the nodesToHydrate (i.e. existing <style> elements in the DOM with the same key). hydrate() then ends up calling _insertTag(), which "re-inserts" each <style> tag into the DOM, causing them to all lose their existing cssRules.
Here's a minimal reproduction with Emotion in Stackblitz: https://stackblitz.com/edit/react-7unnfr?file=src/index.js
I think there might be a simple fix: perhaps Emotion could add a data-hydrated (or similarly named) attribute to <style> tags that either have:
a) Been created by StyleSheet in the browser, or
b) have already been hydrated from SSR
Then when the next Emotion instance calls createCache(), the data-hydrated attribute can be used to exclude existing tags from the nodesToHydrate array. This essentially will exclude existing <style> tags that already belong to other StyleSheet instances, and thus won't try to "move" them in the DOM with _insertTag()
Just to mention why this use case is important btw: we basically have multiple micro-frontends which all rely on the same underlying UI library. This underlying UI library inserts its styles using Emotion. We can't control or tell the micro-frontends to use a different Emotion key, nor do the micro-frontends even have access to where the underlying library calls createEmotion(). If this use case could work correctly right out of the box, that would be best 馃憤
Added a PR (https://github.com/emotion-js/emotion/pull/2222) with a fix for the issue. I realized that all Emotion instances should respect the existing <style> tag's SSR data if it exists, so I allowed for that.
If you agree with the approach in the PR, let me know and I'll update all of the snapshot tests
Found this issue after hitting the same problem. We fight hours to discover that using @emotion/css/create-instance works fine to solve this issue as long as you are not using @emotion/react. As soon as you are using CacheProvider even with your own cache instance, the issue appears again and your style tags are erased.
My guess is that CacheProvider is too tight with some emotion logic that should not be triggered by default.
@Andarist your solution does not work, as said above, as soon CacheProvider is used, style from another emotion 11 app are removed. We are stuck with using only generated classes from css and no JSX components css prop.
Please always try to share a repro case in a runnable form - either by providing a git repository to clone or a codesandbox. OSS maintainers usually can't afford the time to set up the repro, even if exact steps are given.
@JSteunou can you explain how you used @emotion/css/create-instance to fix this problem? I'm working in a microservices architecture as well and am unable to install a package using emotion into an app using emotion because the package styles overwrite the app styles as you've described
@nelsondude This is what I ended up with:
import createEmotion from '@emotion/css/create-instance'
export const {
flush,
hydrate,
cx,
merge,
getRegisteredStyles,
injectGlobal,
keyframes,
css,
sheet,
cache
} = createEmotion({
key: 'APPKEY'
})
You'll then use replace any imports of emotion with this specific instance. For example, replace import { css } from '@emotion/css' with import { css } from '../emotion-instance.js' (or whatever you named the above file). Alternatively pass it through context, rather than imports, if you are using React. Replace 'APPKEY' with a unique string specific to each of your applications.
@nelsondude what @jackmoore said. Same here. But it does not work if you are using @emotion/react on parent app and want to use also @emotion/react on child app
doing import {cache} from '../emotion-instance' then using it for <CacheProvider> does not solve the issue, sadly :(
Just by using CacheProvider it will erase your style.
OK sounds good, yeah so I'm currently using @emotion/react on the parent app but if this isn't fixed soon I'll use @jackmoore 's solution and replace that dependency. Does this work with @emotion/styled as well? We use this to define a number of components
Is there any update on this? I'm currently just doing a dev build until this issue is fixed. If it isnt soon I might just switch to styled components. Currently using @emotion/styled for a bunch of components and i'd prefer not to convert these to use the css prop
Hit this problem recently. I am bit confused as to whether it only occurs with @emotion/react and can be avoid by exclusively using @emotion/css ?
Although using React, we have no particular need for the extra features of the react package, so if moving to @emotion/css is a solution, it is most likely the easiest for us.
Also hit this problem when building chrome extension with material-ui, the site styles removed when my extension is activated.
Most helpful comment
In this situation, you really should use separate caches with unique keys - unfortunately
<style/>elements are shared resources in the global document and we can't easily distinguish which belong to each of those microfrontends.The problematic part though is that you are using hard dependencies for
@emotion/reactin each one and thus you load multiple instances of Emotion 11 so their context identities won't be shared. I guess that because you are in this microfrontend architecture this is a hard requirement for you so I would advise you to just exportCacheProvider+createCachefrom each of your libraries~ and just wrap each React subtree in those CacheProviders.So in the end you would handle things like this:
Note that this doesn't fix your problem with Emotion 11 hijacking Emotion 10 styles - we've ensured that we don't try to hijack Emotion 10 SSR styles but we have missed the case with dynamically created Emotion 10 styles (or assumed that it will just work thanks to the same thing we've done for SSRed styles).
You can workaround this by doing this:
before you fetch a bundle containing Emotion 11 code.
We can't do much for the first thing but we probably should try to handle the second problem and I will give this a thought.