返回 slidev
shiki-options.ts
根目录 / packages / client / setup / shiki-options.ts
1 // This module also runs in the Node.js environment
2
3 import type { ResolvedSlidevUtils, ShikiContext, ShikiSetupReturn } from '@slidev/types'
4 import type { LanguageInput, ThemeInput, ThemeRegistrationAny } from 'shiki'
5 import { objectMap } from '@antfu/utils'
6 import { red, yellow } from 'ansis'
7 import { bundledLanguages, bundledThemes } from 'shiki'
8
9 export const shikiContext: ShikiContext = {
10 /** @deprecated */
11 loadTheme() {
12 throw new Error('`loadTheme` is no longer supported.')
13 },
14 }
15
16 export function resolveShikiOptions(options: (ShikiSetupReturn | void)[]) {
17 const mergedOptions: Record<string, any> = Object.assign({}, ...options)
18
19 if ('theme' in mergedOptions && 'themes' in mergedOptions)
20 delete mergedOptions.theme
21
22 // Rename theme to themes when provided in multiple themes format, but exclude when it's a theme object.
23 if (mergedOptions.theme && typeof mergedOptions.theme !== 'string' && !mergedOptions.theme.name && !mergedOptions.theme.tokenColors) {
24 mergedOptions.themes = mergedOptions.theme
25 delete mergedOptions.theme
26 }
27
28 // No theme at all, apply the default
29 if (!mergedOptions.theme && !mergedOptions.themes) {
30 mergedOptions.themes = {
31 dark: 'vitesse-dark',
32 light: 'vitesse-light',
33 }
34 }
35
36 if (mergedOptions.themes)
37 mergedOptions.defaultColor = false
38
39 const themeOption = extractThemeName(mergedOptions.theme) || extractThemeNames(mergedOptions.themes || {})
40 const themeNames = typeof themeOption === 'string' ? [themeOption] : Object.values(themeOption)
41
42 const themeInput: Record<string, ThemeInput> = Object.assign({}, bundledThemes)
43 if (typeof mergedOptions.theme === 'object' && mergedOptions.theme?.name) {
44 themeInput[mergedOptions.theme.name] = mergedOptions.theme
45 }
46 if (mergedOptions.themes) {
47 for (const theme of Object.values<ThemeRegistrationAny | string>(mergedOptions.themes)) {
48 if (typeof theme === 'object' && theme?.name) {
49 themeInput[theme.name] = theme
50 }
51 }
52 }
53
54 const languageNames = new Set<string>(['markdown', 'vue', 'javascript', 'typescript', 'html', 'css'])
55 const languageInput: Record<string, LanguageInput> = Object.assign({}, bundledLanguages)
56 for (const option of options) {
57 const langs = option?.langs
58 if (langs == null)
59 continue
60 if (Array.isArray(langs)) {
61 for (const lang of langs.flat()) {
62 if (typeof lang === 'function') {
63 console.error(red('[slidev] `langs` option returned by setup/shiki.ts cannot be an array containing functions. Please use the record format (`{ [name]: () => {...} }`) instead.'))
64 }
65 else if (typeof lang === 'string') {
66 // a name of a Shiki built-in language
67 // which can be loaded on demand without overhead, so all built-in languages are available.
68 // Only need to include them explicitly in browser environment.
69 languageNames.add(lang)
70 }
71 else if (lang.name) {
72 // a custom grammar object
73 languageNames.add(lang.name)
74 languageInput[lang.name] = lang
75 for (const alias of lang.aliases || []) {
76 languageNames.add(alias)
77 languageInput[alias] = lang
78 }
79 }
80 else {
81 console.error(red('[slidev] Invalid lang option in shiki setup:'), lang)
82 }
83 }
84 }
85 else if (typeof langs === 'object') {
86 // a map from name to loader or grammar object
87 for (const name of Object.keys(langs))
88 languageNames.add(name)
89 Object.assign(languageInput, langs)
90 }
91 else {
92 console.error(red('[slidev] Invalid langs option in shiki setup:'), langs)
93 }
94 }
95
96 return {
97 options: mergedOptions as ResolvedSlidevUtils['shikiOptions'],
98 themeOption,
99 themeNames,
100 themeInput,
101 languageNames,
102 languageInput,
103 }
104 }
105
106 function extractThemeName(theme?: ThemeRegistrationAny | string): string | undefined {
107 if (!theme)
108 return undefined
109 if (typeof theme === 'string')
110 return theme
111 if (!theme.name)
112 console.warn(yellow('[slidev] Theme'), theme, yellow('does not have a name, which may cause issues.'))
113 return theme.name
114 }
115
116 function extractThemeNames(themes?: Record<string, ThemeRegistrationAny | string>): Record<string, string> {
117 if (!themes)
118 return {}
119 return objectMap(themes, (key, theme) => {
120 const name = extractThemeName(theme)
121 if (!name)
122 return undefined
123 return [key, name]
124 })
125 }
126
126 lines TYPESCRIPT