1
- import {
2
- createTransformerFactory ,
3
- rendererRich ,
4
- transformerTwoslash ,
5
- type TransformerTwoslashOptions ,
6
- } from '@shikijs/twoslash' ;
1
+ import { transformerTwoslash } from '@shikijs/twoslash' ;
7
2
import type { Element , Root } from 'hast' ;
8
3
import { toString } from 'hast-util-to-string' ;
9
4
import type { MdxJsxFlowElementHast , MdxJsxTextElementHast } from 'mdast-util-mdx-jsx' ;
10
5
import { createHighlighter , type Highlighter } from 'shiki' ;
11
- import { createTwoslashFromCDN } from 'twoslash-cdn' ;
12
- import ts from 'typescript' ;
13
6
import type { Plugin } from 'unified' ;
14
7
import { visit } from 'unist-util-visit' ;
15
8
@@ -27,34 +20,19 @@ import {
27
20
DEFAULT_LANGS ,
28
21
SHIKI_TRANSFORMERS ,
29
22
} from './shiki-constants.js' ;
23
+ import {
24
+ cdnTransformerTwoslash ,
25
+ cdnTwoslash ,
26
+ getTwoslashOptions ,
27
+ parseLineComment ,
28
+ } from './twoslash/config.js' ;
30
29
import { getLanguage } from './utils.js' ;
31
30
32
- const twoslashCompilerOptions = {
33
- target : ts . ScriptTarget . ESNext ,
34
- lib : [ 'ESNext' , 'DOM' , 'esnext' , 'dom' , 'es2020' ] ,
35
- } ;
36
-
37
- const twoslashOptions : TransformerTwoslashOptions = {
38
- onTwoslashError ( err , code , lang ) {
39
- console . error ( JSON . stringify ( { err, code, lang } ) ) ;
40
- } ,
41
- onShikiError ( err , code , lang ) {
42
- console . error ( JSON . stringify ( { err, code, lang } ) ) ;
43
- } ,
44
- renderer : rendererRich ( ) ,
45
- langs : [ 'ts' , 'typescript' , 'js' , 'javascript' , 'tsx' , 'jsx' ] ,
46
- explicitTrigger : / m i n t - t w o s l a s h / ,
47
- twoslashOptions : { compilerOptions : twoslashCompilerOptions } ,
48
- } ;
49
-
50
- const cdnTwoslash = createTwoslashFromCDN ( { compilerOptions : twoslashCompilerOptions } ) ;
51
-
52
- const cdnTransformerTwoslash = createTransformerFactory ( cdnTwoslash . runSync ) ;
53
-
54
31
export type RehypeSyntaxHighlightingOptions = {
55
32
theme ?: ShikiTheme ;
56
33
themes ?: Record < 'light' | 'dark' , ShikiTheme > ;
57
34
codeStyling ?: 'dark' | 'system' ;
35
+ linkMap ?: Map < string , string > ;
58
36
} ;
59
37
60
38
let highlighterPromise : Promise < Highlighter > | null = null ;
@@ -73,7 +51,8 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
73
51
options = { }
74
52
) => {
75
53
return async ( tree ) => {
76
- const asyncNodesToProcess : Promise < void > [ ] = [ ] ;
54
+ const nodesToProcess : Promise < void > [ ] = [ ] ;
55
+
77
56
const themesToLoad : ShikiTheme [ ] = [ ] ;
78
57
if ( options . themes ) {
79
58
themesToLoad . push ( options . themes . dark ) ;
@@ -120,32 +99,35 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
120
99
getLanguage ( child , DEFAULT_LANG_ALIASES ) ??
121
100
DEFAULT_LANG ;
122
101
123
- asyncNodesToProcess . push (
102
+ nodesToProcess . push (
124
103
( async ( ) => {
125
104
await cdnTwoslash . prepareTypes ( toString ( node ) ) ;
126
- if ( ! DEFAULT_LANGS . includes ( lang ) ) {
127
- await highlighter . loadLanguage ( lang ) ;
128
- traverseNode ( node , index , parent , highlighter , lang , options ) ;
129
- } else {
130
- traverseNode ( node , index , parent , highlighter , lang , options ) ;
131
- }
105
+ if ( ! DEFAULT_LANGS . includes ( lang ) ) await highlighter . loadLanguage ( lang ) ;
106
+ traverseNode ( { node, index, parent, highlighter, lang, options } ) ;
132
107
} ) ( )
133
108
) ;
134
109
} ) ;
135
- await Promise . all ( asyncNodesToProcess ) ;
110
+ await Promise . all ( nodesToProcess ) ;
136
111
} ;
137
112
} ;
138
113
139
- const traverseNode = (
140
- node : Element ,
141
- index : number ,
142
- parent : Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast ,
143
- highlighter : Highlighter ,
144
- lang : ShikiLang ,
145
- options : RehypeSyntaxHighlightingOptions
146
- ) => {
114
+ function traverseNode ( {
115
+ node,
116
+ index,
117
+ parent,
118
+ highlighter,
119
+ lang,
120
+ options,
121
+ } : {
122
+ node : Element ;
123
+ index : number ;
124
+ parent : Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast ;
125
+ highlighter : Highlighter ;
126
+ lang : ShikiLang ;
127
+ options : RehypeSyntaxHighlightingOptions ;
128
+ } ) {
147
129
try {
148
- const code = toString ( node ) ;
130
+ let code = toString ( node ) ;
149
131
150
132
const meta = node . data ?. meta ?. split ( ' ' ) ?? [ ] ;
151
133
const twoslashIndex = meta . findIndex ( ( str ) => str . toLowerCase ( ) === 'mint-twoslash' ) ;
@@ -156,6 +138,20 @@ const traverseNode = (
156
138
node . data . meta = meta . join ( ' ' ) . trim ( ) || undefined ;
157
139
}
158
140
141
+ const linkMap = options . linkMap ?? new Map ( ) ;
142
+ const splitCode = code . split ( '\n' ) ;
143
+ for ( const [ i , line ] of splitCode . entries ( ) ) {
144
+ const parsedLineComment = parseLineComment ( line ) ;
145
+ if ( ! parsedLineComment ) continue ;
146
+ const { word, href } = parsedLineComment ;
147
+ linkMap . set ( word , href ) ;
148
+ splitCode . splice ( i , 1 ) ;
149
+ }
150
+
151
+ code = splitCode . join ( '\n' ) ;
152
+
153
+ const twoslashOptions = getTwoslashOptions ( { linkMap } ) ;
154
+
159
155
const hast = highlighter . codeToHast ( code , {
160
156
lang : lang ?? DEFAULT_LANG ,
161
157
meta : shouldUseTwoslash ? { __raw : 'mint-twoslash' } : undefined ,
@@ -195,6 +191,6 @@ const traverseNode = (
195
191
}
196
192
throw err ;
197
193
}
198
- } ;
194
+ }
199
195
200
196
export { UNIQUE_LANGS , DEFAULT_LANG_ALIASES , SHIKI_THEMES , ShikiLang , ShikiTheme } ;
0 commit comments