Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

feat(Box): optimize#989

Closed
kuzhelov wants to merge 20 commits intomasterfrom
feat/optimize-box
Closed

feat(Box): optimize#989
kuzhelov wants to merge 20 commits intomasterfrom
feat/optimize-box

Conversation

@kuzhelov
Copy link
Contributor

@kuzhelov kuzhelov commented Feb 28, 2019

  • no test regressions
  • no Screener regressions

Approach

To prevent breaking changes, this PR has preserved original Box component's functionality exported from Stardust.

At the same time, lightweight version of Box component was introduced, all the common pieces between these two were reused and placed in the common.ts file. While these two components have the same API and, in most cases, are interchangeable, the reason previous version of Box was preserved is that lightweight Box doesn't support style customizations via theming.

While this theme-based styles support for Box is not used in Stardust anywhere, however, it might be used by Stardust clients. Eventually I might see it being organised the following way:

  • there would be internal utility component in Stardust. It will be used as one of the basis building blocks (the same way Box was used), and will have minimal abstraction costs
  • component with reacher functionality will be exported to clients - so that all the Stardust features (e.g. theme-based styling) will be supported but, at the same time, it would imply higher perf costs.

With changes made in this PR Stardust consumes light-weight Box component internally.

Perf Numbers (from yarn perf)

List with 10 elements, median from 10x50 runs

  • with unoptimized Box - 67ms
  • with optimized Box - 58ms (~13%)
  • *with ItemLayout - 57ms (~14%)

List with 50 elements, median from 10x50runs

  • with unoptimized Box - 235ms
  • with optimized Box - 200ms (~14%)
  • *with ItemLayout - 203ms (~13%)

Opened questions

  • primarily, naming
    • Box and BoxHeavy vs BoxLight and Box
    • different display names are suggested in the PR (Box and BoxHeavy), to make it clear from React Debug Tools that unoptimized Box is used - should we use the same instead for both?
    • same class name is suggested for both (to make these equal to static CSS) - should we use different ones?
  • additional suggestions to optimize things while preserving correctness 👍

Areas for potential improvements

  • optimize Flex rendering logic
    • optimize styles rendering - most probably we won't need theme mount, as Flex styles shouldn't be customized by theme
    • there is no need to calculate other Stardust bits, such as accessibility, etc
    • use margins instead of Flex.Gap element - will allow to gain ~5% of perf improvement
  • generally optimize rendering logic of Stardust components
    • do not calculate Stardust bits that are not necessary for element
    • *do not mount FelaTheme element each time, in some cases theme could be passed as prop

@kuzhelov kuzhelov changed the title feat(Box): optimize [WIP] feat(Box): optimize Feb 28, 2019
@DustyTheBot
Copy link
Collaborator

Warnings
⚠️ There are no updates provided to CHANGELOG. Ensure there are no publicly visible changes introduced by this PR.

Generated by 🚫 dangerJS

@codecov
Copy link

codecov bot commented Feb 28, 2019

Codecov Report

Merging #989 into master will increase coverage by 0.01%.
The diff coverage is 91.48%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #989      +/-   ##
==========================================
+ Coverage   81.15%   81.16%   +0.01%     
==========================================
  Files         673      675       +2     
  Lines        8654     8691      +37     
  Branches     1528     1529       +1     
==========================================
+ Hits         7023     7054      +31     
- Misses       1616     1622       +6     
  Partials       15       15
Impacted Files Coverage Δ
packages/react/src/components/Box/common.tsx 100% <100%> (ø)
packages/react/src/components/List/ListItem.tsx 100% <100%> (ø) ⬆️
packages/react/src/components/Box/Box.tsx 100% <100%> (ø) ⬆️
packages/react/src/index.ts 100% <100%> (ø) ⬆️
packages/react/src/components/Box/BoxHeavy.tsx 60% <60%> (ø)
packages/react/src/lib/createComponent.tsx 91.66% <0%> (-8.34%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4139454...88f2869. Read the comment docs.

@kuzhelov kuzhelov changed the title [WIP] feat(Box): optimize feat(Box): optimize Mar 1, 2019
})
}

const handledProps = Object.keys(boxPropTypes)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move to common? it took me a while to understand where it's used

const { as: ElementType, theme } = props

if (!theme) {
return withThemeContext(theme =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: inline withThemeContext? it's only used once:

if (theme) {
  return render({
    ElementType,
    props,
    renderer: theme.renderer,
  })
}

return (
  <FelaTheme>
    {theme => render({
      ElementType,
      props,
      renderer: theme.renderer,
    })}
  </FelaTheme>
)


const unhandledProps = getUnhandledProps({ handledProps }, props)
const classes = {
root: renderer.renderRule(callable(styles), props),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the main optimization is somewhere here, can you explain in a few words how it's achieved?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that here we imply that styles is an already prepared object, so that we don't need to

  • consume theme styles, merge them
  • merge theme variables
  • resolve styles with merged variables

The thing we are requiring by optimised box is set of already resolved styles - and this is, essentially, the thing that we always have in Stardust code

>
{childrenExist(children) ? children : content}
</ElementType>
const Box: React.FunctionComponent<BoxProps> & { create: Function } = props => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: React.FunctionComponent -> React.FC

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would rather leave it as it is, due to its readability

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid all abbreviations in the repo. Code is for humans to read. Let minifiers and uglifiers make it small and unreadable.

@@ -0,0 +1,9 @@
const getDisplayName = (Component: React.ReactType) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this method used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this a merge artefact, thank you!

export { default as Avatar, AvatarProps } from './components/Avatar/Avatar'

export { default as Box, BoxProps } from './components/Box/Box'
export { default as Box, BoxProps } from './components/Box/BoxHeavy'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we export BoxHeavy and what does it bring in addition to the new Box that we still need it?

Copy link
Contributor Author

@kuzhelov kuzhelov Mar 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the main reason is mentioned in the description:

To prevent breaking changes, this PR has preserved original Box component's functionality exported from Stardust.

Essentially, we would like to prevent Stardust from breaking changes to the clients that might already used theme-based styling approach to Box. Yes, while Box theme-based styles are not introduced anywhere in Stardust, it might be the case that client had already introduced some style overrides that target Box component at places of custom client's usage.

Consider the following example:

// this might be an approach taken to circumvent current Stardust problem
// of positioning support
<Box variables={{ absolutePositioned: true }}>
   {someContent}
</Box>

with client's styles

export const boxStyles = {
  root: ({ variables }) => ({ 
     variables.absolutePositioned && {
        position: absolute
    }
  })

Note that in that case it is always the case that client won't like to target inner Stardust Box elements - the intent is to introduce styles to only those boxes that are used on client side. If we would export light version of Box that doesn't support theme-based styling, then breaking change will be introduced to the client. However, if we would export the same Box component as before, there won't be any breaking changes happen

@levithomason
Copy link
Member

levithomason commented Mar 1, 2019

I think the solution here is too complex. We don't want to make optimizations that involve forking or deviating individual components. We also don't want to introduce any new APIs or ideas or require any refactoring in our optimizations. We should focus on removing complexity and special cases and put a hard stop on adding any special cases, like a light weight Box.

Improvements made should be first be focused on core pieces such as mergeThemes, factories, UIComponent, ProviderConsumer, etc. Areas from which all other abstractions are derived. If these hot paths are optimized, every component will benefit drastically.

Example, just by _.memoize()ing the functions in mergeThemes() almost all component render times are reduced by around 75-95%:

#1006

==============================
Button.perf.tsx
BEFORE  AFTER  SAVED  
42.83   2.89   93%    
43.53   1.57   96%    
10.57   1.11   89%    
86.06   19.54  77%    

==============================
Header.perf.tsx
BEFORE  AFTER  SAVED  
1.13    1.3    -15%   
0.69    0.66   4%     
0.45    0.46   -2%    
4.71    7.63   -62%   

==============================
Icon.perf.tsx
BEFORE  AFTER  SAVED  
8.34    1.33   84%    
8.69    0.56   94%    
3.27    0.38   88%    
16.25   7.99   51%    

==============================
Loader.perf.tsx
BEFORE  AFTER  SAVED  
25.84   1.86   93%    
31.44   1.14   96%    
5.93    0.82   86%    
48.51   11.24  77%    

==============================
HeaderDescription.perf.tsx
BEFORE  AFTER  SAVED  
7.35    2.25   69%    
5.86    1.3    78%    
2.53    0.89   65%    
15.79   16.41  -4%    

==============================
Attachment.perf.tsx
BEFORE  AFTER  SAVED  
35.33   2.47   93%    
26.54   1.44   95%    
7.94    1.04   87%    
68.63   11.54  83%    

==============================
Divider.perf.tsx
BEFORE  AFTER  SAVED  
8.26    1.18   86%    
8.42    0.58   93%    
3.27    0.41   87%    
16.49   11.22  32%    

==============================
List.perf.tsx
BEFORE  AFTER  SAVED  
68.27   68.78  -1%    
67.67   65.39  3%     
54.4    56.3   -3%    
98.21   99.29  -1%    

==============================
Chat.perf.tsx
BEFORE  AFTER  SAVED  
284.64  40.03  86%    
276.29  39.44  86%    
208.04  30.11  86%    
426.62  82.98  81% 

@kuzhelov kuzhelov closed this Apr 18, 2019
@kuzhelov
Copy link
Contributor Author

closing as this strategy but based on static API for React context was implemented for the entire Stardust library: #1163

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants