Closed knenkne closed 3 years ago
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.
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.
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.
It doesn't matter as both use @emotion/cache
and the problem is there.
We have a working PR that intends to fix this: https://github.com/emotion-js/emotion/pull/2361 . If anyone here could give it a spin in their projects to check if it resolves your problems that would be great.
Was the version of emotion/styled
also supposed to be incremented last night? It looks like github actions bumped the version number of emotion/cache
and emotion/react
(because of a dependency) and published them - but emotion/styled
(which had a bumped dependency on emotion/react
) didn't get published.
Is that normal?
@rleguen-broadsign technically you can force reinstalls of @emotion/styled
's dependencies. Since their versions still satisfy ranges specified in @emotion/styled/package.json#dependencies
etc.
I admit that this is somewhat frustrating though and we could try to use updateInternalDependents
to avoid situations like this in the future. Would you like to raise a PR with this?
Just ran into this issue today, and can confirm that bumping @emotion/react
to 11.4.0
to pull in the fixes in #2361 resolved the issue for me -- thanks so much!
In this situation, you really should use separate caches with unique keys - unfortunately elements are shared resources in the global document and we can't easily distinguish which belong to each of those microfrontends.
@Andarist we are following this separate-caches-with-unique-keys approach in our microfrontends setup, and we are still receiving the warning:
You are loading @emotion/react when it is already loaded. Running multiple instances may cause problems. This can happen if multiple versions are used, or if multiple builds of the same version are used.
Is there still a risk of problems since we are using the separate caches with unique keys? In other words, in this scenario, is the message correctly warning us of a real risk, or is the message incorrect because the risk has been addressed?
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 inproduction
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)Library repo 1 Library repo 2
Expected behavior: Allow multiple instances of emotion
Environment information: