1
1
import type { CodeOptionsMeta , CodeOptionsThemes , CodeToHastOptions , HighlighterGeneric , TransformerOptions } from 'shiki/core'
2
2
import type { Element , Root } from 'hast'
3
3
import type { BuiltinTheme } from 'shiki'
4
- import type { Plugin } from 'unified'
4
+ import type { Transformer } from 'unified'
5
5
import { toString } from 'hast-util-to-string'
6
6
import { visit } from 'unist-util-visit'
7
7
@@ -18,6 +18,16 @@ export interface RehypeShikiExtraOptions {
18
18
*/
19
19
addLanguageClass ?: boolean
20
20
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
+
21
31
/**
22
32
* Custom meta string parser
23
33
* Return an object to merge with `meta`
@@ -57,19 +67,23 @@ declare module 'hast' {
57
67
}
58
68
}
59
69
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 ( )
64
77
const {
65
78
addLanguageClass = false ,
66
79
parseMetaString,
67
80
cache,
81
+ defaultLanguage,
82
+ fallbackLanguage,
83
+ onError,
68
84
...rest
69
85
} = options
70
86
71
- const prefix = 'language-'
72
-
73
87
return function ( tree ) {
74
88
visit ( tree , 'element' , ( node , index , parent ) => {
75
89
if ( ! parent || index == null || node . tagName !== 'pre' )
@@ -87,19 +101,21 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
87
101
}
88
102
89
103
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
90
109
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
97
111
98
- if ( typeof language !== 'string' )
112
+ if ( ! lang )
99
113
return
100
114
101
- const code = toString ( head as any )
115
+ if ( fallbackLanguage && ! langs . includes ( lang ) )
116
+ lang = fallbackLanguage
102
117
118
+ const code = toString ( head )
103
119
const cachedValue = cache ?. get ( code )
104
120
105
121
if ( cachedValue ) {
@@ -112,7 +128,7 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
112
128
113
129
const codeOptions : CodeToHastOptions = {
114
130
...rest ,
115
- lang : language . slice ( prefix . length ) ,
131
+ lang,
116
132
meta : {
117
133
...rest . meta ,
118
134
...meta ,
@@ -125,7 +141,7 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
125
141
codeOptions . transformers . push ( {
126
142
name : 'rehype-shiki:code-language-class' ,
127
143
code ( node ) {
128
- this . addClassToHast ( node , language )
144
+ this . addClassToHast ( node , ` ${ languagePrefix } ${ lang } ` )
129
145
return node
130
146
} ,
131
147
} )
@@ -137,8 +153,8 @@ const rehypeShikiFromHighlighter: Plugin<[HighlighterGeneric<any, any>, RehypeSh
137
153
parent . children . splice ( index , 1 , ...fragment . children )
138
154
}
139
155
catch ( error ) {
140
- if ( options . onError )
141
- options . onError ( error )
156
+ if ( onError )
157
+ onError ( error )
142
158
else
143
159
throw error
144
160
}
0 commit comments