返回 slidev
config.ts
根目录 / packages / parser / src / config.ts
1 import type { DrawingsOptions, FontOptions, ResolvedDrawingsOptions, ResolvedExportOptions, ResolvedFontOptions, SlidevConfig, SlidevThemeMeta } from '@slidev/types'
2 import { toArray, uniq } from '@antfu/utils'
3 import { parseAspectRatio } from './utils'
4
5 const RE_QUOTED_STRING = /^(['"]).*\1$/
6 const RE_FILENAME_STEM = /([^\\/]+?)(?:\.\w+)?$/
7
8 export function getDefaultConfig(): SlidevConfig {
9 return {
10 theme: 'default',
11 title: 'Slidev',
12 titleTemplate: '%s - Slidev',
13 addons: [],
14 remoteAssets: false,
15 monaco: true,
16 monacoTypesSource: 'local',
17 monacoTypesAdditionalPackages: [],
18 monacoTypesIgnorePackages: [],
19 monacoRunAdditionalDeps: [],
20 monacoRunUseStrict: true,
21 download: false,
22 export: {} as ResolvedExportOptions,
23 info: false,
24 highlighter: 'shiki',
25 twoslash: true,
26 lineNumbers: false,
27 colorSchema: 'auto',
28 routerMode: 'history',
29 aspectRatio: 16 / 9,
30 canvasWidth: 980,
31 exportFilename: '',
32 selectable: false,
33 themeConfig: {},
34 fonts: {} as ResolvedFontOptions,
35 favicon: 'https://cdn.jsdelivr.net/gh/slidevjs/slidev/assets/favicon.png',
36 drawings: {} as ResolvedDrawingsOptions,
37 plantUmlServer: 'https://www.plantuml.com/plantuml',
38 codeCopy: true,
39 magicMoveCopy: true,
40 author: '',
41 record: 'dev',
42 css: 'unocss',
43 presenter: true,
44 browserExporter: 'dev',
45 htmlAttrs: {},
46 transition: null,
47 editor: true,
48 mcp: true,
49 contextMenu: null,
50 wakeLock: true,
51 pwa: false,
52 remote: false,
53 mdc: false,
54 comark: false,
55 seoMeta: {},
56 notesAutoRuby: {},
57 duration: '30min',
58 timer: 'stopwatch',
59 magicMoveDuration: 800,
60 preloadImages: true,
61 clickAnimation: '',
62 }
63 }
64
65 export function resolveConfig(headmatter: any, themeMeta: SlidevThemeMeta = {}, filepath?: string, verify = false) {
66 const themeHightlighter = ['prism', 'shiki', 'shikiji'].includes(themeMeta.highlighter || '')
67 ? themeMeta.highlighter as 'shiki'
68 : undefined
69 const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '')
70 ? themeMeta.colorSchema as 'light' | 'dark'
71 : undefined
72
73 const defaultConfig = getDefaultConfig()
74
75 const config: SlidevConfig = {
76 ...defaultConfig,
77 highlighter: themeHightlighter || defaultConfig.highlighter,
78 colorSchema: themeColorSchema || defaultConfig.colorSchema,
79 ...themeMeta.defaults,
80 ...headmatter.config,
81 ...headmatter,
82 fonts: resolveFonts({
83 ...defaultConfig.fonts,
84 ...themeMeta.defaults?.fonts,
85 ...headmatter.config?.fonts,
86 ...headmatter?.fonts,
87 }),
88 drawings: resolveDrawings(headmatter.drawings, filepath),
89 htmlAttrs: {
90 ...defaultConfig.htmlAttrs,
91 ...themeMeta.defaults?.htmlAttrs,
92 ...headmatter.config?.htmlAttrs,
93 ...headmatter?.htmlAttrs,
94 },
95 themeConfig: {
96 ...defaultConfig.themeConfig,
97 ...themeMeta.defaults?.themeConfig,
98 ...headmatter.config?.themeConfig,
99 ...headmatter?.themeConfig,
100 },
101 }
102
103 // @ts-expect-error compat
104 if (config.highlighter === 'shikiji') {
105 console.warn(`[slidev] "shikiji" is merged back to "shiki", you can safely change it "highlighter: shiki"`)
106 config.highlighter = 'shiki'
107 }
108
109 // @ts-expect-error compat
110 if (config.highlighter === 'prism')
111 throw new Error(`[slidev] "prism" support has been dropped. Please use "highlighter: shiki" instead`)
112
113 if (config.colorSchema !== 'dark' && config.colorSchema !== 'light')
114 config.colorSchema = 'auto'
115 if (themeColorSchema && config.colorSchema === 'auto')
116 config.colorSchema = themeColorSchema
117
118 config.aspectRatio = parseAspectRatio(config.aspectRatio)
119
120 if (verify)
121 verifyConfig(config, themeMeta)
122
123 return config
124 }
125
126 export function verifyConfig(
127 config: SlidevConfig,
128 themeMeta: SlidevThemeMeta = {},
129 warn = (v: string) => console.warn(`[slidev] ${v}`),
130 ) {
131 const themeHightlighter = themeMeta.highlighter === 'shiki'
132 ? themeMeta.highlighter as 'shiki'
133 : undefined
134 const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '')
135 ? themeMeta.colorSchema as 'light' | 'dark'
136 : undefined
137
138 if (themeColorSchema && config.colorSchema !== themeColorSchema)
139 warn(`Color schema "${config.colorSchema}" does not supported by the theme`)
140
141 if (themeHightlighter && config.highlighter !== themeHightlighter)
142 warn(`Syntax highlighter "${config.highlighter}" does not supported by the theme`)
143
144 if (config.css !== 'unocss') {
145 warn(`Unsupported Atomic CSS engine "${config.css}", fallback to UnoCSS`)
146 config.css = 'unocss'
147 }
148 }
149
150 export function resolveFonts(fonts: FontOptions = {}): ResolvedFontOptions {
151 const {
152 fallbacks = true,
153 italic = false,
154 provider = 'google',
155 } = fonts
156 let sans = toArray(fonts.sans).flatMap(i => i.split(',')).map(i => i.trim())
157 let serif = toArray(fonts.serif).flatMap(i => i.split(',')).map(i => i.trim())
158 let mono = toArray(fonts.mono).flatMap(i => i.split(',')).map(i => i.trim())
159 const weights = toArray(fonts.weights || '200,400,600').flatMap(i => i.toString().split(',')).map(i => i.trim())
160 const custom = toArray(fonts.custom).flatMap(i => i.split(',')).map(i => i.trim())
161
162 const local = toArray(fonts.local).flatMap(i => i.split(',')).map(i => i.trim())
163 const webfonts = fonts.webfonts
164 ? fonts.webfonts
165 : fallbacks
166 ? uniq([...sans, ...serif, ...mono, ...custom]).filter(i => !local.includes(i))
167 : []
168
169 function toQuoted(font: string) {
170 if (RE_QUOTED_STRING.test(font))
171 return font
172 return `"${font}"`
173 }
174
175 if (fallbacks) {
176 sans = uniq([
177 ...sans.map(toQuoted),
178 'ui-sans-serif',
179 'system-ui',
180 '-apple-system',
181 'BlinkMacSystemFont',
182 '"Segoe UI"',
183 'Roboto',
184 '"Helvetica Neue"',
185 'Arial',
186 '"Noto Sans"',
187 'sans-serif',
188 '"Apple Color Emoji"',
189 '"Segoe UI Emoji"',
190 '"Segoe UI Symbol"',
191 '"Noto Color Emoji"',
192 ])
193 serif = uniq([
194 ...serif.map(toQuoted),
195 'ui-serif',
196 'Georgia',
197 'Cambria',
198 '"Times New Roman"',
199 'Times',
200 'serif',
201 ])
202 mono = uniq([
203 ...mono.map(toQuoted),
204 'ui-monospace',
205 'SFMono-Regular',
206 'Menlo',
207 'Monaco',
208 'Consolas',
209 '"Liberation Mono"',
210 '"Courier New"',
211 'monospace',
212 ])
213 }
214
215 return {
216 sans,
217 serif,
218 mono,
219 webfonts,
220 provider,
221 local,
222 italic,
223 weights,
224 }
225 }
226
227 function resolveDrawings(options: DrawingsOptions = {}, filepath?: string): ResolvedDrawingsOptions {
228 const {
229 enabled = true,
230 persist = false,
231 presenterOnly = false,
232 syncAll = true,
233 } = options
234
235 const persistPath = typeof persist === 'string'
236 ? persist
237 : persist
238 ? `.slidev/drawings${filepath ? `/${filepath.match(RE_FILENAME_STEM)?.[1]}` : ''}`
239 : false
240
241 return {
242 enabled,
243 persist: persistPath,
244 presenterOnly,
245 syncAll,
246 }
247 }
248
248 lines TYPESCRIPT