1
1
import type { Element , Root } from 'hast' ;
2
2
import { toString } from 'hast-util-to-string' ;
3
+ import type { MdxJsxFlowElementHast , MdxJsxTextElementHast } from 'mdast-util-mdx-jsx' ;
3
4
import { createHighlighter , type Highlighter } from 'shiki' ;
4
5
import type { Plugin } from 'unified' ;
5
6
import { visit } from 'unist-util-visit' ;
@@ -15,6 +16,7 @@ import {
15
16
DEFAULT_DARK_THEME ,
16
17
DEFAULT_LIGHT_THEME ,
17
18
DEFAULT_THEMES ,
19
+ DEFAULT_LANGS ,
18
20
} from './shiki-constants.js' ;
19
21
import {
20
22
getLanguage ,
@@ -35,7 +37,7 @@ async function getHighlighter(): Promise<Highlighter> {
35
37
if ( ! highlighterPromise ) {
36
38
highlighterPromise = createHighlighter ( {
37
39
themes : DEFAULT_THEMES ,
38
- langs : UNIQUE_LANGS ,
40
+ langs : DEFAULT_LANGS ,
39
41
} ) ;
40
42
}
41
43
return highlighterPromise ;
@@ -45,6 +47,7 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
45
47
options = { }
46
48
) => {
47
49
return async ( tree ) => {
50
+ const asyncNodesToProcess : Promise < void > [ ] = [ ] ;
48
51
const themesToLoad : ShikiTheme [ ] = [ ] ;
49
52
if ( options . themes ) {
50
53
themesToLoad . push ( options . themes . dark ) ;
@@ -91,74 +94,94 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
91
94
getLanguage ( child , DEFAULT_LANG_ALIASES ) ??
92
95
DEFAULT_LANG ;
93
96
94
- try {
95
- const code = toString ( node ) ;
96
- const lines = code . split ( '\n' ) ;
97
- let linesToHighlight = getLinesToHighlight ( node , lines . length ) ;
98
-
99
- const hast = highlighter . codeToHast ( code , {
100
- lang : lang ?? DEFAULT_LANG ,
101
- themes : {
102
- light :
103
- options . themes ?. light ??
104
- options . theme ??
105
- ( options . codeStyling === 'dark' ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME ) ,
106
- dark : options . themes ?. dark ?? options . theme ?? DEFAULT_DARK_THEME ,
107
- } ,
108
- colorReplacements : shikiColorReplacements ,
109
- tabindex : false ,
110
- tokenizeMaxLineLength : 1000 ,
111
- } ) ;
112
-
113
- const codeElement = hast . children [ 0 ] as Element ;
114
- if ( ! codeElement ) return ;
115
-
116
- let lineNumber = 0 ;
117
- visit ( codeElement , 'element' , ( span , spanIndex , spanParent ) => {
118
- if (
119
- ! spanParent ||
120
- spanParent . type !== 'element' ||
121
- spanParent . tagName !== 'code' ||
122
- span . tagName !== 'span' ||
123
- ( ! span . children . length && spanIndex === spanParent . children . length - 1 ) ||
124
- ( typeof span . properties . class !== 'string' && ! Array . isArray ( span . properties . class ) ) ||
125
- ! span . properties . class . includes ( 'line' )
126
- ) {
127
- return ;
128
- }
129
-
130
- lineNumber ++ ;
131
- if ( linesToHighlight . includes ( lineNumber ) ) {
132
- if ( typeof span . properties . class === 'string' ) {
133
- span . properties . class += ' ' + LINE_HIGHLIGHT_CLASS ;
134
- } else {
135
- span . properties . class = [ ...span . properties . class , LINE_HIGHLIGHT_CLASS ] ;
136
- }
137
- }
138
- } ) ;
139
-
140
- const preChild = codeElement . children [ 0 ] as Element ;
141
- const numberOfLines = lineNumber ;
142
-
143
- node . data = node . data ?? { } ;
144
- if ( node . data . meta ) {
145
- node . data . meta = node . data . meta . replace ( lineHighlightPattern , '' ) . trim ( ) ;
146
- }
147
- codeElement . data = node . data ;
148
- codeElement . properties . numberOfLines = numberOfLines ;
149
- if ( preChild ) {
150
- preChild . data = node . data ;
151
- preChild . properties . numberOfLines = numberOfLines ;
152
- }
153
- parent . children . splice ( index , 1 , codeElement ) ;
154
- } catch ( err ) {
155
- if ( err instanceof Error && / U n k n o w n l a n g u a g e / . test ( err . message ) ) {
156
- return ;
157
- }
158
- throw err ;
97
+ if ( ! DEFAULT_LANGS . includes ( lang ) ) {
98
+ asyncNodesToProcess . push (
99
+ highlighter . loadLanguage ( lang ) . then ( ( ) => {
100
+ traverseNode ( node , index , parent , highlighter , lang , options ) ;
101
+ } )
102
+ ) ;
103
+ } else {
104
+ traverseNode ( node , index , parent , highlighter , lang , options ) ;
159
105
}
160
106
} ) ;
107
+ await Promise . all ( asyncNodesToProcess ) ;
161
108
} ;
162
109
} ;
163
110
111
+ const traverseNode = (
112
+ node : Element ,
113
+ index : number ,
114
+ parent : Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast ,
115
+ highlighter : Highlighter ,
116
+ lang : ShikiLang ,
117
+ options : RehypeSyntaxHighlightingOptions
118
+ ) => {
119
+ try {
120
+ const code = toString ( node ) ;
121
+ const lines = code . split ( '\n' ) ;
122
+ let linesToHighlight = getLinesToHighlight ( node , lines . length ) ;
123
+
124
+ const hast = highlighter . codeToHast ( code , {
125
+ lang : lang ?? DEFAULT_LANG ,
126
+ themes : {
127
+ light :
128
+ options . themes ?. light ??
129
+ options . theme ??
130
+ ( options . codeStyling === 'dark' ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME ) ,
131
+ dark : options . themes ?. dark ?? options . theme ?? DEFAULT_DARK_THEME ,
132
+ } ,
133
+ colorReplacements : shikiColorReplacements ,
134
+ tabindex : false ,
135
+ tokenizeMaxLineLength : 1000 ,
136
+ } ) ;
137
+
138
+ const codeElement = hast . children [ 0 ] as Element ;
139
+ if ( ! codeElement ) return ;
140
+
141
+ let lineNumber = 0 ;
142
+ visit ( codeElement , 'element' , ( span , spanIndex , spanParent ) => {
143
+ if (
144
+ ! spanParent ||
145
+ spanParent . type !== 'element' ||
146
+ spanParent . tagName !== 'code' ||
147
+ span . tagName !== 'span' ||
148
+ ( ! span . children . length && spanIndex === spanParent . children . length - 1 ) ||
149
+ ( typeof span . properties . class !== 'string' && ! Array . isArray ( span . properties . class ) ) ||
150
+ ! span . properties . class . includes ( 'line' )
151
+ ) {
152
+ return ;
153
+ }
154
+
155
+ lineNumber ++ ;
156
+ if ( linesToHighlight . includes ( lineNumber ) ) {
157
+ if ( typeof span . properties . class === 'string' ) {
158
+ span . properties . class += ' ' + LINE_HIGHLIGHT_CLASS ;
159
+ } else {
160
+ span . properties . class = [ ...span . properties . class , LINE_HIGHLIGHT_CLASS ] ;
161
+ }
162
+ }
163
+ } ) ;
164
+
165
+ const preChild = codeElement . children [ 0 ] as Element ;
166
+ const numberOfLines = lineNumber ;
167
+
168
+ node . data = node . data ?? { } ;
169
+ if ( node . data . meta ) {
170
+ node . data . meta = node . data . meta . replace ( lineHighlightPattern , '' ) . trim ( ) ;
171
+ }
172
+ codeElement . data = node . data ;
173
+ codeElement . properties . numberOfLines = numberOfLines ;
174
+ if ( preChild ) {
175
+ preChild . data = node . data ;
176
+ preChild . properties . numberOfLines = numberOfLines ;
177
+ }
178
+ parent . children . splice ( index , 1 , codeElement ) ;
179
+ } catch ( err ) {
180
+ if ( err instanceof Error && / U n k n o w n l a n g u a g e / . test ( err . message ) ) {
181
+ return ;
182
+ }
183
+ throw err ;
184
+ }
185
+ } ;
186
+
164
187
export { UNIQUE_LANGS , DEFAULT_LANG_ALIASES , SHIKI_THEMES , ShikiLang , ShikiTheme } ;
0 commit comments