11import type { CodeOptionsMeta , CodeOptionsThemes , CodeToHastOptions , HighlighterGeneric , TransformerOptions } from 'shiki/core'
22import type { Element , Root } from 'hast'
33import type { BuiltinTheme } from 'shiki'
4- import type { Plugin } from 'unified'
4+ import type { Transformer } from 'unified'
55import { toString } from 'hast-util-to-string'
66import { visit } from 'unist-util-visit'
77
@@ -18,6 +18,16 @@ export interface RehypeShikiExtraOptions {
1818 */
1919 addLanguageClass ?: boolean
2020
21+ /**
22+ * The default language to use when is not specified
23+ */
24+ defaultLanguage ?: string
25+
26+ /**
27+ * The fallback language to use when specified language is not loaded
28+ */
29+ fallbackLanguage ?: string
30+
2131 /**
2232 * Custom meta string parser
2333 * Return an object to merge with `meta`
@@ -57,19 +67,23 @@ declare module 'hast' {
5767 }
5868}
5969
60- const rehypeShikiFromHighlighter : Plugin < [ HighlighterGeneric < any , any > , RehypeShikiCoreOptions ] , Root > = function (
61- highlighter ,
62- options ,
63- ) {
70+ const languagePrefix = 'language-'
71+
72+ function rehypeShikiFromHighlighter (
73+ highlighter : HighlighterGeneric < any , any > ,
74+ options : RehypeShikiCoreOptions ,
75+ ) : Transformer < Root , Root > {
76+ const langs = highlighter . getLoadedLanguages ( )
6477 const {
6578 addLanguageClass = false ,
6679 parseMetaString,
6780 cache,
81+ defaultLanguage,
82+ fallbackLanguage,
83+ onError,
6884 ...rest
6985 } = options
7086
71- const prefix = 'language-'
72-
7387 return function ( tree ) {
7488 visit ( tree , 'element' , ( node , index , parent ) => {
7589 if ( ! parent || index == null || node . tagName !== 'pre' )
@@ -87,19 +101,21 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
87101 }
88102
89103 const classes = head . properties . className
104+ const languageClass = Array . isArray ( classes )
105+ ? classes . find (
106+ d => typeof d === 'string' && d . startsWith ( languagePrefix ) ,
107+ )
108+ : undefined
90109
91- if ( ! Array . isArray ( classes ) )
92- return
93-
94- const language = classes . find (
95- d => typeof d === 'string' && d . startsWith ( prefix ) ,
96- )
110+ let lang = typeof languageClass === 'string' ? languageClass . slice ( languagePrefix . length ) : defaultLanguage
97111
98- if ( typeof language !== 'string' )
112+ if ( ! lang )
99113 return
100114
101- const code = toString ( head as any )
115+ if ( fallbackLanguage && ! langs . includes ( lang ) )
116+ lang = fallbackLanguage
102117
118+ const code = toString ( head )
103119 const cachedValue = cache ?. get ( code )
104120
105121 if ( cachedValue ) {
@@ -112,7 +128,7 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
112128
113129 const codeOptions : CodeToHastOptions = {
114130 ...rest ,
115- lang : language . slice ( prefix . length ) ,
131+ lang,
116132 meta : {
117133 ...rest . meta ,
118134 ...meta ,
@@ -125,7 +141,7 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
125141 codeOptions . transformers . push ( {
126142 name : 'rehype-shiki:code-language-class' ,
127143 code ( node ) {
128- this . addClassToHast ( node , language )
144+ this . addClassToHast ( node , ` ${ languagePrefix } ${ lang } ` )
129145 return node
130146 } ,
131147 } )
@@ -137,8 +153,8 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
137153 parent . children . splice ( index , 1 , ...fragment . children )
138154 }
139155 catch ( error ) {
140- if ( options . onError )
141- options . onError ( error )
156+ if ( onError )
157+ onError ( error )
142158 else
143159 throw error
144160 }
0 commit comments