Skip to content

feat: Add enableScopedSelectors prop to Stack that, when true, makes the Stack styles selectors be more scoped to not be as expensive in style recalculation#25397

Merged
khmakoto merged 6 commits intomicrosoft:masterfrom
khmakoto:stackScopedSelectors
Oct 27, 2022

Conversation

@khmakoto
Copy link
Member

Previous Behavior

The Stack component was very expensive for style recalculation performance because children selectors are not scoped (they are of the type > * and the browser evaluates selectors from right to left so it always matches everything in the page first). Some improvements were made in #25381, but the performance of the component was still not up to the level we wanted it to be.

Here are the results of a trace running on our v8 docsite's Stack page, where most of the most expensive selectors are Stack-related:

  • rulesFastRejected: 47342
  • rulesRejected: 5959
  • elapsed time: 5711 microseconds

New Behavior

This PR adds an enableScopedSelectors prop that, when set to true, makes the Stack children selectors be more scoped (they go from being of the type > * to being of the type > .ms-Stack-child). This greatly reduces the style recalculation cost.

We have gated this behavior behind a prop because this approach requires all immediate children of the Stack component to be able to receive and apply a className prop (excluding Fragments, in which case all their immediate children are the ones looked at instead). Since this was not a requirement before, this could create some issues for people who are already using the Stack component in their product, which would be a breaking change. By making this behavior opt-in, we can still provide a way for people to get the performance benefits without having a breaking change in the library.

Enabling this change for all Stacks in the examples of our v8 docsite's Stack page gives us the following numbers, which are much improved from before:

  • rulesFastRejected: 8281
  • rulesRejected: 5444
  • elapsed time: 2474 microseconds

Furthermore, the v8 docsite leverages the Stack component in several places that are not the examples. I experimented by enabling this change in the whole page (this change is not included in this PR and was just for experimentation) and I got much better results than even the ones above:

  • rulesFastRejected: 5288
  • rulesRejected: 4983
  • elapsed time: 672 microseconds

Related Issue(s)

Fixes #24259
Follow-up of #25381

KHMakoto added 2 commits October 26, 2022 17:19
…the Stack styles selectors be more scoped to not be as expensive in style recalculation.
@khmakoto khmakoto requested a review from a team as a code owner October 27, 2022 00:38
@khmakoto khmakoto self-assigned this Oct 27, 2022
@khmakoto khmakoto requested a review from a team as a code owner October 27, 2022 00:38
@size-auditor
Copy link

size-auditor bot commented Oct 27, 2022

Asset size changes

Project Bundle Baseline Size New Size Difference
office-ui-fabric-react fluentui-react-Stack 39.335 kB 40.244 kB ExceedsBaseline     909 bytes
office-ui-fabric-react fluentui-react-TeachingBubble 193.143 kB 193.751 kB ExceedsBaseline     608 bytes

ExceedsTolerance Over Tolerance (1024 B) ExceedsBaseline Over Baseline BelowBaseline Below Baseline New New Deleted  Removed 1 kB = 1000 B

Baseline commit: fe895d6bb4f9df0304f63f7e77acafef4b912925 (build)

@fabricteam
Copy link
Collaborator

fabricteam commented Oct 27, 2022

📊 Bundle size report

🤖 This report was generated against fe895d6bb4f9df0304f63f7e77acafef4b912925

@codesandbox-ci
Copy link

codesandbox-ci bot commented Oct 27, 2022

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 2423974:

Sandbox Source
@fluentui/react 8 starter Configuration
@fluentui/react-components 9 starter Configuration

@fabricteam
Copy link
Collaborator

fabricteam commented Oct 27, 2022

Perf Analysis (@fluentui/react)

Scenario Render type Master Ticks PR Ticks Iterations Status
StackWithIntrinsicChildren mount 2761 2968 5000 Possible regression
StackWithTextChildren mount 5695 5872 5000 Possible regression
All results

Scenario Render type Master Ticks PR Ticks Iterations Status
BaseButton mount 1402 1430 5000
Breadcrumb mount 3615 3488 1000
Checkbox mount 3164 3181 5000
CheckboxBase mount 2776 2908 5000
ChoiceGroup mount 5270 5372 5000
ComboBox mount 1467 1483 1000
CommandBar mount 11496 11475 1000
ContextualMenu mount 13075 13013 1000
DefaultButton mount 1639 1650 5000
DetailsRow mount 4316 4361 5000
DetailsRowFast mount 4375 4308 5000
DetailsRowNoStyles mount 4191 4243 5000
Dialog mount 3613 3637 1000
DocumentCardTitle mount 700 717 1000
Dropdown mount 4224 3834 5000
FocusTrapZone mount 2425 2473 5000
FocusZone mount 2346 2349 5000
GroupedList mount 62664 70674 2
GroupedList virtual-rerender 29610 29789 2
GroupedList virtual-rerender-with-unmount 108278 110366 2
GroupedListV2 mount 676 683 2
GroupedListV2 virtual-rerender 661 670 2
GroupedListV2 virtual-rerender-with-unmount 663 684 2
IconButton mount 2309 2303 5000
Label mount 870 875 5000
Layer mount 5146 5102 5000
Link mount 1033 992 5000
MenuButton mount 1990 2017 5000
MessageBar mount 2803 2773 5000
Nav mount 3966 3872 1000
OverflowSet mount 1664 1618 5000
Panel mount 3047 3001 1000
Persona mount 1530 1536 1000
Pivot mount 2006 1923 1000
PrimaryButton mount 1798 1816 5000
Rating mount 8299 8305 5000
SearchBox mount 1850 1796 5000
Shimmer mount 3358 3331 5000
Slider mount 2455 2514 5000
SpinButton mount 5639 5512 5000
Spinner mount 979 943 5000
SplitButton mount 3696 3716 5000
Stack mount 989 1011 5000
StackWithIntrinsicChildren mount 2761 2968 5000 Possible regression
StackWithTextChildren mount 5695 5872 5000 Possible regression
SwatchColorPicker mount 12366 12420 5000
TagPicker mount 3074 3140 5000
TeachingBubble mount 100640 99561 5000
Text mount 958 944 5000
TextField mount 1906 1864 5000
ThemeProvider mount 1819 1847 5000
ThemeProvider virtual-rerender 1280 1291 5000
ThemeProvider virtual-rerender-with-unmount 2566 2590 5000
Toggle mount 1326 1319 5000
buttonNative mount 644 658 5000

Copy link
Contributor

@jspurlin jspurlin left a comment

Choose a reason for hiding this comment

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

:shipit:

@khmakoto khmakoto merged commit 277d440 into microsoft:master Oct 27, 2022
@khmakoto khmakoto deleted the stackScopedSelectors branch October 27, 2022 23:26

/**
* Defines if scoped style selectors are enabled for the Stack component, which greatly helps in style recalculation
* performance but requires children of the Stack to be able to accept a className prop (excluding Fragments).
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:

Suggested change
* performance but requires children of the Stack to be able to accept a className prop (excluding Fragments).
* performance, but requires children of the Stack to be able to accept a className prop (excluding Fragments).

const disableShrinkStyles = {
// flexShrink styles are applied by the StackItem
'> *:not(.ms-StackItem)': {
[`> ${enableScopedSelectors ? '.' + GlobalClassNames.child : '*'}:not(.${StackItemGlobalClassNames.root})`]: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe save the scoped selector to a local const, since you use it in multiple places. It would also make this easier to read.

const childSelector = enableScopedSelectors ? `> .${GlobalClassNames.child}` : '> *';
Suggested change
[`> ${enableScopedSelectors ? '.' + GlobalClassNames.child : '*'}:not(.${StackItemGlobalClassNames.root})`]: {
[`${childSelector}:not(.${StackItemGlobalClassNames.root})`]: {

maxWidth: '100vw',

'> *': {
[`> ${enableScopedSelectors ? '.' + GlobalClassNames.child : '*'}`]: {
Copy link
Contributor

Choose a reason for hiding this comment

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

If you use my childSelector suggestion, this would be:

Suggested change
[`> ${enableScopedSelectors ? '.' + GlobalClassNames.child : '*'}`]: {
[childSelector]: {

Ditto below.

: null;
}

const childAsReactElement = child as React.ReactElement;
Copy link
Contributor

Choose a reason for hiding this comment

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

(nit) is this cast necessary? I think the React.isValidElement check above should narrow the type of child to React.ReactElement.

marcosmoura added a commit to marcosmoura/fluentui that referenced this pull request Oct 31, 2022
* master: (22 commits)
  fix(react-menu): removes exposing of internal type FluentTriggerComponent (microsoft#25410)
  fix(react-popover): removes exposing of internal type FluentTriggerComponent (microsoft#25411)
  applying package updates
  fix(react-tooltip): removes exposing of internal type FluentTriggerComponent (microsoft#25409)
  chore: Reducing bundle size of Stack by moving selector used in multiple places to local const (microsoft#25429)
  docs(rfcs): Simple component implementation (microsoft#25139)
  Fix migration publishing (microsoft#25422)
  Integrate storywright for story tests - As part of exploring screener alternative (microsoft#25399)
  fix(react-utilities): exposes internal methods used in API surface (microsoft#25406)
  fix(react-dialog): removes exposing of internal type FluentTriggerComponent (microsoft#25408)
  fix(react-context-selector): exposes internal type ContextSelector (microsoft#25404)
  fix(react-aria): exposes internal leaking types (microsoft#25403)
  fix(react-shared-contexts): exposes internal leaks used in the API surface (microsoft#25405)
  fix(react-positioning): exposes new typings to avoid exposing internal methods (microsoft#25407)
  applying package updates
  fix: Allowing DatePicker to be focusable within FocusZones by default (microsoft#25428)
  fix: Pad in slider so the thumb does not render outside the bounds of the root element (microsoft#25378)
  feat: Add enableScopedSelectors prop to Stack that, when true, makes the Stack styles selectors be more scoped to not be as expensive in style recalculation (microsoft#25397)
  fix(react-avatar): Remove gaps between AvatarGroupItem/OveflowButton and its outline (microsoft#25382)
  fix: Combobox text attribute ignored when empty string is passed (microsoft#24665)
  ...
NotWoods pushed a commit to NotWoods/fluentui that referenced this pull request Nov 18, 2022
…the Stack styles selectors be more scoped to not be as expensive in style recalculation (microsoft#25397)

* feat: Add enableScopedSelectors prop to Stack that, when true, makes the Stack styles selectors be more scoped to not be as expensive in style recalculation.

* Adding change file.

* Only apply stack child classname to children if enableScopeSelectors is set to true.

* Addressing PR feedback.

* Small improvement.

Co-authored-by: KHMakoto <humberto_makoto@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Stack style selectors are greatly contributing to recalcStyle cost

7 participants