diff --git a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx index 0c31225248be6..645b428affe18 100644 --- a/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx +++ b/apps/site/components/Downloads/Release/ReleaseCodeBox.tsx @@ -16,6 +16,7 @@ import { ReleaseContext, ReleasesContext, } from '#site/providers/releaseProvider'; +import type { DownloadSnippet } from '#site/types/download'; import type { ReleaseContextType } from '#site/types/release'; import { INSTALL_METHODS } from '#site/util/download'; @@ -26,80 +27,109 @@ import { INSTALL_METHODS } from '#site/util/download'; // by Shiki to render the highlighted syntax. Hence XSS attacks or JavaScript injections are not possible. const interpreter = createSval({}, 'script'); -const parseSnippet = (s: string, releaseContext: ReleaseContextType) => { - // Adds the release context to the interpreter context +/** + * Parses a snippet string using the interpreter with the given release context + */ +const parseSnippet = (snippet: string, releaseContext: ReleaseContextType) => { interpreter.import({ props: releaseContext }); - - // Evaluates the JavaScript code applying the release context to the code - interpreter.run(`exports.content = \`${s}\``); - - // Sets the parsed raw string to be used by the JSX CodeBox - return String(interpreter.exports.content); + interpreter.run(`exports.content = \`${snippet}\``); + return interpreter.exports.content; }; -const ReleaseCodeBox: FC = () => { - const { snippets } = useContext(ReleasesContext); +/** + * Custom hook to handle snippet processing logic + */ +const useSnippetProcessor = ( + snippets: Array, + context: ReleaseContextType +) => { + return useMemo(() => { + // Find relevant snippets + const installMethodSnippet = snippets.find( + ({ name }) => name === context.installMethod.toLowerCase() + ); - const { installMethod, os, packageManager, release } = - useContext(ReleaseContext); + const packageManagerSnippet = snippets.find( + ({ name }) => name === context.packageManager.toLowerCase() + ); - const t = useTranslations(); + // Only process if both snippets are available + if (!installMethodSnippet || !packageManagerSnippet) { + return ''; + } - // Retrieves the current platform (Dropdown Item) based on the selected platform value - const currentPlatform = useMemo( - () => INSTALL_METHODS.find(({ value }) => value === installMethod), - [installMethod] - ); + const verifyNodeSnippet = snippets.find(({ name }) => name === 'node'); + + const installCorepackSnippet = + context.packageManager !== 'NPM' && + // Corepack is no longer distributed with Node.js v25 + context.release.major >= 25 && + snippets.find(({ name }) => name === 'corepack'); + + // Combine and parse snippets + const parsedContent = parseSnippet( + [ + installMethodSnippet, + verifyNodeSnippet, + installCorepackSnippet, + packageManagerSnippet, + ] + .filter(Boolean) + .map(snippet => (snippet as DownloadSnippet).content) + .join('\n'), + context + ); - // 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 installMethodSnippet = snippets.find( - ({ name }) => name === installMethod.toLowerCase() + // Convert to HTML using Shiki's highlighter + // This is faster than JSX rendering as it avoids React runtime overhead + return highlightToHtml( + parsedContent, + context.os === 'WIN' ? 'ps1' : 'bash' ); + }, [snippets, context]); +}; - // Retrieves a snippet for the given Package Manager to be bundled with the Platform snippet - const packageManagerSnippet = snippets.find( - ({ name }) => name === packageManager.toLowerCase() +/** + * Custom hook to get current platform information + */ +const usePlatformInfo = (installMethod: string) => { + return useMemo(() => { + const platform = INSTALL_METHODS.find( + ({ value }) => value === installMethod ); - // 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'); - } + // Provide defaults for destructuring + return { + label: platform?.label || '', + url: platform?.url || '', + info: platform?.info || 'layouts.download.codeBox.platformInfo.default', + recommended: platform?.recommended || false, + exists: !!platform, + }; + }, [installMethod]); +}; - 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]); +/** + * ReleaseCodeBox component displays installation instructions based on platform and context + */ +const ReleaseCodeBox: FC = () => { + const { snippets } = useContext(ReleasesContext); + const context = useContext(ReleaseContext); + const t = useTranslations(); - // Determines the code language based on the OS - const displayName = os === 'WIN' ? 'PowerShell' : 'Bash'; + // Process platform information + const platformInfo = usePlatformInfo(context.installMethod); - // Determines if the code box should render the skeleton loader - const renderSkeleton = os === 'LOADING' || installMethod === ''; + // Process snippets + const parsedSnippets = useSnippetProcessor(snippets, context); - // Defines fallbacks for the currentPlatform object - const { - label = '', - url = '', - info = 'layouts.download.codeBox.platformInfo.default', - } = currentPlatform ?? {}; + // UI state calculations + const displayLanguage = context.os === 'WIN' ? 'PowerShell' : 'Bash'; + const isLoading = context.os === 'LOADING' || context.installMethod === ''; return (
+ {/* NoScript warning */} - + {/* Release status alert */} + - {!currentPlatform || currentPlatform.recommended || ( + {/* Community platform notice */} + {platformInfo.exists && !platformInfo.recommended && ( { )} - - + {/* Code display with skeleton loading */} + + + {/* Platform info footer */} - - {t(info, { platform: label })}{' '} + + {t(platformInfo.info, { platform: platformInfo.label })}{' '} {t.rich('layouts.download.codeBox.externalSupportInfo', { - platform: label, + platform: platformInfo.label, link: text => ( - + {text} ), diff --git a/apps/site/snippets/en/download/brew.bash b/apps/site/snippets/en/download/brew.bash index 5e9920102fa4f..96c16ac6bd083 100644 --- a/apps/site/snippets/en/download/brew.bash +++ b/apps/site/snippets/en/download/brew.bash @@ -3,6 +3,3 @@ curl -o- https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | ba # Download and install Node.js: brew install node@${props.release.major} - -# Verify the Node.js version: -node -v # Should print "${props.release.versionWithPrefix}". diff --git a/apps/site/snippets/en/download/choco.bash b/apps/site/snippets/en/download/choco.bash index 8a79968b2ed64..95c3d5750a7b6 100644 --- a/apps/site/snippets/en/download/choco.bash +++ b/apps/site/snippets/en/download/choco.bash @@ -3,6 +3,3 @@ powershell -c "irm https://community.chocolatey.org/install.ps1|iex" # Download and install Node.js: choco install nodejs --version="${props.release.version}" - -# Verify the Node.js version: -node -v # Should print "${props.release.versionWithPrefix}". diff --git a/apps/site/snippets/en/download/corepack.bash b/apps/site/snippets/en/download/corepack.bash new file mode 100644 index 0000000000000..5dfe35d3cd17a --- /dev/null +++ b/apps/site/snippets/en/download/corepack.bash @@ -0,0 +1,2 @@ +# Install Corepack: +npm install -g corepack diff --git a/apps/site/snippets/en/download/devbox.bash b/apps/site/snippets/en/download/devbox.bash index 791e40fb4c71c..d536761f0fc8e 100644 --- a/apps/site/snippets/en/download/devbox.bash +++ b/apps/site/snippets/en/download/devbox.bash @@ -9,6 +9,3 @@ devbox add node@${props.release.major} # Open a Devbox shell devbox shell - -# Verify the Node.js version: -node -v # Should print "${props.release.versionWithPrefix}". diff --git a/apps/site/snippets/en/download/docker.bash b/apps/site/snippets/en/download/docker.bash index 9be099e1d7373..bee8df5c03ae1 100644 --- a/apps/site/snippets/en/download/docker.bash +++ b/apps/site/snippets/en/download/docker.bash @@ -6,6 +6,3 @@ docker pull node:${props.release.major}-${props.release.major >= 4 ? 'alpine' : # Create a Node.js container and start a Shell session: docker run -it --rm --entrypoint sh node:${props.release.major}-${props.release.major >= 4 ? 'alpine' : 'slim'} - -# Verify the Node.js version: -node -v # Should print "${props.release.versionWithPrefix}". diff --git a/apps/site/snippets/en/download/fnm.bash b/apps/site/snippets/en/download/fnm.bash index 5692c0b633f25..063ca3e6aec66 100644 --- a/apps/site/snippets/en/download/fnm.bash +++ b/apps/site/snippets/en/download/fnm.bash @@ -6,6 +6,3 @@ ${props.os === 'WIN' ? # Download and install Node.js: fnm install ${props.release.major} - -# Verify the Node.js version: -node -v # Should print "${props.release.versionWithPrefix}". diff --git a/apps/site/snippets/en/download/n.bash b/apps/site/snippets/en/download/n.bash index 4ced5d55f7bcd..6de7abe3d79bb 100644 --- a/apps/site/snippets/en/download/n.bash +++ b/apps/site/snippets/en/download/n.bash @@ -3,6 +3,3 @@ curl -fsSL https://raw.githubusercontent.com/mklement0/n-install/stable/bin/n-in # Node.js already installs during n-install, but you can also install it manually: # n install ${props.release.major} - -# Verify the Node.js version: -node -v # Should print "${props.release.versionWithPrefix}". diff --git a/apps/site/snippets/en/download/node.bash b/apps/site/snippets/en/download/node.bash new file mode 100644 index 0000000000000..9a8e88e27bb54 --- /dev/null +++ b/apps/site/snippets/en/download/node.bash @@ -0,0 +1,2 @@ +# Verify the Node.js version: +node -v # Should print "${props.release.versionWithPrefix}". diff --git a/apps/site/snippets/en/download/volta.bash b/apps/site/snippets/en/download/volta.bash index b1978fe00a511..fa3427b0c28fe 100644 --- a/apps/site/snippets/en/download/volta.bash +++ b/apps/site/snippets/en/download/volta.bash @@ -6,6 +6,3 @@ ${props.os === 'WIN' ? # Download and install Node.js: volta install node@${props.release.major} - -# Verify the Node.js version: -node -v # Should print "${props.release.versionWithPrefix}".