返回 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 shiki: {},
61 preloadImages: true,
62 clickAnimation: '',
63 }
64 }
65
66 export function resolveConfig(headmatter: any, themeMeta: SlidevThemeMeta = {}, filepath?: string, verify = false) {
67 const themeHightlighter = ['prism', 'shiki', 'shikiji'].includes(themeMeta.highlighter || '')
68 ? themeMeta.highlighter as 'shiki'
69 : undefined
70 const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '')
71 ? themeMeta.colorSchema as 'light' | 'dark'
72 : undefined
73
74 const defaultConfig = getDefaultConfig()
75
76 const config: SlidevConfig = {
77 ...defaultConfig,
78 highlighter: themeHightlighter || defaultConfig.highlighter,
79 colorSchema: themeColorSchema || defaultConfig.colorSchema,
80 ...themeMeta.defaults,
81 ...headmatter.config,
82 ...headmatter,
83 fonts: resolveFonts({
84 ...defaultConfig.fonts,
85 ...themeMeta.defaults?.fonts,
86 ...headmatter.config?.fonts,
87 ...headmatter?.fonts,
88 }),
89 drawings: resolveDrawings({
90 ...themeMeta.defaults?.drawings,
91 ...headmatter.config?.drawings,
92 ...headmatter?.drawings,
93 }, filepath),
94 htmlAttrs: {
95 ...defaultConfig.htmlAttrs,
96 ...themeMeta.defaults?.htmlAttrs,
97 ...headmatter.config?.htmlAttrs,
98 ...headmatter?.htmlAttrs,
99 },
100 themeConfig: {
101 ...defaultConfig.themeConfig,
102 ...themeMeta.defaults?.themeConfig,
103 ...headmatter.config?.themeConfig,
104 ...headmatter?.themeConfig,
105 },
106 }
107
108 // @ts-expect-error compat
109 if (config.highlighter === 'shikiji') {
110 console.warn(`[slidev] "shikiji" is merged back to "shiki", you can safely change it "highlighter: shiki"`)
111 config.highlighter = 'shiki'
112 }
113
114 // @ts-expect-error compat
115 if (config.highlighter === 'prism')
116 throw new Error(`[slidev] "prism" support has been dropped. Please use "highlighter: shiki" instead`)
117
118 if (config.colorSchema !== 'dark' && config.colorSchema !== 'light')
119 config.colorSchema = 'auto'
120 if (themeColorSchema && config.colorSchema === 'auto')
121 config.colorSchema = themeColorSchema
122
123 config.aspectRatio = parseAspectRatio(config.aspectRatio)
124
125 if (verify)
126 verifyConfig(config, themeMeta)
127
128 return config
129 }
130
131 export function verifyConfig(
132 config: SlidevConfig,
133 themeMeta: SlidevThemeMeta = {},
134 warn = (v: string) => console.warn(`[slidev] ${v}`),
135 ) {
136 const themeHightlighter = themeMeta.highlighter === 'shiki'
137 ? themeMeta.highlighter as 'shiki'
138 : undefined
139 const themeColorSchema = ['light', 'dark'].includes(themeMeta.colorSchema || '')
140 ? themeMeta.colorSchema as 'light' | 'dark'
141 : undefined
142
143 if (themeColorSchema && config.colorSchema !== themeColorSchema)
144 warn(`Color schema "${config.colorSchema}" does not supported by the theme`)
145
146 if (themeHightlighter && config.highlighter !== themeHightlighter)
147 warn(`Syntax highlighter "${config.highlighter}" does not supported by the theme`)
148
149 if (config.css !== 'unocss') {
150 warn(`Unsupported Atomic CSS engine "${config.css}", fallback to UnoCSS`)
151 config.css = 'unocss'
152 }
153 }
154
155 export function resolveFonts(fonts: FontOptions = {}): ResolvedFontOptions {
156 const {
157 fallbacks = true,
158 italic = false,
159 provider = 'google',
160 } = fonts
161 let sans = toArray(fonts.sans).flatMap(i => i.split(',')).map(i => i.trim())
162 let serif = toArray(fonts.serif).flatMap(i => i.split(',')).map(i => i.trim())
163 let mono = toArray(fonts.mono).flatMap(i => i.split(',')).map(i => i.trim())
164 const weights = toArray(fonts.weights || '200,400,600').flatMap(i => i.toString().split(',')).map(i => i.trim())
165 const custom = toArray(fonts.custom).flatMap(i => i.split(',')).map(i => i.trim())
166
167 const local = toArray(fonts.local).flatMap(i => i.split(',')).map(i => i.trim())
168 const webfonts = fonts.webfonts
169 ? fonts.webfonts
170 : fallbacks
171 ? uniq([...sans, ...serif, ...mono, ...custom]).filter(i => !local.includes(i))
172 : []
173
174 function toQuoted(font: string) {
175 if (RE_QUOTED_STRING.test(font))
176 return font
177 return `"${font}"`
178 }
179
180 if (fallbacks) {
181 sans = uniq([
182 ...sans.map(toQuoted),
183 'ui-sans-serif',
184 'system-ui',
185 '-apple-system',
186 'BlinkMacSystemFont',
187 '"Segoe UI"',
188 'Roboto',
189 '"Helvetica Neue"',
190 'Arial',
191 '"Noto Sans"',
192 'sans-serif',
193 '"Apple Color Emoji"',
194 '"Segoe UI Emoji"',
195 '"Segoe UI Symbol"',
196 '"Noto Color Emoji"',
197 ])
198 serif = uniq([
199 ...serif.map(toQuoted),
200 'ui-serif',
201 'Georgia',
202 'Cambria',
203 '"Times New Roman"',
204 'Times',
205 'serif',
206 ])
207 mono = uniq([
208 ...mono.map(toQuoted),
209 'ui-monospace',
210 'SFMono-Regular',
211 'Menlo',
212 'Monaco',
213 'Consolas',
214 '"Liberation Mono"',
215 '"Courier New"',
216 'monospace',
217 ])
218 }
219
220 return {
221 sans,
222 serif,
223 mono,
224 webfonts,
225 provider,
226 local,
227 italic,
228 weights,
229 }
230 }
231
232 function resolveDrawings(options: DrawingsOptions = {}, filepath?: string): ResolvedDrawingsOptions {
233 const {
234 enabled = true,
235 persist = false,
236 presenterOnly = false,
237 syncAll = true,
238 } = options
239
240 const persistPath = typeof persist === 'string'
241 ? persist
242 : persist
243 ? `.slidev/drawings${filepath ? `/${filepath.match(RE_FILENAME_STEM)?.[1]}` : ''}`
244 : false
245
246 return {
247 enabled,
248 persist: persistPath,
249 presenterOnly,
250 syncAll,
251 }
252 }
253
253 lines TYPESCRIPT