Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
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
4 changes: 2 additions & 2 deletions .github/workflows/lint-and-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@ jobs:
if: ${{ !cancelled() && github.event_name != 'merge_group' }}
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
with:
files: ./apps/site/lcov.info,./packages/ui-components/lcov.info
files: ./apps/site/lcov.info,./packages/*/lcov.info

- name: Upload test results to Codecov
if: ${{ !cancelled() && github.event_name != 'merge_group' }}
uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0
with:
files: ./apps/site/junit.xml,./packages/ui-components/junit.xml
files: ./apps/site/junit.xml,./packages/*/junit.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { highlightToHtml } from '@node-core/rehype-shiki';
import AlertBox from '@node-core/ui-components/Common/AlertBox';
import Skeleton from '@node-core/ui-components/Common/Skeleton';
import { useTranslations } from 'next-intl';
Expand All @@ -16,7 +17,6 @@ import {
} from '#site/providers/releaseProvider';
import type { ReleaseContextType } from '#site/types/release';
import { INSTALL_METHODS } from '#site/util/downloadUtils';
import { highlightToHtml } from '#site/util/getHighlighter';

// Creates a minimal JavaScript interpreter for parsing the JavaScript code from the snippets
// Note: that the code runs inside a sandboxed environment and cannot interact with any code outside of the sandbox
Expand Down
2 changes: 1 addition & 1 deletion apps/site/components/MDX/CodeBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getLanguageDisplayName } from '@node-core/rehype-shiki';
import type { FC, PropsWithChildren } from 'react';

import CodeBox from '#site/components/Common/CodeBox';
import { getLanguageDisplayName } from '#site/util/getLanguageDisplayName';

type CodeBoxProps = { className?: string; showCopyButton?: string };

Expand Down
3 changes: 1 addition & 2 deletions apps/site/next.mdx.plugins.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
'use strict';

import rehypeShikiji from '@node-core/rehype-shiki';
import remarkHeadings from '@vcarl/remark-headings';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import rehypeSlug from 'rehype-slug';
import remarkGfm from 'remark-gfm';
import readingTime from 'remark-reading-time';

import rehypeShikiji from './next.mdx.shiki.mjs';

/**
* Provides all our Rehype Plugins that are used within MDX
*/
Expand Down
6 changes: 1 addition & 5 deletions apps/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dependencies": {
"@heroicons/react": "~2.2.0",
"@mdx-js/mdx": "^3.1.0",
"@node-core/rehype-shiki": "workspace:*",
"@node-core/ui-components": "workspace:*",
"@node-core/website-i18n": "workspace:*",
"@nodevu/core": "0.3.0",
Expand All @@ -41,8 +42,6 @@
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.2.4",
"@shikijs/core": "^3.2.2",
"@shikijs/engine-javascript": "^3.2.2",
"@tailwindcss/postcss": "~4.1.5",
"@types/node": "22.15.3",
"@types/react": "^19.1.0",
Expand All @@ -56,7 +55,6 @@
"github-slugger": "~2.0.0",
"glob": "~11.0.1",
"gray-matter": "~4.0.3",
"hast-util-to-string": "~3.0.1",
"next": "15.3.1",
"next-intl": "~4.1.0",
"next-themes": "~0.4.6",
Expand All @@ -71,10 +69,8 @@
"remark-gfm": "~4.0.1",
"remark-reading-time": "~2.0.1",
"semver": "~7.7.1",
"shiki": "~3.3.0",
"sval": "^0.6.3",
"tailwindcss": "~4.0.17",
"unist-util-visit": "~5.0.0",
"vfile": "~6.0.3",
"vfile-matter": "~5.0.1"
},
Expand Down
23 changes: 0 additions & 23 deletions apps/site/util/getHighlighter.ts

This file was deleted.

11 changes: 0 additions & 11 deletions apps/site/util/getLanguageDisplayName.ts

This file was deleted.

17 changes: 17 additions & 0 deletions packages/rehype-shiki/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import baseConfig from '../../eslint.config.js';

export default [
...baseConfig,
{
languageOptions: {
parserOptions: {
// Allow nullish syntax (i.e. "?." or "??")
ecmaVersion: 2020,
},
},
rules: {
// Shiki's export isn't named, it's a re-export
'import-x/named': 'off',
},
},
];
18 changes: 18 additions & 0 deletions packages/rehype-shiki/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@node-core/rehype-shiki",
"type": "module",
"main": "./src/index.mjs",
"module": "./src/index.mjs",
"scripts": {
"lint:js": "eslint \"**/*.mjs\"",
"test": "node --test"
},
"dependencies": {
"@shikijs/core": "^3.3.0",
"@shikijs/engine-javascript": "^3.3.0",
"classnames": "~2.5.1",
"hast-util-to-string": "^3.0.1",
"shiki": "~3.3.0",
"unist-util-visit": "^5.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import assert from 'node:assert/strict';
import { it, describe, mock } from 'node:test';
import { it, describe } from 'node:test';

describe('getLanguageDisplayName', async () => {
mock.module('#site/shiki.config.mjs', {
namedExports: {
LANGUAGES: [
{ name: 'javascript', aliases: ['js'], displayName: 'JavaScript' },
{ name: 'typescript', aliases: ['ts'], displayName: 'TypeScript' },
],
},
});
import { getLanguageDisplayName, LANGUAGES } from '../languages.mjs';

const { getLanguageDisplayName } = await import(
'#site/util/getLanguageDisplayName'
);
LANGUAGES.splice(
Comment thread
avivkeller marked this conversation as resolved.
0,
LANGUAGES.length,
{ name: 'javascript', aliases: ['js'], displayName: 'JavaScript' },
{ name: 'typescript', aliases: ['ts'], displayName: 'TypeScript' }
);

describe('getLanguageDisplayName', async () => {
it('should return the display name for a known language', () => {
assert.equal(getLanguageDisplayName('javascript'), 'JavaScript');
assert.equal(getLanguageDisplayName('js'), 'JavaScript');
Expand Down
47 changes: 47 additions & 0 deletions packages/rehype-shiki/src/highlighter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { createHighlighterCoreSync } from '@shikijs/core';
import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript';

import { LANGUAGES, DEFAULT_THEME } from './languages.mjs';

let _shiki;

/**
* Lazy-load and memoize the minimal Shikiji Syntax Highlighter
* @returns {import('@shikijs/core').HighlighterCore}
*/
export const getShiki = () => {
if (!_shiki) {
_shiki = createHighlighterCoreSync({
themes: [DEFAULT_THEME],
langs: LANGUAGES,
// Let's use Shiki's new Experimental JavaScript-based regex engine!
engine: createJavaScriptRegexEngine(),
});
}
return _shiki;
};

/**
* Highlights code and returns the inner HTML inside the <code> tag
*
* @param {string} code - The code to highlight
* @param {string} language - The programming language to use for highlighting
* @returns {string} The inner HTML of the highlighted code
*/
export const highlightToHtml = (code, language) =>
getShiki()
.codeToHtml(code, { lang: language, theme: DEFAULT_THEME })
// Shiki will always return the Highlighted code encapsulated in a <pre> and <code> tag
// since our own CodeBox component handles the <code> tag, we just want to extract
// the inner highlighted code to the CodeBox
.match(/<code>(.+?)<\/code>/s)[1];
Comment thread
avivkeller marked this conversation as resolved.

/**
* Highlights code and returns a HAST tree
*
* @param {string} code - The code to highlight
* @param {string} language - The programming language to use for highlighting
* @returns {import('hast').Element} The HAST representation of the highlighted code
*/
export const highlightToHast = (code, language) =>
getShiki().codeToHast(code, { lang: language, theme: DEFAULT_THEME });
5 changes: 5 additions & 0 deletions packages/rehype-shiki/src/index.mjs
Comment thread
avivkeller marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { rehypeShikiji } from './plugin.mjs';
export * from './highlighter.mjs';
export * from './languages.mjs';

export default rehypeShikiji;
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,18 @@ export const DEFAULT_THEME = {
colorReplacements: { '#616e88': '#707e99' },
...shikiNordTheme,
};

/**
* Get the display name of a given language
* @param {string} language The language ID
* @returns {string} The display name of the language, or the input as a fallback
*/
export const getLanguageDisplayName = language => {
const languageByIdOrAlias = LANGUAGES.find(
({ name, aliases }) =>
name.toLowerCase() === language.toLowerCase() ||
(aliases !== undefined && aliases.includes(language.toLowerCase()))
);

return languageByIdOrAlias?.displayName ?? language;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import classNames from 'classnames';
import { toString } from 'hast-util-to-string';
import { SKIP, visit } from 'unist-util-visit';

import { highlightToHast } from './util/getHighlighter';
import { highlightToHast } from './highlighter.mjs';

// This is what Remark will use as prefix within a <pre> className
// to attribute the current language of the <pre> element
Expand Down Expand Up @@ -53,7 +53,7 @@ function isCodeBlock(node) {
);
}

export default function rehypeShikiji() {
export function rehypeShikiji() {
return function (tree) {
visit(tree, 'element', (_, index, parent) => {
const languages = [];
Expand Down
12 changes: 12 additions & 0 deletions packages/rehype-shiki/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"lint:js": {
"inputs": ["src/**/*.mjs"]
},
"test": {
"inputs": ["src/**/*.mjs"]
}
}
}
Loading
Loading