11import type { Element , Root } from 'hast' ;
22import { toString } from 'hast-util-to-string' ;
3+ import type { MdxJsxFlowElementHast , MdxJsxTextElementHast } from 'mdast-util-mdx-jsx' ;
34import { createHighlighter , type Highlighter } from 'shiki' ;
45import type { Plugin } from 'unified' ;
56import { visit } from 'unist-util-visit' ;
@@ -15,6 +16,7 @@ import {
1516 DEFAULT_DARK_THEME ,
1617 DEFAULT_LIGHT_THEME ,
1718 DEFAULT_THEMES ,
19+ DEFAULT_LANGS ,
1820} from './shiki-constants.js' ;
1921import {
2022 getLanguage ,
@@ -35,7 +37,7 @@ async function getHighlighter(): Promise<Highlighter> {
3537 if ( ! highlighterPromise ) {
3638 highlighterPromise = createHighlighter ( {
3739 themes : DEFAULT_THEMES ,
38- langs : UNIQUE_LANGS ,
40+ langs : DEFAULT_LANGS ,
3941 } ) ;
4042 }
4143 return highlighterPromise ;
@@ -45,6 +47,7 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
4547 options = { }
4648) => {
4749 return async ( tree ) => {
50+ const asyncNodesToProcess : Promise < void > [ ] = [ ] ;
4851 const themesToLoad : ShikiTheme [ ] = [ ] ;
4952 if ( options . themes ) {
5053 themesToLoad . push ( options . themes . dark ) ;
@@ -91,74 +94,94 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
9194 getLanguage ( child , DEFAULT_LANG_ALIASES ) ??
9295 DEFAULT_LANG ;
9396
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 ) ;
159105 }
160106 } ) ;
107+ await Promise . all ( asyncNodesToProcess ) ;
161108 } ;
162109} ;
163110
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+
164187export { UNIQUE_LANGS , DEFAULT_LANG_ALIASES , SHIKI_THEMES , ShikiLang , ShikiTheme } ;
0 commit comments