Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
162 changes: 98 additions & 64 deletions apps/site/components/Downloads/Release/ReleaseCodeBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<DownloadSnippet>,
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 (
<div className="mb-6 mt-4 flex flex-col gap-2">
{/* NoScript warning */}
<noscript>
<AlertBox
title={t('components.common.alertBox.warning')}
Expand All @@ -116,9 +146,11 @@ const ReleaseCodeBox: FC = () => {
</AlertBox>
</noscript>

<WithReleaseAlertBox status={release.status} />
{/* Release status alert */}
<WithReleaseAlertBox status={context.release.status} />

{!currentPlatform || currentPlatform.recommended || (
{/* Community platform notice */}
{platformInfo.exists && !platformInfo.recommended && (
<AlertBox
title={t('components.common.alertBox.info')}
level="info"
Expand All @@ -128,19 +160,21 @@ const ReleaseCodeBox: FC = () => {
</AlertBox>
)}

<Skeleton loading={renderSkeleton}>
<CodeBox language={displayName} className="min-h-[16.5rem]">
{/* Code display with skeleton loading */}
<Skeleton loading={isLoading}>
<CodeBox language={displayLanguage} className="min-h-[16.5rem]">
<code dangerouslySetInnerHTML={{ __html: parsedSnippets }} />
</CodeBox>
</Skeleton>

{/* Platform info footer */}
<span className="text-center text-xs text-neutral-800 dark:text-neutral-200">
<Skeleton loading={renderSkeleton} hide={!currentPlatform}>
{t(info, { platform: label })}{' '}
<Skeleton loading={isLoading} hide={!platformInfo.exists}>
{t(platformInfo.info, { platform: platformInfo.label })}{' '}
{t.rich('layouts.download.codeBox.externalSupportInfo', {
platform: label,
platform: platformInfo.label,
link: text => (
<LinkWithArrow href={url}>
<LinkWithArrow href={platformInfo.url}>
<b>{text}</b>
</LinkWithArrow>
),
Expand Down
3 changes: 0 additions & 3 deletions apps/site/snippets/en/download/brew.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}".
3 changes: 0 additions & 3 deletions apps/site/snippets/en/download/choco.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}".
2 changes: 2 additions & 0 deletions apps/site/snippets/en/download/corepack.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Install Corepack
npm install -g corepack
3 changes: 0 additions & 3 deletions apps/site/snippets/en/download/devbox.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}".
3 changes: 0 additions & 3 deletions apps/site/snippets/en/download/docker.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}".
3 changes: 0 additions & 3 deletions apps/site/snippets/en/download/fnm.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}".
3 changes: 0 additions & 3 deletions apps/site/snippets/en/download/n.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}".
2 changes: 2 additions & 0 deletions apps/site/snippets/en/download/node.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Verify the Node.js version:
node -v # Should print "${props.release.versionWithPrefix}".
3 changes: 0 additions & 3 deletions apps/site/snippets/en/download/volta.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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}".
Loading