Skip to content
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

Merged

Conversation

darthtrevino
Copy link
Contributor

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.

@kitten
Copy link
Member

kitten commented Mar 23, 2018

@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.

@darthtrevino
Copy link
Contributor Author

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 () => {
Copy link
Member

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

if (areStylesCacheable && isStatic && lastClassName !== undefined) {
return lastClassName
}

Copy link
Member

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

const flatCSS = flatten(this.rules, executionContext)
const name = generateRuleHash(this.componentId + flatCSS.join(''))

const useLastClassName: boolean =
Copy link
Member

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) {
Copy link
Member

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

@darthtrevino
Copy link
Contributor Author

I'm guessing the flatten() is what you're trying to avoid?

@kitten
Copy link
Member

kitten commented Mar 23, 2018

@darthtrevino

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.

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:

I'm guessing the flatten() is what you're trying to avoid?

We're avoiding flatten and stringifyRules. generateRuleHash(this.componentId + flatCSS.join('')) is also quite expensive but not as bad. styleSheet.hasNameForId(componentId, name) is cheap so since that suffices in this case, that'd be a super elegant change

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 👍

@darthtrevino
Copy link
Contributor Author

darthtrevino commented Mar 23, 2018

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"

@kitten
Copy link
Member

kitten commented Mar 23, 2018

@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

@darthtrevino
Copy link
Contributor Author

@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])
Copy link
Member

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 darthtrevino changed the title Eliminate the short-circuiting of the ComponentStyle stylesheet-registration check Fix ComponentStyle caching strategy to take StyleSheet cache into account Mar 23, 2018
@kitten
Copy link
Member

kitten commented Mar 23, 2018

@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 :)

@bliitzkrieg
Copy link

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.

@kitten
Copy link
Member

kitten commented Apr 6, 2018

@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) 😉

@kitten kitten merged commit e0ba2c6 into styled-components:master Apr 6, 2018
@mxstbr
Copy link
Member

mxstbr commented Apr 6, 2018

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 styled-components logo on your GitHub profile!) You'll be able to label and close issues, merge pull requests, create new branches, etc. We want you to help us out with those things, so don't be scared to do them! Make sure to read our contribution and community guidelines, which explains all of this in more detail. Of course, if you have any questions just let me know!

@darthtrevino darthtrevino deleted the 1634/stylesheet_manager_caching2 branch April 6, 2018 15:44
@darthtrevino
Copy link
Contributor Author

Sounds great - thanks Max!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants