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

feat(useStyles): add caching when no inline overrides are applied #2309

Merged
merged 39 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
53ae41b
wip
mnajdova Feb 3, 2020
f90e31a
cleanup
mnajdova Feb 3, 2020
4ac7bfd
-prettier fixes
mnajdova Feb 3, 2020
db1e7aa
-disabled tests
mnajdova Feb 3, 2020
77d0748
-test updates
mnajdova Feb 3, 2020
f213a4e
-test fixes
mnajdova Feb 4, 2020
693d237
wip
layershifter Feb 4, 2020
d7be310
Merge branch 'master' into feat/add-style-caching
mnajdova Feb 4, 2020
e4c7fb8
-fixes
mnajdova Feb 4, 2020
6372814
wip
mnajdova Feb 4, 2020
f7fbcd1
prettier fixes
mnajdova Feb 5, 2020
84eb6d4
prettier fixes
mnajdova Feb 5, 2020
7d45f26
-fixes in resolveStylesAndCLasses
mnajdova Feb 5, 2020
6083215
-removed variables cache from Provider's context
mnajdova Feb 5, 2020
3d7732d
-prettier fixes
mnajdova Feb 5, 2020
eb389d5
-added provider flag for enabling caching
mnajdova Feb 7, 2020
53bab76
-prettier fixes
mnajdova Feb 7, 2020
6f3b551
Merge branch 'master' into feat/add-style-caching
mnajdova Feb 7, 2020
214a6ac
-addressing comments
mnajdova Feb 7, 2020
f220251
-addressing comments
mnajdova Feb 7, 2020
0a308bd
prettier fixes
mnajdova Feb 7, 2020
e1f75d3
-updated comments
mnajdova Feb 7, 2020
399e8ac
-refactored key generation
mnajdova Feb 7, 2020
d403c1e
-prettier fixes
mnajdova Feb 7, 2020
697ca82
-added one Provider's prop performance
mnajdova Feb 7, 2020
0b970c4
-moved PrimitiveProps typings
mnajdova Feb 7, 2020
9dae249
prettier fixes
mnajdova Feb 7, 2020
cfb0462
-fixed unit tests
mnajdova Feb 10, 2020
e951ba8
-renamed resolveStylesAndClasses to resolveStyles and merged two func…
mnajdova Feb 10, 2020
ac92aa9
-prettier fixes
mnajdova Feb 10, 2020
40712af
-added component variables cache
mnajdova Feb 10, 2020
f6db0e7
-updated params for resolveStyles
mnajdova Feb 10, 2020
1b91683
-addressed final comments
mnajdova Feb 10, 2020
79f2ce2
prettier fixes
mnajdova Feb 10, 2020
6eea045
Merge branch 'master' into feat/add-style-caching
mnajdova Feb 10, 2020
7b84579
-fixed typings
mnajdova Feb 10, 2020
c03fe01
-updated changelog
mnajdova Feb 10, 2020
3111bbd
-improved tests
mnajdova Feb 10, 2020
d89bf0e
-fixed changelog
mnajdova Feb 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class App extends React.Component<any, ThemeContextData> {
return (
<ThemeContext.Provider value={this.state}>
<Provider
enableCaching={true}
as={React.Fragment}
theme={mergeThemes(themes.fontAwesome, themes[themeName], {
staticStyles: [
Expand Down
6 changes: 3 additions & 3 deletions packages/react-bindings/src/hooks/useStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '../styles/types'
import getStyles from '../styles/getStyles'

type PrimitiveProps = Record<string, boolean | number | string | undefined>
export type PrimitiveProps = Record<string, boolean | number | string | undefined>
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
type UseStylesOptions<StyleProps extends PrimitiveProps> = {
className?: string
mapPropsToStyles?: () => StyleProps
Expand Down Expand Up @@ -45,9 +45,9 @@ type InlineStyleProps<StyleProps> = {

const defaultContext: StylesContextValue<{ renderRule: RendererRenderRule }> = {
disableAnimations: false,
enableCaching: false, // TODO: check id this is correct defualt
renderer: { renderRule: () => '' },
theme: emptyTheme,
_internal_resolvedComponentVariables: {},
}

const useStyles = <StyleProps extends PrimitiveProps>(
Expand Down Expand Up @@ -81,7 +81,7 @@ const useStyles = <StyleProps extends PrimitiveProps>(
rtl,
saveDebug: fluentUIDebug => (debug.current = { fluentUIDebug }),
theme: context.theme,
_internal_resolvedComponentVariables: context._internal_resolvedComponentVariables,
enableCaching: context.enableCaching,
})

return { classes, styles: resolvedStyles }
Expand Down
186 changes: 133 additions & 53 deletions packages/react-bindings/src/styles/getStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,29 @@ import {
ComponentSlotStylesPrepared,
ComponentSlotStylesResolved,
ComponentStyleFunctionParam,
ComponentVariablesInput,
ComponentVariablesObject,
DebugData,
ICSSInJSStyle,
isDebugEnabled,
mergeComponentStyles,
mergeComponentVariables,
PropsWithVarsAndStyles,
ThemePrepared,
withDebugId,
} from '@fluentui/styles'
import cx from 'classnames'
import * as _ from 'lodash'

import resolveStylesAndClasses from './resolveStylesAndClasses'
import resolveStylesAndClasses, { ResolveStylesResult } from './resolveStylesAndClasses'
import {
ComponentDesignProp,
ComponentSlotClasses,
RendererParam,
RendererRenderRule,
StylesContextValue,
} from '../styles/types'
import { PrimitiveProps } from '../hooks/useStyles'

type GetStylesOptions = StylesContextValue<{
renderRule: RendererRenderRule
Expand All @@ -42,75 +45,80 @@ export type GetStylesResult = {
theme: StylesContextValue['theme']
}

const variablesCache = new WeakMap<ThemePrepared, Record<string, ComponentVariablesObject>>()

const resolveVariables = (
displayName: string,
theme: ThemePrepared,
variables: ComponentVariablesInput | undefined,
): ComponentVariablesObject => {
//
// Simple caching model, works only if there is no `props.variables`
// Resolves variables for this component, cache the result in provider
//

if (!variablesCache.has(theme)) {
variablesCache.set(theme, {})
}

const variablesThemeCache = variablesCache.get(theme) || {}

if (!variablesThemeCache[displayName]) {
variablesThemeCache[displayName] =
callable(theme.componentVariables[displayName])(theme.siteVariables) || {}
variablesCache.set(theme, variablesThemeCache)
}

if (variables === undefined) {
return variablesThemeCache[displayName]
}

return mergeComponentVariables(
variablesThemeCache[displayName],
withDebugId(variables, 'props.variables'),
)(theme.siteVariables)
}

const getStyles = (options: GetStylesOptions): GetStylesResult => {
const {
className,
className: componentClassName,
disableAnimations,
displayName,
props,
renderer,
rtl,
saveDebug,
theme,
_internal_resolvedComponentVariables: resolvedComponentVariables,
enableCaching,
} = options
const { className, design, styles, variables, ...restProps } = props

// Resolve variables for this component, cache the result in provider
if (!resolvedComponentVariables[displayName]) {
resolvedComponentVariables[displayName] =
callable(theme.componentVariables[displayName])(theme.siteVariables) || {} // component variables must not be undefined/null (see mergeComponentVariables contract)
}

// Merge inline variables on top of cached variables
const resolvedVariables = props.variables
? mergeComponentVariables(
resolvedComponentVariables[displayName],
withDebugId(props.variables, 'props.variables'),
)(theme.siteVariables)
: resolvedComponentVariables[displayName]
//
// To compute styles are going through three stages:
// - resolve variables (siteVariables => componentVariables + props.variables)
// - resolve styles (with resolvedVariables & props.styles & props.design)
// - compute classes (with resolvedStyles)
//

// Resolve styles using resolved variables, merge results, allow props.styles to override
let mergedStyles: ComponentSlotStylesPrepared = theme.componentStyles[displayName] || {
root: () => ({}),
}
const resolvedVariables = resolveVariables(displayName, theme, props.variables)

const hasInlineOverrides = !_.isNil(props.design) || !_.isNil(props.styles)
//
// STYLES
//

if (hasInlineOverrides) {
mergedStyles = mergeComponentStyles(
mergedStyles,
props.design && withDebugId({ root: props.design }, 'props.design'),
props.styles &&
withDebugId({ root: props.styles } as ComponentSlotStylesInput, 'props.styles'),
)
}
const noInlineOverrides = !(design || styles || variables)

const styleParam: ComponentStyleFunctionParam = {
displayName,
props,
variables: resolvedVariables,
const { classes, resolvedStyles, resolvedStylesDebug } = resolveStyles({
theme,
rtl,
disableAnimations,
}

// Fela plugins rely on `direction` param in `theme` prop instead of RTL
// Our API should be aligned with it
// Heads Up! Keep in sync with Design.tsx render logic
const direction = rtl ? 'rtl' : 'ltr'
const felaParam: RendererParam = {
theme: { direction },
displayName,
disableAnimations,
displayName, // does not affect styles, only used by useEnhancedRenderer in docs
}

const { resolvedStyles, resolvedStylesDebug, classes } = resolveStylesAndClasses(
mergedStyles,
styleParam,
(style: ICSSInJSStyle) => renderer.renderRule(() => style, felaParam),
)

classes.root = cx(className, classes.root, props.className)
rtl,
renderer,
props,
resolvedVariables,
cacheEnabled: enableCaching && noInlineOverrides,
styleProps: restProps,
})

// conditionally add sources for evaluating debug information to component
if (process.env.NODE_ENV !== 'production' && isDebugEnabled) {
Expand Down Expand Up @@ -140,6 +148,8 @@ const getStyles = (options: GetStylesOptions): GetStylesResult => {
})
}

classes.root = cx(componentClassName, classes.__root, className)

return {
classes,
variables: resolvedVariables,
Expand All @@ -148,4 +158,74 @@ const getStyles = (options: GetStylesOptions): GetStylesResult => {
}
}

const resolveStyles = ({
theme,
displayName,
props,
resolvedVariables,
rtl,
disableAnimations,
renderer,
cacheEnabled,
styleProps,
}: {
theme: ThemePrepared
displayName: string
props: PropsWithVarsAndStyles & { design?: ComponentDesignProp }
resolvedVariables: object
rtl: boolean
disableAnimations: boolean
renderer: {
renderRule: RendererRenderRule
}
cacheEnabled: boolean | undefined
styleProps: PrimitiveProps
}): ResolveStylesResult => {
// Resolve styles using resolved variables, merge results, allow props.styles to override
let mergedStyles: ComponentSlotStylesPrepared = theme.componentStyles[displayName] || {
root: () => ({}),
}
const hasInlineStylesOverrides = !_.isNil(props.design) || !_.isNil(props.styles)

if (hasInlineStylesOverrides) {
mergedStyles = mergeComponentStyles(
mergedStyles,
props.design && withDebugId({ root: props.design }, 'props.design'),
props.styles &&
withDebugId({ root: props.styles } as ComponentSlotStylesInput, 'props.styles'),
)
}

const styleParam: ComponentStyleFunctionParam = {
displayName,
props,
variables: resolvedVariables,
theme,
rtl,
disableAnimations,
}

// Fela plugins rely on `direction` param in `theme` prop instead of RTL
// Our API should be aligned with it
// Heads Up! Keep in sync with Design.tsx render logic
const direction = rtl ? 'rtl' : 'ltr'
const felaParam: RendererParam = {
theme: { direction },
disableAnimations,
displayName, // does not affect styles, only used by useEnhancedRenderer in docs
}

const result = resolveStylesAndClasses(
mergedStyles,
styleParam,
(style: ICSSInJSStyle) => renderer.renderRule(() => style, felaParam),
cacheEnabled,
displayName,
theme,
styleProps,
)

return result
}

export default getStyles
Loading