From 7dd604c4a74143bbe5c1564b7b552836f9df3646 Mon Sep 17 00:00:00 2001 From: Claudio Wunder Date: Sat, 28 Dec 2024 03:02:01 +0000 Subject: [PATCH] chore: react optimizations and profiling optimizations --- apps/site/components/Common/Select/index.tsx | 57 ++++++++++-------- .../Downloads/Release/ReleaseCodeBox.tsx | 59 +++++++++++-------- apps/site/components/JSX/CodeBox/index.tsx | 38 ------------ apps/site/components/withLayout.tsx | 2 +- apps/site/layouts/GlowingBackdrop.tsx | 2 +- 5 files changed, 69 insertions(+), 89 deletions(-) delete mode 100644 apps/site/components/JSX/CodeBox/index.tsx diff --git a/apps/site/components/Common/Select/index.tsx b/apps/site/components/Common/Select/index.tsx index 1428b39a0f487..b2abbbd1b848e 100644 --- a/apps/site/components/Common/Select/index.tsx +++ b/apps/site/components/Common/Select/index.tsx @@ -86,6 +86,37 @@ const Select = ({ [mappedValues, value] ); + const memoizedMappedValues = useMemo(() => { + return mappedValues.map(({ label, items }, key) => ( + + {label && ( + + {label} + + )} + + {items.map(({ value, label, iconImage, disabled }) => ( + + + {iconImage} + {label} + + + ))} + + )); + // We explicitly want to recalculate these values only when the values themselves changed + // This is to prevent re-rendering and re-calcukating the values on every render + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(values)]); + // Both change the internal state and emit the change event const handleChange = (value: T) => { setValue(value); @@ -141,31 +172,7 @@ const Select = ({ - {mappedValues.map(({ label, items }, key) => ( - - {label && ( - - {label} - - )} - - {items.map(({ value, label, iconImage, disabled }) => ( - - - {iconImage} - {label} - - - ))} - - ))} + {memoizedMappedValues} diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 8174d334c12bf..46701c5b17fa6 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -5,13 +5,14 @@ import type { FC } from 'react'; import { useContext, useMemo } from 'react'; import AlertBox from '@/components/Common/AlertBox'; +import CodeBox from '@/components/Common/CodeBox'; import Skeleton from '@/components/Common/Skeleton'; -import JSXCodeBox from '@/components/JSX/CodeBox'; import Link from '@/components/Link'; import { createSval } from '@/next.jsx.compiler.mjs'; import { ReleaseContext, ReleasesContext } from '@/providers/releaseProvider'; import type { ReleaseContextType } from '@/types/release'; import { INSTALL_METHODS } from '@/util/downloadUtils'; +import { highlightToHtml } from '@/util/getHighlighter'; import LinkWithArrow from './LinkWithArrow'; @@ -35,46 +36,56 @@ const parseSnippet = (s: string, releaseContext: ReleaseContextType) => { const ReleaseCodeBox: FC = () => { const { snippets } = useContext(ReleasesContext); - const { - installMethod: platform, - os, - packageManager, - release, - } = useContext(ReleaseContext); + const { installMethod, os, packageManager, release } = + useContext(ReleaseContext); const t = useTranslations(); // Retrieves the current platform (Dropdown Item) based on the selected platform value const currentPlatform = useMemo( - () => INSTALL_METHODS.find(({ value }) => value === platform), - [platform] + () => INSTALL_METHODS.find(({ value }) => value === installMethod), + [installMethod] ); // Parses the snippets based on the selected platform, package manager, and release context const parsedSnippets = useMemo(() => { // Retrieves a snippet for the given Installation Method (aka Platform) - const platformSnippet = snippets.find( - s => s.name === platform.toLowerCase() + const installMethodSnippet = snippets.find( + ({ name }) => name === installMethod.toLowerCase() ); // Retrieves a snippet for the given Package Manager to be bundled with the Platform snippet const packageManagerSnippet = snippets.find( - s => s.name === packageManager.toLowerCase() + ({ name }) => name === packageManager.toLowerCase() ); - return parseSnippet( - // Bundles the Platform and Package Manager snippets - `${platformSnippet?.content ?? ''}\n${packageManagerSnippet?.content ?? ''}`, - // Passes a partial state of only the things we need to the parser - { release, installMethod: platform, os } as ReleaseContextType - ); - }, [snippets, release, platform, os, packageManager]); + // Prevents numerous recalculations of `sval` and `Shiki` when not necessary + // As we only want to parse the snippets when both the Platform and Package Manager snippets are available + if (installMethodSnippet && packageManagerSnippet) { + const content = parseSnippet( + // Bundles the Platform and Package Manager snippets + `${installMethodSnippet.content}\n${packageManagerSnippet.content}`, + // Passes a partial state of only the things we need to the parser + { release, os } as ReleaseContextType + ); + + // We use Shikis's `hast-util-to-html` to convert the highlighted code into plain HTML (Pretty much using Rehype) + // This is actually faster than using `hast-util-to-jsx-runtime` and then rendering the JSX + // As it requires React's runtime to interpolate and build these components dynamically + // Which also leads to a lot o GC being emitted. (Tested via Profiling) + return highlightToHtml(content, os === 'WIN' ? 'ps1' : 'bash'); + } + + return ''; + // Only change to these specific properties which are relevant for the re-rendering of the CodeBox + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [release.versionWithPrefix, installMethod, os, packageManager]); // Determines the code language based on the OS - const codeLanguage = os === 'WIN' ? 'ps1' : 'bash'; + const displayName = os === 'WIN' ? 'PowerShell' : 'Bash'; // Determines if the code box should render the skeleton loader - const renderSkeleton = os === 'LOADING' || platform === ''; + const renderSkeleton = os === 'LOADING' || installMethod === ''; // Defines fallbacks for the currentPlatform object const { @@ -100,9 +111,9 @@ const ReleaseCodeBox: FC = () => { )} - - {parsedSnippets} - + + + diff --git a/apps/site/components/JSX/CodeBox/index.tsx b/apps/site/components/JSX/CodeBox/index.tsx deleted file mode 100644 index ddc2c8643e1e9..0000000000000 --- a/apps/site/components/JSX/CodeBox/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import classNames from 'classnames'; -import { toJsxRuntime } from 'hast-util-to-jsx-runtime'; -import type { FC, PropsWithChildren } from 'react'; - -import MDXCodeBox from '@/components/MDX/CodeBox'; -import { reactRuntime } from '@/next.mdx.compiler.mjs'; -import { highlightToHast } from '@/util/getHighlighter'; - -type CodeBoxProps = { - language: string; - showCopyButton?: boolean; - className?: string; -}; - -const CodeBox: FC> = ({ - children, - language, - showCopyButton, - className, -}) => { - const highlighted = highlightToHast(String(children), language); - - return toJsxRuntime(highlighted, { - ...reactRuntime, - components: { - pre: ({ children }) => ( - - {children} - - ), - }, - }); -}; - -export default CodeBox; diff --git a/apps/site/components/withLayout.tsx b/apps/site/components/withLayout.tsx index 23eab80e4efd6..0c4a1638ee5b3 100644 --- a/apps/site/components/withLayout.tsx +++ b/apps/site/components/withLayout.tsx @@ -12,7 +12,7 @@ import type { Layouts } from '@/types'; const layouts = { about: AboutLayout, - home: props => , + home: GlowingBackdropLayout, learn: LearnLayout, page: DefaultLayout, 'blog-post': PostLayout, diff --git a/apps/site/layouts/GlowingBackdrop.tsx b/apps/site/layouts/GlowingBackdrop.tsx index 2457baf8e0877..377eac2e18903 100644 --- a/apps/site/layouts/GlowingBackdrop.tsx +++ b/apps/site/layouts/GlowingBackdrop.tsx @@ -12,7 +12,7 @@ type GlowingBackdropLayoutProps = PropsWithChildren<{ }>; const GlowingBackdropLayout: FC = ({ - kind, + kind = 'home', children, }) => ( <>