-
Notifications
You must be signed in to change notification settings - Fork 844
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
[Emotion] Create hook utility for memoizing component styles per-theme #7529
[Emotion] Create hook utility for memoizing component styles per-theme #7529
Conversation
- replace previous `UseEuiCodeSyntaxVariables` memoization with this new architecture
4020fb1
to
cd77f3a
Compare
* Hook that memoizes the returned values of components style fns/generators | ||
* per-theme | ||
*/ | ||
export const useEuiMemoizedStyles = < |
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.
[Feedback request] I am not super in love with this name or anything and am very open to renaming it something snappier! (applies to the general context and file name as well)
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.
just useEuiStyles
imo
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.
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.
@kqualters-elastic I'm going to stick with the useEuiMemoizedStyles
name for now (it feels a little weird to drop Memoized
as it's the entire point of the utility). But if we can always change it later down the road if it gets tedious! It's currently a mostly internal/beta utility at this point in any case 🙏
if (!styleGenerator.name) { | ||
throw new Error( | ||
'Styles are memoized per function. Your style functions must be statically defined in order to not create a new map entry every rerender.' | ||
); | ||
} |
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 catch exists to attempt to catch anonymous functions that re-instantiate every rerender, which end up adding potentially hundreds of map entries that we don't need/want instead of a single one. Style generator functions should ideally be static constants to correctly memoize, since I'm using the function itself as the key to its mapped output.
I'd be interested in hearing people's thoughts as to whether that's a spicy decision. In theory I could use function.name
as the map key instead, but I think that leads to potentially worse end-user behavior (what if we use the same generic function name, e.g. styles()
in two places, and the wrong styles get mapped/sent back?).
Would be down to hear more thoughts about which approach feels safer either from a 'things break' perspective or 'going OOM' perspective 😅
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 should definitely keep the function reference as the key, so there's no easy way for them to collide!
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.
Quick note on the above: Tomasz's suggestion to change from Map()
to WeakMap()
(chatted about in Slack) should also help with issues where named functions get regenerated every rerender. 875dd01
type Styles = SerializedStyles | CSSObject | string | null; // Is this too restrictive? | ||
type StylesMaps = Record<string, Styles | Record<string, Styles>>; | ||
type MemoizedStylesMap = Map<Function, StylesMaps>; |
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 me trying to anticipate/type out the general shape that our component styles objects take, but I'm not sure if it's too restrictive honestly and if I should just do Map<Function, any>
🤷
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.
It looks good to me as is. We can always make it less restrictive later without breaking compatibility
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.
Tested locally and confirmed the performance benefits, especially when scaled to multiples of the same components per page. The code looks clean, and the implementation should scale well.
- to allow browser GC / avoid possible edge cases with massive RAM usage
- it's more verbose but oh well
This reverts commit cd77f3a.
Preview staging links for this PR:
|
💚 Build Succeeded
History
cc @cee-chen |
`v93.1.1`⏩ `v93.2.0` --- - Updated `EuiPageSidebar` and `EuiPageTemplate.Sidebar` with a new `hasEmbellish` prop (defaults to false) ([#7521](elastic/eui#7521)) - Added `diff` glyph to `EuiIcon` ([#7520](elastic/eui#7520)) - Added `newChat` glyph to `EuiIcon` ([#7524](elastic/eui#7524)) **Bug fixes** - Fixed `EuiSideNav` not correctly typing the `items` prop as required ([#7521](elastic/eui#7521)) - Fixed the `CSS is not defined` bug in `EuiPageTemplate` when rendering in some SSR environments, particularly Next.js v13 and up ([#7525](elastic/eui#7525)) - Fixed `EuiDataGrid` component to clean up timer from side effect on unmount ([#7534](elastic/eui#7534)) **Accessibility** - Fixed `EuiSideNav` to render a fallback aria-label on mobile toggles if no heading or mobile title exists ([#7521](elastic/eui#7521)) **CSS-in-JS conversions** - Converted `EuiSideNav` to Emotion; Removed the following Sass variables: ([#7521](elastic/eui#7521)) - `$euiSideNavEmphasizedBackgroundColor` - `$euiSideNavRootTextcolor` - `$euiSideNavBranchTextcolor` - `$euiSideNavSelectedTextcolor` - `$euiSideNavDisabledTextcolor` - Removed the `euiSideNavEmbellish` Sass mixin. Use the new `EuiPageSidebar` `hasEmbellish` prop instead ([#7521](elastic/eui#7521)) - Added a new memoization/performance optimization utility for CSS-in-JS styles ([#7529](elastic/eui#7529))
`v93.1.1`⏩ `v93.2.0` --- - Updated `EuiPageSidebar` and `EuiPageTemplate.Sidebar` with a new `hasEmbellish` prop (defaults to false) ([elastic#7521](elastic/eui#7521)) - Added `diff` glyph to `EuiIcon` ([elastic#7520](elastic/eui#7520)) - Added `newChat` glyph to `EuiIcon` ([elastic#7524](elastic/eui#7524)) **Bug fixes** - Fixed `EuiSideNav` not correctly typing the `items` prop as required ([elastic#7521](elastic/eui#7521)) - Fixed the `CSS is not defined` bug in `EuiPageTemplate` when rendering in some SSR environments, particularly Next.js v13 and up ([elastic#7525](elastic/eui#7525)) - Fixed `EuiDataGrid` component to clean up timer from side effect on unmount ([elastic#7534](elastic/eui#7534)) **Accessibility** - Fixed `EuiSideNav` to render a fallback aria-label on mobile toggles if no heading or mobile title exists ([elastic#7521](elastic/eui#7521)) **CSS-in-JS conversions** - Converted `EuiSideNav` to Emotion; Removed the following Sass variables: ([elastic#7521](elastic/eui#7521)) - `$euiSideNavEmphasizedBackgroundColor` - `$euiSideNavRootTextcolor` - `$euiSideNavBranchTextcolor` - `$euiSideNavSelectedTextcolor` - `$euiSideNavDisabledTextcolor` - Removed the `euiSideNavEmbellish` Sass mixin. Use the new `EuiPageSidebar` `hasEmbellish` prop instead ([elastic#7521](elastic/eui#7521)) - Added a new memoization/performance optimization utility for CSS-in-JS styles ([elastic#7529](elastic/eui#7529))
`v93.1.1`⏩ `v93.2.0` --- - Updated `EuiPageSidebar` and `EuiPageTemplate.Sidebar` with a new `hasEmbellish` prop (defaults to false) ([elastic#7521](elastic/eui#7521)) - Added `diff` glyph to `EuiIcon` ([elastic#7520](elastic/eui#7520)) - Added `newChat` glyph to `EuiIcon` ([elastic#7524](elastic/eui#7524)) **Bug fixes** - Fixed `EuiSideNav` not correctly typing the `items` prop as required ([elastic#7521](elastic/eui#7521)) - Fixed the `CSS is not defined` bug in `EuiPageTemplate` when rendering in some SSR environments, particularly Next.js v13 and up ([elastic#7525](elastic/eui#7525)) - Fixed `EuiDataGrid` component to clean up timer from side effect on unmount ([elastic#7534](elastic/eui#7534)) **Accessibility** - Fixed `EuiSideNav` to render a fallback aria-label on mobile toggles if no heading or mobile title exists ([elastic#7521](elastic/eui#7521)) **CSS-in-JS conversions** - Converted `EuiSideNav` to Emotion; Removed the following Sass variables: ([elastic#7521](elastic/eui#7521)) - `$euiSideNavEmphasizedBackgroundColor` - `$euiSideNavRootTextcolor` - `$euiSideNavBranchTextcolor` - `$euiSideNavSelectedTextcolor` - `$euiSideNavDisabledTextcolor` - Removed the `euiSideNavEmbellish` Sass mixin. Use the new `EuiPageSidebar` `hasEmbellish` prop instead ([elastic#7521](elastic/eui#7521)) - Added a new memoization/performance optimization utility for CSS-in-JS styles ([elastic#7529](elastic/eui#7529))
Summary
This PR is an initial setup PR for memoizing a component's Emotion styles per theme (instead of per component). This should (hopefully) greatly improve the performance of our Emotion styles/object generation, especially for components used repeatedly in many places, e.g. EuiCode, EuiButton, etc.
I replaced the memoization used in #7486 with this new architecture as a proof of concept; this PR should maintain the performance improvements of the linked PR while being easy to swap in and replace across all of EUI's existing components, e.g.
What this PR does not do
Handle class components - I have a spike for this in a separate branch, but would prefer to keep this PR small for now to focus on the overlying idea/concept
Memoize dark vs. light themes - I played around with trying to memoize both light and dark themes separately, to make swapping back and forth faster/more performant, but eventually gave up on the idea as the full theme reference re-instantiates every time
colorMode
changes (which makes sense as theme tokens are also changing). TBH I also anticipate this as not being necessary as most end-users will not be swapping back and forth frequently between light/dark modesConvert all components to use this util - this is a very basic setup and proof of concept of 2 components. If it passes muster, I'll open more follow-up PRs in batches later.
QA
euiCodeSyntaxVariables()
only fires 3 times on page loadeuiCodeSyntaxVariables()
does not fire on fullscreen mode state change nor on page navigationeuiCodeSyntaxVariables()
does fire again on dark/light screen mode change (because the theme variables have changed)General checklist
- [ ] Checked for accessibility including keyboard-only and screenreader modes- [ ] Checked in mobileand cypress tests- [ ] If applicable, added the breaking change issue label (and filled out the breaking change checklist)