New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix ComponentStyle caching strategy to take StyleSheet cache into account #1636
Fix ComponentStyle caching strategy to take StyleSheet cache into account #1636
Conversation
@darthtrevino Changing the StyleSheet while the component is mounted — essentially hot swapping the sheet — is not something we should support, just to be clear again. However, this is a reasonably small change that I can maybe accept, but we'll have to regression test it before release. Hence a small disclaimer: Sorry in advance if this change doesn't land 😉 The current state of the code breaks an important caching optimisation, as it runs a bunch of operations that we're trying to avoid just to fix this. I'll leave some comments on the code itself since there's another way. |
I don't believe this will hot-swap stylesheets for mounted and styled components. This is kind of analogous to a Java thread-local - it's two components of the same type in totally different stylesheet contexts. |
@@ -116,6 +119,69 @@ describe('StyleSheetManager', () => { | |||
expect(document.body.innerHTML).toMatchSnapshot() | |||
}) | |||
|
|||
// https://github.com/styled-components/styled-components/issues/1634 | |||
it('should flush styles to an iframe when the iframe is re-rendered', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is extremely specific to your environment and additionally this conflicts with it as well:
Changing the StyleSheet while the component is mounted — essentially hot swapping the sheet — is not something we should support,
Also it's a little unnecessarily complex. Instead we can use enzyme to swap out the the stylesheet itself together with a prop, and expect to see a different class.
Edit: I'd say it's ok to not test this for now. I can write a test eventually, but one that is specific to all caching guarantees instead
src/models/ComponentStyle.js
Outdated
if (areStylesCacheable && isStatic && lastClassName !== undefined) { | ||
return lastClassName | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you're leading the code into expensive operations that we're trying to avoid
src/models/ComponentStyle.js
Outdated
const flatCSS = flatten(this.rules, executionContext) | ||
const name = generateRuleHash(this.componentId + flatCSS.join('')) | ||
|
||
const useLastClassName: boolean = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This goes through all operations even if it's not necessary too
@@ -78,12 +78,14 @@ export default ( | |||
* */ | |||
generateAndInjectStyles(executionContext: Object, styleSheet: StyleSheet) { | |||
const { isStatic, componentId, lastClassName } = this | |||
if (areStylesCacheable && isStatic && lastClassName !== undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the crux of it, so instead you can replace this:
if (areStylesCacheable && isStatic && lastClassName !== undefined) {
with this:
if (areStylesCacheable && isStatic && lastClassName !== undefined && styleSheet.hasNameForId(componentId, lastClassName)) {
a lot simpler, doesn't break our caching and optimisation, and it should have the same effect 👍
so essentially this is the only change that is needed
I'm guessing the flatten() is what you're trying to avoid? |
No, this code change won't. Essentially you're just invalidating the cache for a new stylesheet. Whether that's hot swapped through the context, or because you're suddenly in a new context with the same ComponentStyle doesn't matter that much, because it won't occur during normal usage. I think it's a fair addition though, so I've left some comments above and I think we can ship this Edit:
We're avoiding Edit: also, sorry to make you jump through a couple of hoops and so on, but I'm really — still — missing a lot of context of what you're trying to achieve and are seeing as a result 😉 apologies if I come across a little rude due to that. Anyway, the change seems cool and kind of explains some changing assumption in your env compare to a typical one 👍 |
No problem, I get that it's a weird use case. I put some additional context at the tail of the last PR. I guess I would call this "iframes for css-isolation in plugin-style applications" |
@darthtrevino just seen your last changes. Haven’t given it a quick run yet but see you sure that the change I proposed here won’t be enough? #1636 (comment) Because essentially we only have to prevent returning the lastClassName if it’s not on the StyleSheet so that one line change should be enough |
@kitten Switching to your suggestion - I think that will be fine |
CHANGELOG.md
Outdated
@@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. If a contri | |||
|
|||
## Unreleased | |||
|
|||
- n/a | |||
- Fix issue with multiple iframes and StyleSheetManager instances. @darthtrevino (see [#1634]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this generic as well like the test?
I.e. fix last className caching strategy to take StyleSheet cache into account
Or sth
@darthtrevino thanks for making this change this quickly 👏 hope it will help more ppl with similar edge cases. I’ll give it a couple of tests tomorrow and will push a prerelease. If that looks fine I’ll publish a patch :) |
This fix should resolve a similar issue I am having with a different use case. I created an embedded component system that mounts a react component (bundled with styled components) on html markup on any given client. The components wrap a StyleSheetManager that use the element its mounting as the target and when I programically unmount/mount the component for advanced usage, the styles disappear. |
@bliitzkrieg yes, that might be fixed as well by this. Quick note that what you're doing will break specificity and certain guarantees (see previous comments) 😉 |
Thank you so much for helping us improve styled-components! Based on our Community Guidelines every person that has a PR of any kind merged is offered an invitation to the styled-components organization—that is you right now! Should you accept, you'll get write access to the main repository. (and a fancy |
Sounds great - thanks Max! |
Fixes #1634, Whitespace-change free version of PR #1635
When ComponentStyle.generateAndInjectStyles is invoked, the stylesheet instance should be checked even if the style name is already defined. If the component is being rendered in a sibling iframe with a separate StyleSheetManager, this allows the second iframe to receive the injected styles.