Style merge issues #2893
Replies: 1 comment
-
I think I might have thought of a reason why Canvas Kit styles may be merging out of order in non-compat mode... const cardStyles = createStyles({
padding: 10
})
const menuCardStyles = createStyles({
padding: 20
})
<div className={[cardStyles, menuCardStyles].join(' ')} /> The result will be But there's an optimization that happens to make sure we don't inject endless amounts of styles into the browser's StyleSheetList with all the versions of Canvas Kit that get bundled. We use Emotion's cache key based on the style's hash when we build. Here's a link to a reproduction where I simulate the build object the style transformer does in our build step (this isn't a dev issue, just a production build issue): https://stackblitz.com/edit/style-merge-failure-clufwt?file=src%2FApp.tsx To fix this, we have a few options:
|
Beta Was this translation helpful? Give feedback.
-
tl;dr;
There have been reports that styles are not merging correctly in all cases. We'll enable compatibility mode for all style merging until we can figure out why.
Long Story
Canvas Kit Styling utilities like
createStyles
andcreateStencils
work a bit differently than Emotion'sstyled
from@emotion/styled
and thecss
prop from@emotion/react
. Both work by creating a wrapper component that handles theclassName
prop specially. If aclassName
prop is provided, those wrapper components will look at each class name and see if it is in the Emotion cache. If there is a class name found in the emotion cache, styles will be collected into an object containing all styles associated with the cache. When everything is done, this new style object will be hashed an a new CSS class name is generated.For example, imaging some components are created using
@emotion/styled
viastyled
:The HTML generated will look something like this:
What does Emotion do if we call
```styled
on another component already styled?:const ComponentB = styled(ComponentA)({
padding: 20
});
The HTML generated will look something like this:
This looks interesting. There's a style for
ComponentA
,ComponentB
, and one for merged styles.ComponentB
renders and inserts styles into the StyleSheetList.ComponentB
then rendersComponentA
, passing down aclassName="css-abcde"
.ComponentA
handles it's own styles and injects them into theStyleSheetList
and adds to theclassName
prop:className="css-12345 css-abcde"
. After this, the wrapper component processes the resultingclassName
prop, splitting on all spaces. It will check each CSS class name against the Emotion cache, collecting all styles found in the cache, in order. In this case, it findspadding: 10
fromcss-12345
andpadding: 20
fromcss-abcde
and creates a new style object:It creates a new CSS class name and injects it into the
StyleSheetList
. It removes all CSS class names on theclassName
prop that were found in the cache and adds the new CSS class name to it instead. TheclassName
prop now looks likeclassName="css-123de"
.New Styling
The
createStyles
andcreateStencil
work differently by using the browser's built in style merging using CSS specificity. In the previous example usingcreateStyles
instead:The HTML looks like:
Notice
handleCsProp
adds class names together without merging them together. IfcomponentCStyles
are inserted beforecomponentDStyles
, the latter has a higher specificity because it is defined last.Compatibility Mode
createStyles
andcreateStencil
rely on styles being defined in the right order because the solution depends on CSS specificity. Emotion cannot predict style insertion order, so new CSS classes are always created when merging styles together. So, what happens when you combine a component usingcreateStyles
and one usingstyled
?The
styled
wrapper is controlled by thestyled
component wrapper. SincecreateStyles
uses the same Emotion cache, the style wrapper will merge styles the way Emotion always does.In the
ComponentG
,handleCsProp
is doing a bit of magic. It tracks styles added by allcreateStyles
calls in a cache. It will also check the Emotion cache against every CSS class name found against Emotion's cache negating any CSS class names found in thecreateStyles
cache. If there's a hit, it will call the same style merging Emotion does.The Problem
createStyles
andcreateStencil
rely on the browser's CSS specificity for style merging and by extension, rely on styles being inserted in the correct order. Styles are inserted as soon ascreateStyles
orcreateStencil
are called, making them execution-order dependent. If you declare styles in the same file, it means you MUST define them in the order you expect styles to merge, top to bottom where bottom-most styles will override previous styles. If you usecreateStencil
, the function makes surebase
styles are executed beforemodifiers
and, lastly,compoundModifiers
.modifiers
andcompoundModifiers
will be executed in order as defined in your source file.But, sometimes modules can be out-of-order. A simple example is when using async imports:
In this example, we declare
myStyles
at the top level, but import thePrimaryButton
inside thethen
callback. This is an asynchronous import and will execute styles out of order. FirstmyStyles
, thenPrimaryButton
styles. This can result in incorrect style merging. In this example, you should define your styles at the same level as your component:This is a known limitation of our styling solution.
There have also been reports that styles are merging incorrectly even outside this known use-case. Technically, inserting styles into the
StyleSheetList
as part of the JavaScript evaluation phase is a side effect. There are examples of JS modules being evaluated out-of-order that we don't fully understand. Until we can understand all cases of out-of-order evaluation and find a work-around, the safest and easiest option is to enable the combability mode at all times.The upside is styles will merge the same way as expected using the same merging technique Emotion uses. The downside is Emotion's solution for style merging has a runtime cost. Static style merging has the benefit of a near zero runtime cost. Compatibility mode has the same performance profile as Emotion. But we'd rather be accurate than fast and broken. We'll enable static styling again in the future once we can guarantee styles always merge correctly in static styling mode.
Beta Was this translation helpful? Give feedback.
All reactions