|
9 | 9 | import { customAlphabet } from 'nanoid' |
10 | 10 | import { createRequire } from 'node:module' |
11 | 11 | import c from 'picocolors' |
12 | | -import type { ShikiTransformer } from 'shiki' |
| 12 | +import type { LanguageRegistration, ShikiTransformer } from 'shiki' |
13 | 13 | import { createHighlighter, isSpecialLang } from 'shiki' |
14 | 14 | import { createSyncFn } from 'synckit' |
15 | 15 | import type { Logger } from 'vite' |
@@ -61,22 +61,44 @@ export async function highlight( |
61 | 61 | logger: Pick<Logger, 'warn'> = console |
62 | 62 | ): Promise<[(str: string, lang: string, attrs: string) => string, () => void]> { |
63 | 63 | const { |
64 | | - defaultHighlightLang: defaultLang = '', |
| 64 | + defaultHighlightLang: defaultLang = 'txt', |
65 | 65 | codeTransformers: userTransformers = [] |
66 | 66 | } = options |
67 | 67 |
|
| 68 | + const usingTwoslash = userTransformers.some( |
| 69 | + ({ name }) => name === '@shikijs/vitepress-twoslash' |
| 70 | + ) |
| 71 | + |
68 | 72 | const highlighter = await createHighlighter({ |
69 | 73 | themes: |
70 | 74 | typeof theme === 'object' && 'light' in theme && 'dark' in theme |
71 | 75 | ? [theme.light, theme.dark] |
72 | 76 | : [theme], |
73 | 77 | langs: [ |
74 | 78 | ...(options.languages || []), |
75 | | - ...Object.values(options.languageAlias || {}) |
| 79 | + ...Object.values(options.languageAlias || {}), |
| 80 | + |
| 81 | + // patch for twoslash - https://github.com/vuejs/vitepress/issues/4334 |
| 82 | + ...(usingTwoslash |
| 83 | + ? Object.keys((await import('shiki')).bundledLanguages) |
| 84 | + : []) |
76 | 85 | ], |
77 | 86 | langAlias: options.languageAlias |
78 | 87 | }) |
79 | 88 |
|
| 89 | + function loadLanguage(name: string | LanguageRegistration) { |
| 90 | + const lang = typeof name === 'string' ? name : name.name |
| 91 | + if ( |
| 92 | + !isSpecialLang(lang) && |
| 93 | + !highlighter.getLoadedLanguages().includes(lang) |
| 94 | + ) { |
| 95 | + const resolvedLang = resolveLangSync(lang) |
| 96 | + if (resolvedLang.length) highlighter.loadLanguageSync(resolvedLang) |
| 97 | + else return false |
| 98 | + } |
| 99 | + return true |
| 100 | + } |
| 101 | + |
80 | 102 | await options?.shikiSetup?.(highlighter) |
81 | 103 |
|
82 | 104 | const transformers: ShikiTransformer[] = [ |
@@ -116,23 +138,13 @@ export async function highlight( |
116 | 138 | .replace(vueRE, '') |
117 | 139 | .toLowerCase() || defaultLang |
118 | 140 |
|
119 | | - if (lang) { |
120 | | - const langLoaded = highlighter.getLoadedLanguages().includes(lang) |
121 | | - if (!langLoaded && !isSpecialLang(lang)) { |
122 | | - const resolvedLang = resolveLangSync(lang) |
123 | | - if (!resolvedLang.length) { |
124 | | - logger.warn( |
125 | | - c.yellow( |
126 | | - `\nThe language '${lang}' is not loaded, falling back to '${ |
127 | | - defaultLang || 'txt' |
128 | | - }' for syntax highlighting.` |
129 | | - ) |
130 | | - ) |
131 | | - lang = defaultLang |
132 | | - } else { |
133 | | - highlighter.loadLanguageSync(resolvedLang) |
134 | | - } |
135 | | - } |
| 141 | + if (!loadLanguage(lang)) { |
| 142 | + logger.warn( |
| 143 | + c.yellow( |
| 144 | + `\nThe language '${lang}' is not loaded, falling back to '${defaultLang}' for syntax highlighting.` |
| 145 | + ) |
| 146 | + ) |
| 147 | + lang = defaultLang |
136 | 148 | } |
137 | 149 |
|
138 | 150 | const lineOptions = attrsToLines(attrs) |
|
0 commit comments