1- import { createHighlighterCoreSync } from '@shikijs/core' ;
1+ import { createHighlighterCoreSync , isSpecialLang } from '@shikijs/core' ;
22import shikiNordTheme from 'shiki/themes/nord.mjs' ;
33
44const DEFAULT_THEME = {
@@ -9,6 +9,8 @@ const DEFAULT_THEME = {
99 ...shikiNordTheme ,
1010} ;
1111
12+ const FALLBACK_LANGUAGE = 'text' ;
13+
1214/**
1315 * @template {{ name: string; aliases?: string[] }} T
1416 * @param {string } language
@@ -17,7 +19,6 @@ const DEFAULT_THEME = {
1719 */
1820export const getLanguageByName = ( language , langs ) => {
1921 const normalized = language . toLowerCase ( ) ;
20-
2122 return langs . find (
2223 ( { name, aliases } ) =>
2324 name . toLowerCase ( ) === normalized || aliases ?. includes ( normalized )
@@ -27,6 +28,7 @@ export const getLanguageByName = (language, langs) => {
2728/**
2829 * @typedef {Object } SyntaxHighlighter
2930 * @property {import('@shikijs/core').HighlighterCore } shiki - The underlying shiki core instance.
31+ * @property {(languageId?: string) => string } resolveLanguage - Resolves a language id to a loaded language, falling back to plain text.
3032 * @property {(code: string, lang: string, meta?: Record<string, any>) => string } highlightToHtml - Highlights code and returns inner HTML of the <code> tag.
3133 * @property {(code: string, lang: string, meta?: Record<string, any>) => any } highlightToHast - Highlights code and returns a HAST tree.
3234 */
@@ -44,11 +46,34 @@ const createHighlighter = ({ coreOptions = {}, highlighterOptions = {} }) => {
4446 themes : [ DEFAULT_THEME ] ,
4547 ...coreOptions ,
4648 } ;
47-
4849 const shiki = createHighlighterCoreSync ( options ) ;
49-
5050 const theme = options . themes [ 0 ] ;
5151
52+ const loadedLanguages = new Set (
53+ shiki . getLoadedLanguages ( ) . map ( lang => lang . toLowerCase ( ) )
54+ ) ;
55+
56+ /**
57+ * Resolves a language id to one this highlighter can handle.
58+ * Falls back to plain text for unknown/unloaded languages so
59+ * highlighting never throws on unrecognized code fences.
60+ *
61+ * @param {string } [languageId]
62+ * @returns {string }
63+ */
64+ const resolveLanguage = languageId => {
65+ const normalized = languageId ?. toLowerCase ( ) ;
66+
67+ if (
68+ normalized &&
69+ ( isSpecialLang ( normalized ) || loadedLanguages . has ( normalized ) )
70+ ) {
71+ return languageId ;
72+ }
73+
74+ return FALLBACK_LANGUAGE ;
75+ } ;
76+
5277 /**
5378 * Highlights code and returns the inner HTML inside the <code> tag
5479 *
@@ -59,7 +84,12 @@ const createHighlighter = ({ coreOptions = {}, highlighterOptions = {} }) => {
5984 */
6085 const highlightToHtml = ( code , lang , meta = { } ) =>
6186 shiki
62- . codeToHtml ( code , { lang, theme, meta, ...highlighterOptions } )
87+ . codeToHtml ( code , {
88+ lang : resolveLanguage ( lang ) ,
89+ theme,
90+ meta,
91+ ...highlighterOptions ,
92+ } )
6393 // Shiki will always return the Highlighted code encapsulated in a <pre> and <code> tag
6494 // since our own CodeBox component handles the <code> tag, we just want to extract
6595 // the inner highlighted code to the CodeBox
@@ -73,10 +103,16 @@ const createHighlighter = ({ coreOptions = {}, highlighterOptions = {} }) => {
73103 * @param {Record<string, any> } meta - Metadata
74104 */
75105 const highlightToHast = ( code , lang , meta = { } ) =>
76- shiki . codeToHast ( code , { lang, theme, meta, ...highlighterOptions } ) ;
106+ shiki . codeToHast ( code , {
107+ lang : resolveLanguage ( lang ) ,
108+ theme,
109+ meta,
110+ ...highlighterOptions ,
111+ } ) ;
77112
78113 return {
79114 shiki,
115+ resolveLanguage,
80116 highlightToHtml,
81117 highlightToHast,
82118 } ;
0 commit comments