From 0abab261f0199f04de706bee6a5035a1f742d61f Mon Sep 17 00:00:00 2001 From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com> Date: Wed, 31 Jan 2024 21:58:29 +0100 Subject: [PATCH] [EuiCode/Block] Avoid recomputing code syntax colors (#7486) Co-authored-by: Cee Chen --- changelogs/upcoming/7486.md | 3 + src/components/code/code.styles.ts | 14 +- src/components/code/code.tsx | 4 +- src/components/code/code_block.styles.ts | 21 +- src/components/code/code_block.tsx | 8 +- .../code/code_block_controls.styles.ts | 10 +- src/components/code/code_block_controls.tsx | 7 +- .../code/code_block_full_screen.tsx | 5 +- src/components/code/code_syntax.styles.ts | 343 +++++++++--------- 9 files changed, 218 insertions(+), 197 deletions(-) create mode 100644 changelogs/upcoming/7486.md diff --git a/changelogs/upcoming/7486.md b/changelogs/upcoming/7486.md new file mode 100644 index 00000000000..c7a8b913065 --- /dev/null +++ b/changelogs/upcoming/7486.md @@ -0,0 +1,3 @@ +**Performance** + +- Improved the amount of recomputed styles being generated by `EuiCode` and `EuiCodeBlock` diff --git a/src/components/code/code.styles.ts b/src/components/code/code.styles.ts index b3a2fb4f78b..363a894f741 100644 --- a/src/components/code/code.styles.ts +++ b/src/components/code/code.styles.ts @@ -9,11 +9,13 @@ import { css } from '@emotion/react'; import { logicalShorthandCSS } from '../../global_styling'; import { UseEuiTheme } from '../../services'; -import { euiCodeSyntaxColors, euiCodeSyntaxTokens } from './code_syntax.styles'; +import { UseEuiCodeSyntaxVariables } from './code_syntax.styles'; -export const euiCodeStyles = (euiThemeContext: UseEuiTheme) => { +export const euiCodeStyles = ( + euiThemeContext: UseEuiTheme, + euiCodeSyntaxVariables: UseEuiCodeSyntaxVariables +) => { const { euiTheme } = euiThemeContext; - const euiCodeSyntax = euiCodeSyntaxColors(euiThemeContext); return { /* @@ -23,12 +25,12 @@ export const euiCodeStyles = (euiThemeContext: UseEuiTheme) => { font-family: ${euiTheme.font.familyCode}; font-size: 0.9em; /* 1 */ ${logicalShorthandCSS('padding', '0.2em 0.5em')} /* 1 */ - background: ${euiCodeSyntax.backgroundColor}; + background: ${euiCodeSyntaxVariables.backgroundColor}; border-radius: ${euiTheme.border.radius.small}; font-weight: ${euiTheme.font.weight.bold}; - color: ${euiCodeSyntax.inlineCodeColor}; + color: ${euiCodeSyntaxVariables.inlineCodeColor}; - ${euiCodeSyntaxTokens(euiThemeContext)} + ${euiCodeSyntaxVariables.tokensCss} `, transparentBackground: css` background: transparent; diff --git a/src/components/code/code.tsx b/src/components/code/code.tsx index 2f7a4013a18..e982e28e405 100644 --- a/src/components/code/code.tsx +++ b/src/components/code/code.tsx @@ -16,6 +16,7 @@ import { getHtmlContent, } from './utils'; import { useEuiTheme } from '../../services'; +import { useEuiCodeSyntaxVariables } from './code_syntax.styles'; import { euiCodeStyles } from './code.styles'; export type EuiCodeProps = EuiCodeSharedProps; @@ -46,8 +47,7 @@ export const EuiCode: FunctionComponent = ({ const classes = classNames('euiCode', className); - const euiTheme = useEuiTheme(); - const styles = euiCodeStyles(euiTheme); + const styles = euiCodeStyles(useEuiTheme(), useEuiCodeSyntaxVariables()); const cssStyles = [ styles.euiCode, transparentBackground && styles.transparentBackground, diff --git a/src/components/code/code_block.styles.ts b/src/components/code/code_block.styles.ts index 454a97109c0..f10cf2f5a7e 100644 --- a/src/components/code/code_block.styles.ts +++ b/src/components/code/code_block.styles.ts @@ -21,20 +21,23 @@ import { mathWithUnits, } from '../../global_styling'; import { UseEuiTheme } from '../../services'; -import { euiCodeSyntaxColors, euiCodeSyntaxTokens } from './code_syntax.styles'; -export const euiCodeBlockStyles = (euiThemeContext: UseEuiTheme) => { +import { UseEuiCodeSyntaxVariables } from './code_syntax.styles'; + +export const euiCodeBlockStyles = ( + euiThemeContext: UseEuiTheme, + euiCodeSyntaxVariables: UseEuiCodeSyntaxVariables +) => { const { euiTheme } = euiThemeContext; - const euiCodeSyntax = euiCodeSyntaxColors(euiThemeContext); return { euiCodeBlock: css` max-inline-size: 100%; display: block; position: relative; - background: ${euiCodeSyntax.backgroundColor}; + background: ${euiCodeSyntaxVariables.backgroundColor}; - ${euiCodeSyntaxTokens(euiThemeContext)} + ${euiCodeSyntaxVariables.tokensCss} `, // Font size s: css` @@ -131,15 +134,17 @@ export const euiCodeBlockPreStyles = (euiThemeContext: UseEuiTheme) => { }; }; -export const euiCodeBlockCodeStyles = (euiThemeContext: UseEuiTheme) => { +export const euiCodeBlockCodeStyles = ( + euiThemeContext: UseEuiTheme, + euiCodeSyntaxVariables: UseEuiCodeSyntaxVariables +) => { const { euiTheme } = euiThemeContext; - const euiCodeSyntax = euiCodeSyntaxColors(euiThemeContext); return { euiCodeBlock__code: css` font-family: ${euiTheme.font.familyCode}; font-size: inherit; - color: ${euiCodeSyntax.color}; + color: ${euiCodeSyntaxVariables.color}; display: block; `, isVirtualized: css` diff --git a/src/components/code/code_block.tsx b/src/components/code/code_block.tsx index 199f84562e4..71eb83f986a 100644 --- a/src/components/code/code_block.tsx +++ b/src/components/code/code_block.tsx @@ -32,6 +32,7 @@ import { euiCodeBlockPreStyles, euiCodeBlockCodeStyles, } from './code_block.styles'; +import { useEuiCodeSyntaxVariables } from './code_syntax.styles'; // Based on observed line height for non-virtualized code blocks const fontSizeToRowHeightMap = { @@ -122,6 +123,7 @@ export const EuiCodeBlock: FunctionComponent = ({ ...rest }) => { const euiTheme = useEuiTheme(); + const euiCodeSyntaxVariables = useEuiCodeSyntaxVariables(); const language = useMemo( () => checkSupportedLanguage(_language), [_language] @@ -175,7 +177,7 @@ export const EuiCodeBlock: FunctionComponent = ({ const hasControls = !!(copyButton || fullScreenButton); const hasBothControls = !!(copyButton && fullScreenButton); - const styles = euiCodeBlockStyles(euiTheme); + const styles = euiCodeBlockStyles(euiTheme, euiCodeSyntaxVariables); const cssStyles = [ styles.euiCodeBlock, styles[fontSize], @@ -236,7 +238,7 @@ export const EuiCodeBlock: FunctionComponent = ({ ]); const codeProps = useMemo(() => { - const styles = euiCodeBlockCodeStyles(euiTheme); + const styles = euiCodeBlockCodeStyles(euiTheme, euiCodeSyntaxVariables); const cssStyles = [ styles.euiCodeBlock__code, isVirtualized && styles.isVirtualized, @@ -248,7 +250,7 @@ export const EuiCodeBlock: FunctionComponent = ({ 'data-code-language': language, ...rest, }; - }, [language, euiTheme, isVirtualized, rest]); + }, [language, euiTheme, euiCodeSyntaxVariables, isVirtualized, rest]); return (
{ +export const euiCodeBlockControlsStyles = ( + euiThemeContext: UseEuiTheme, + euiCodeSyntaxVariables: UseEuiCodeSyntaxVariables +) => { const { euiTheme } = euiThemeContext; - const euiCodeSyntax = euiCodeSyntaxColors(euiThemeContext); return { euiCodeBlock__controls: css` @@ -28,7 +30,7 @@ export const euiCodeBlockControlsStyles = (euiThemeContext: UseEuiTheme) => { display: flex; flex-direction: column; gap: ${euiTheme.size.xs}; - background: ${euiCodeSyntax.backgroundColor}; + background: ${euiCodeSyntaxVariables.backgroundColor}; `, offset: { none: css` diff --git a/src/components/code/code_block_controls.tsx b/src/components/code/code_block_controls.tsx index 8511456968a..607f21d6170 100644 --- a/src/components/code/code_block_controls.tsx +++ b/src/components/code/code_block_controls.tsx @@ -9,14 +9,17 @@ import React, { FC, Fragment, ReactNode } from 'react'; import { useEuiTheme } from '../../services'; import type { EuiCodeBlockPaddingSize } from './code_block'; +import { useEuiCodeSyntaxVariables } from './code_syntax.styles'; import { euiCodeBlockControlsStyles } from './code_block_controls.styles'; export const EuiCodeBlockControls: FC<{ controls: ReactNode[]; paddingSize: EuiCodeBlockPaddingSize; }> = ({ paddingSize, controls }) => { - const euiTheme = useEuiTheme(); - const styles = euiCodeBlockControlsStyles(euiTheme); + const styles = euiCodeBlockControlsStyles( + useEuiTheme(), + useEuiCodeSyntaxVariables() + ); const cssStyles = [styles.euiCodeBlock__controls, styles.offset[paddingSize]]; const hasControls = controls.some((control) => !!control); diff --git a/src/components/code/code_block_full_screen.tsx b/src/components/code/code_block_full_screen.tsx index 96465e78096..21b12d52497 100644 --- a/src/components/code/code_block_full_screen.tsx +++ b/src/components/code/code_block_full_screen.tsx @@ -19,6 +19,7 @@ import { useEuiI18n } from '../i18n'; import { EuiButtonIcon } from '../button'; import { EuiFocusTrap } from '../focus_trap'; import { EuiOverlayMask } from '../overlay_mask'; +import { useEuiCodeSyntaxVariables } from './code_syntax.styles'; import { euiCodeBlockStyles } from './code_block.styles'; /** @@ -91,9 +92,7 @@ export const useFullScreen = ({ export const EuiCodeBlockFullScreenWrapper: FunctionComponent< PropsWithChildren > = ({ children }) => { - const euiThemeContext = useEuiTheme(); - - const styles = euiCodeBlockStyles(euiThemeContext); + const styles = euiCodeBlockStyles(useEuiTheme(), useEuiCodeSyntaxVariables()); const cssStyles = [ styles.euiCodeBlock, styles.l, // Force fullscreen to use large font diff --git a/src/components/code/code_syntax.styles.ts b/src/components/code/code_syntax.styles.ts index 65d71c4fd1c..961b2fd5b57 100644 --- a/src/components/code/code_syntax.styles.ts +++ b/src/components/code/code_syntax.styles.ts @@ -6,181 +6,186 @@ * Side Public License, v 1. */ +import { useMemo } from 'react'; import { - UseEuiTheme, + useEuiTheme, makeHighContrastColor, euiPaletteColorBlind, } from '../../services'; const visColors = euiPaletteColorBlind(); -export const euiCodeSyntaxColors = (euiThemeContext: UseEuiTheme) => { - const { euiTheme } = euiThemeContext; - - const backgroundColor = euiTheme.colors.lightestShade; - - return { - backgroundColor: backgroundColor, - color: makeHighContrastColor(euiTheme.colors.text)(backgroundColor), - inlineCodeColor: makeHighContrastColor(visColors[3])(backgroundColor), - selectedBackgroundColor: 'inherit', - commentColor: makeHighContrastColor(euiTheme.colors.subduedText)( - backgroundColor - ), - selectorTagColor: 'inherit', - stringColor: makeHighContrastColor(visColors[2])(backgroundColor), - tagColor: makeHighContrastColor(visColors[1])(backgroundColor), - nameColor: makeHighContrastColor(visColors[1])(backgroundColor), - numberColor: makeHighContrastColor(visColors[0])(backgroundColor), - keywordColor: makeHighContrastColor(visColors[3])(backgroundColor), - functionTitleColor: 'inherit', - typeColor: makeHighContrastColor(visColors[1])(backgroundColor), - attributeColor: 'inherit', - symbolColor: makeHighContrastColor(visColors[9])(backgroundColor), - paramsColor: 'inherit', - metaColor: makeHighContrastColor(euiTheme.colors.subduedText)( - backgroundColor - ), - titleColor: makeHighContrastColor(visColors[7])(backgroundColor), - sectionColor: makeHighContrastColor(visColors[9])(backgroundColor), - additionColor: makeHighContrastColor(visColors[0])(backgroundColor), - deletionColor: makeHighContrastColor(euiTheme.colors.danger)( - backgroundColor - ), - selectorClassColor: 'inherit', - selectorIdColor: 'inherit', - }; -}; +// These variables are computationally expensive, so it needs +// to be a hook in order to memoize it per theme +export const useEuiCodeSyntaxVariables = () => { + const { euiTheme } = useEuiTheme(); + + return useMemo(() => { + const backgroundColor = euiTheme.colors.lightestShade; + + return { + backgroundColor: backgroundColor, + color: makeHighContrastColor(euiTheme.colors.text)(backgroundColor), + inlineCodeColor: makeHighContrastColor(visColors[3])(backgroundColor), + selectedBackgroundColor: 'inherit', + commentColor: makeHighContrastColor(euiTheme.colors.subduedText)( + backgroundColor + ), + selectorTagColor: 'inherit', + stringColor: makeHighContrastColor(visColors[2])(backgroundColor), + tagColor: makeHighContrastColor(visColors[1])(backgroundColor), + nameColor: makeHighContrastColor(visColors[1])(backgroundColor), + numberColor: makeHighContrastColor(visColors[0])(backgroundColor), + keywordColor: makeHighContrastColor(visColors[3])(backgroundColor), + functionTitleColor: 'inherit', + typeColor: makeHighContrastColor(visColors[1])(backgroundColor), + attributeColor: 'inherit', + symbolColor: makeHighContrastColor(visColors[9])(backgroundColor), + paramsColor: 'inherit', + metaColor: makeHighContrastColor(euiTheme.colors.subduedText)( + backgroundColor + ), + titleColor: makeHighContrastColor(visColors[7])(backgroundColor), + sectionColor: makeHighContrastColor(visColors[9])(backgroundColor), + additionColor: makeHighContrastColor(visColors[0])(backgroundColor), + deletionColor: makeHighContrastColor(euiTheme.colors.danger)( + backgroundColor + ), + selectorClassColor: 'inherit', + selectorIdColor: 'inherit', + + get tokensCss() { + return ` + .token.punctuation:not(.interpolation-punctuation):not([class*='attr-']) { + opacity: .7; + } + + .token.comment, + .token.prolog, + .token.doctype, + .token.cdata, + .token.coord, + .token.blockquote { + color: ${this.commentColor}; + font-style: italic; + } + + .token.selector { + color: ${this.selectorTagColor}; + } + + .token.string, + .token.interpolation, + .token.interpolation-punctuation, + .token.doc-comment .token.keyword, + .token.attr-value, + .token.url .token.content { + color: ${this.stringColor}; + } + + .token.number, + .token.boolean, + .token.keyword.nil, + .token.regex, + .token.variable, + .token.unit, + .token.hexcode, + .token.attr-name, + .token.attr-equals { + color: ${this.numberColor}; + } + + .token.atrule .token.rule, + .token.keyword { + color: ${this.keywordColor}; + } + + .token.function { + color: ${this.functionTitleColor}; + } + + .token.tag { + color: ${this.tagColor}; + } + + .token.class-name { + color: ${this.typeColor}; + } + + .token.property { + color: ${this.attributeColor}; + } + + .token.console, + .token.list-punctuation, + .token.url-reference, + .token.url .token.url { + color: ${this.symbolColor}; + } -export const euiCodeSyntaxTokens = (euiThemeContext: UseEuiTheme) => { - const { euiTheme } = euiThemeContext; - const euiCodeBlock = euiCodeSyntaxColors(euiThemeContext); - - return ` - .token.punctuation:not(.interpolation-punctuation):not([class*='attr-']) { - opacity: .7; - } - - .token.comment, - .token.prolog, - .token.doctype, - .token.cdata, - .token.coord, - .token.blockquote { - color: ${euiCodeBlock.commentColor}; - font-style: italic; - } - - .token.selector { - color: ${euiCodeBlock.selectorTagColor}; - } - - .token.string, - .token.interpolation, - .token.interpolation-punctuation, - .token.doc-comment .token.keyword, - .token.attr-value, - .token.url .token.content { - color: ${euiCodeBlock.stringColor}; - } - - .token.number, - .token.boolean, - .token.keyword.nil, - .token.regex, - .token.variable, - .token.unit, - .token.hexcode, - .token.attr-name, - .token.attr-equals { - color: ${euiCodeBlock.numberColor}; - } - - .token.atrule .token.rule, - .token.keyword { - color: ${euiCodeBlock.keywordColor}; - } - - .token.function { - color: ${euiCodeBlock.functionTitleColor}; - } - - .token.tag { - color: ${euiCodeBlock.tagColor}; - } - - .token.class-name { - color: ${euiCodeBlock.typeColor}; - } - - .token.property { - color: ${euiCodeBlock.attributeColor}; - } - - .token.console, - .token.list-punctuation, - .token.url-reference, - .token.url .token.url { - color: ${euiCodeBlock.symbolColor}; - } - - .token.paramater { - color: ${euiCodeBlock.paramsColor}; - } - - .token.meta, - .token.important { - color: ${euiCodeBlock.metaColor}; - } - - .token.title { - color: ${euiCodeBlock.titleColor}; - } - - .token.section { - color: ${euiCodeBlock.sectionColor}; - } - - .token.prefix.inserted, - .token.prefix.deleted { - padding-inline-start: -${euiTheme.size.xs}; - margin-inline-start: -${euiTheme.size.xs}; - } - - .token.prefix.inserted { - box-shadow: -${euiTheme.size.xs} 0 ${euiCodeBlock.additionColor}; - color: ${euiCodeBlock.additionColor}; - } - - .token.prefix.deleted { - box-shadow: -${euiTheme.size.xs} 0 ${euiCodeBlock.deletionColor}; - color: ${euiCodeBlock.deletionColor}; - } - - .token.selector .token.class { - color: ${euiCodeBlock.selectorClassColor}; - } - - .token.selector .token.id { - color: ${euiCodeBlock.selectorIdColor}; - } - - .token.italic { - font-style: italic; - } - - .token.important, - .token.bold { - font-weight: ${euiTheme.font.weight.bold}; - } - - .token.url-reference, - .token.url .token.url { - text-decoration: underline; - } - - .token.entity { - cursor: help; - } - `; + .token.paramater { + color: ${this.paramsColor}; + } + + .token.meta, + .token.important { + color: ${this.metaColor}; + } + + .token.title { + color: ${this.titleColor}; + } + + .token.section { + color: ${this.sectionColor}; + } + + .token.prefix.inserted, + .token.prefix.deleted { + padding-inline-start: -${euiTheme.size.xs}; + margin-inline-start: -${euiTheme.size.xs}; + } + + .token.prefix.inserted { + box-shadow: -${euiTheme.size.xs} 0 ${this.additionColor}; + color: ${this.additionColor}; + } + + .token.prefix.deleted { + box-shadow: -${euiTheme.size.xs} 0 ${this.deletionColor}; + color: ${this.deletionColor}; + } + + .token.selector .token.class { + color: ${this.selectorClassColor}; + } + + .token.selector .token.id { + color: ${this.selectorIdColor}; + } + + .token.italic { + font-style: italic; + } + + .token.important, + .token.bold { + font-weight: ${euiTheme.font.weight.bold}; + } + + .token.url-reference, + .token.url .token.url { + text-decoration: underline; + } + + .token.entity { + cursor: help; + }`; + }, + }; + }, [euiTheme]); }; + +export type UseEuiCodeSyntaxVariables = ReturnType< + typeof useEuiCodeSyntaxVariables +>;