-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
feat(server): add extractCriticalToChunks #2334
Conversation
🦋 Changeset detectedLatest commit: 2cc16da The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
I am not sure if this is all that we need, to be honest. I think that one problem may be that the ids still contain the ids of the global styles too, not sure if this will be problematic. |
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 2cc16da:
|
I may have a bit of a different proposal. As emotion is already automatically adding and removing the |
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.
We'd need to a test like this:
- SSR
<Global/><Global/><div css={{}}/>
usingextractCritical2
- rehydrate that
- unmount one of
<Global/>
s
The expected outcome is that only those styles would be removed and that they would be removed successfully (which I might sometimes refer to as flushing).
I think that this might require adding key
to the items returned by extractCritical2
as <Global/>
styles have keys suffixed with *-global
(from what I recall).
A second concern is that we should check how this is going to work when rehydrating with @emotion/css
. It would be great if we could add the ability to unmount injectGlobal
styles:
insertWithoutScoping(cache, serialized) |
@emotion/css
assumes a single sheet so flush
is easy and straightforward:emotion/packages/css/src/create-instance.js
Lines 112 to 116 in 4d7efcb
flush() { | |
cache.registered = {} | |
cache.inserted = {} | |
cache.sheet.flush() | |
}, |
The ability to unmount
injectGlobal
styles would require maintaining multiple sheets (this is what <Global/>
kinda does) and this might be a little bit tricky/unclear for @emotion/css
@emotion/css
stuff is not a must here but would be great if we could at least take a closer look at it to assess how problematic implementing this for it would be.
Co-authored-by: Mateusz Burzyński <[email protected]>
Co-authored-by: Mateusz Burzyński <[email protected]>
I'll need some help with the test.
I've tried implementing this with d7e7f8c (I've updated the components), but I am missing the step 2
Will it be enough if I just call |
This comment has been minimized.
This comment has been minimized.
I've first done this but this wasn't quite "e2e" just yet so I've followed up with this. In the process I've fixed some small~ issues that were in there - so even if my test is somewhat complicated, it was still worth doing this. I've pushed those commits to your branch so please fetch them before you continue working on this :) I'll leave some additional comments in a moment but those will just be about things I've noticed so far - gonna try to follow up with a full review in the following days. |
<style | ||
data-emotion="mui-global" | ||
data-s="" | ||
> | ||
|
||
body{color:white;} | ||
</style> | ||
<style | ||
data-emotion="mui-global" | ||
data-s="" | ||
> | ||
|
||
html{background:red;} | ||
</style> |
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.
SSRed global style elements were replaced here by client-rendered ones, this all happens very quickly so this might not be noticeable (although I'm slightly worried about unmounting @font-face
and stuff like that, even for a brief moment) but I would still try to check what could be done about it
Why does this happen? SSR style is immediately flushed here:
emotion/packages/react/src/global.js
Lines 126 to 131 in 4d7efcb
if (sheet.tags.length) { | |
// if this doesn't exist then it will be null so the style element will be appended | |
let element = sheet.tags[sheet.tags.length - 1].nextElementSibling | |
sheet.before = ((element: any): Element | null) | |
sheet.flush() | |
} |
since we already have some rehydrated
sheet.tags
coming from here:emotion/packages/react/src/global.js
Lines 108 to 110 in 4d7efcb
if (node !== null) { | |
sheet.hydrate([node]) | |
} |
I believe that the original intention was to flush this on updates but right now we are also flushing it when rehydrating 😕
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.
Ah, this may be the reason for the flickering here - https://deploy-preview-25690--material-ui.netlify.app/ The text is black for a split second, then it changes to white, which is coming from the global styles. Although on this example, I am just omitting the global styles coming from extractCritical2
, so it may be a different issue. Will have to double-check.
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.
Hm, I can only see the color:white
declaration in the SSRed HTML 🤔 Has this site been updated since then?
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.
I cannot spot the flickering too this one time.. I’ve updated the example, we should see the borders after 5s too as that Global component stays mounted. Only the color should change to black (default)
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.
@mitchellhamilton I would like to fix this issue with flushing and reinjecting Global styles when rehydrating. The easiest fix for that would be to just merge those 2 layout effects. Any objections to that? The first effect just avoids recreating StyleSheet (from what I can tell) based on the serialized.name
but since sheet.flush
is called anyway when it changes and highly dynamic global styles are rather rare I don't think we must maintain that split between two effects 🤔
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.
Sounds good
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.
I've implemented this change - could you take a look @mitchellhamilton ? I doubt that this can break anything but better to recheck this
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.
Hmmm, reading the code, it seems as though the change doesn't preserve the behavior of inserting the new style tag in the same position as the previous one. Could you add a test based on https://codesandbox.io/s/still-leaf-invs2?file=/src/App.js?
Co-authored-by: Mateusz Burzyński <[email protected]>
packages/server/test/util.js
Outdated
} | ||
} | ||
|
||
export const prettifyCritical2 = ({ |
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.
as this critical2 was kinda renamed this should be adjusted as well
export interface EmotionServer { | ||
extractCritical(html: string): EmotionCritical | ||
renderStylesToString(html: string): string | ||
renderStylesToNodeStream(): NodeJS.ReadWriteStream | ||
constructStyleTags(html: string): EmotionCriticalToChunks |
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 not correct. The return type of this is a string but the interface definition is missing a declaration for extractCriticalToChunks
- for which this return type would be correct.
wdyt about renaming constructStyleTags
to constructStyleTagsFromChunks
? That way it would be more clear that those 2 APIs can be used together (extractCriticalToChunks
and constructStyleTagsFromChunks
)
@Andarist I've upgraded our test branch on Material-UI, and I got the error below when navigating from one page to another. I think it's related to 00ce87b. It fails on
Repro steps:
We use different |
@mnajdova I believe that I've fixed the issue & added tests for this - I've also confirmed that in your repository. |
`) | ||
}) | ||
|
||
test('duplicated global styles can be removed safely after rehydrating HTML SSRed with zero config approach', () => { |
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.
I thought that this case could be failing so I've added a test for this but it turned out that it wasn't failing.
I was under the impression that this query would select the same node twice when looking for the same hash:
emotion/packages/react/src/global.js
Lines 98 to 100 in 9596d32
let node: HTMLStyleElement | null = document.querySelector( | |
`style[data-emotion="${key} ${serialized.name}"]` | |
) |
but:
- we SSR those style elements as
[0, 1]
- they are moved automatically to head when initializing, and the order is preserved:
[0, 1]
- when rehydrating the first global it grabs the element 0 and rehydrates it which moves that element before all other tags from the main cache so we end up temporarily with
[1 , 0, ...mainCache]
order - the second global thus now grabs the element 1 and also moves It before other tags from the main chance and we end up with
[0, 1, ...mainCache]
I think this works OK and I couldn't find a situation in which it would break but maybe @mitchellhamilton would have some idea for an edge case here that could be tested.
Great! I double-checked all failing scenarios we had, no issues found 🚀 |
Ok, I'll be taking a last look at this in the following days but I think it's in a good shape and that there are no major blockers against merging this. If you, @mitchellhamilton, have some spare time I would also appreciate your review here since this is touching some core logic that we need to get right. |
What:
This PR aims to fix #2158 and possibly #2049
Why:
The new implementation of
extractCritical
should allow developers to solve issues with SSR, mostly with the order of injecting the global css as well as fix dynamic re-writes of the global styles.How:
It is implemented by adding a new
extractCriticalToChunks
utility that will return HTML and an array of styles objects each containingcss
string and an array of ids. EachinjectGlobal
or<Global />
component will be one item in the array, so that it can be treated independently. The css for the regular styles is added to one item of the list.Checklist: