尧图精选

Slidev 预解析器(Pre-Parser)扩展配置指南:用 setup/preparser.ts 实现自定义语法

🕒 发布时间:2026/9/6 22:31:02 📁 来源:尧图网络
Slidev 预解析器Pre-Parser扩展配置指南用 setup/preparser.ts 实现自定义语法【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev在 Slidev 中如果你想在slides.md里发明新的语法例如一行cover:直接生成封面页、用自定义 frontmatter 批量缩放幻灯片就必须在「Markdown 解析之前」的预处理阶段介入。本文围绕官方文档《Configure Pre-Parser》展开完整覆盖预解析器扩展的文件位置、API 结构与三个官方用例并结合slidev/parser、slidev/types的源码讲清扩展的加载与执行时机帮助你在理解三步解析管线的前提下安全地扩展 Slidev 语法层。Slidev 的三步解析管线官方文档将 Slidev 对演示文件如slides.md的解析描述为三个步骤预解析preparsing文件先被拆分成分页分页依据是---分隔符并考虑可能存在的 frontmatter 块逐页解析每一页的 Markdown 内容由外部库markdown-it 生态解析为 Vue 模板src解析Slidev 解析特殊的 frontmatter 属性src: ...从而把其他 md 文件的内容引入当前演示。从源码结构看这三步对应着不同的模块第 1 步的核心是 parse 函数slidev/parser包。它逐行扫描文件遇到---就切出一个 slide 片段代码块和 HTML 注释!-- --内部的分隔符会被正确跳过这正是「considering the possible frontmatter blocks」以及注释不产生假分页的实现所在advanceHtmlCommentStatecore.ts。第 2 步发生在 Vite 层由内置的 markdown 插件完成配置方式见下文 Markdown Parser 配置。第 3 步在 load 函数packages/parser/src/fs.ts中实现当某页 frontmatter 带有src时按相对/绝对路径解析被引入文件做循环引用检测与项目根目录逃逸检查后再递归加载。理解这条管线是选择扩展点的前提如果你要改的是「Markdown 语法 → 模板」的转换第 2 步应该用 Transformers 或 Vite 内部插件如果你要改的是「文件 → 分页结构」本身第 1 步和第 3 步才需要本文的主角——预解析器扩展。Markdown Parser 的配置第 2 步所用的 Markdown 解析器不是通过 preparser 配置的。官方文档给出的方式是通过配置 Vite 内部插件来完成具体见 Configure Vite and Plugins 中的 “Configure Internal Plugins” 一节。在那里你可以通过vite.config.ts的slidev.vue/slidev.markdown等字段覆盖内置的vite-plugin-vue-markdownmarkdown-it配置例如在markdownSetup(md)中挂载自定义 markdown-it 插件。文档同时给出了一个使用上的重要提示info 框原文语义自定义 pre-parser 不应该被频繁使用。对于自定义语法通常优先使用 Transformers。也就是说preparser 的定位是高级功能advanced feature它运行在解析管线最前端、影响面最大能用 Transformers 解决的语法问题不要上 preparser。Preparser 扩展API 与类型定义预解析器扩展自v0.37.0起可用。官方文档中有两条必须牢记的注意事项::: warning修改 preparser 配置后需要完全停止并重新启动Slidev仅仅热重启可能不够。扩展 preparser 属于高级特性由于它会隐式改变 md 文件的语法有可能破坏编辑器集成如 Side Editor——编辑器看到的仍是原始语法如cover:而运行时分页结构已经被扩展改写。启用方式为在项目根目录或主题根目录创建./setup/preparser.ts文件import { definePreparserSetup } from slidev/types export default definePreparserSetup(({ filepath, headmatter, mode }) { return [ { transformRawLines(lines) { for (const i in lines) { if (lines[i] ) lines[i] HELLO } }, } ] })这个例子会把所有行替换为HELLO行。围绕它文档定义了以下核心概念类型定义均可在源码中一一对应definePreparserSetup必须以一个函数为参数调用。它定义于 packages/types/src/setups.ts本质是defineSetupPreparserSetup的别名即只做类型标注、原样返回函数。该函数收到的上下文包含三个字段对应 PreparserSetup 类型filepath根演示文件entry的路径headmattermd 文件头部headmatter解析出的对象mode自v0.48.0起提供取值如dev、build、export。可据此启用不同的扩展例如“仅在导出 PDF 时生效”。函数必须返回一个 preparser 扩展的数组。单个扩展SlidevPreparserExtension定义见 packages/types/src/types.ts可以包含以下成员transformRawLines(lines)在 headmatter 解析完成后立即执行收到 md 文件的全部行字符串数组函数可任意变更这个数组增删改行均可官方示例正是靠splice改写行数transformSlide(content, frontmatter)对每一页调用时机在文件切分之后收到页内容字符串与页 frontmatter 对象。函数可以变更 frontmatter并必须返回内容字符串——允许返回修改后的字符串也允许返回undefined表示未做修改transformNote(note, frontmatter)同样对每一页调用收到演讲者备注字符串或undefined与 frontmatter 对象可以变更 frontmatter返回修改后的备注字符串返回undefined表示未修改name可选的扩展名称便于调试与区分顺序。从源码看执行顺序是明确的在 parse 函数中所有扩展的transformRawLines先行执行对全文件逐行生效随后才进行---切分并在 slice 阶段对每个 slide 依次调用各扩展的transformSlide与transformNote。另外源码还有一处细节transformSlide修改 frontmatter 后若title/level被设为 string / number会被同步到 slide 的标题与层级字段core.ts这意味着你的扩展可以通过注入 frontmatter 间接影响页面标题。用例 1紧凑语法的顶层演示cover:/src:设想你的演示的一部分主要是封面图与引入其他 md 文件希望用紧凑记法书写slides.mdcover: /nice.jpg # Welcome src: page1.md src: page2.md cover: /break.jpg src: pages3-4.md cover: https://cover.sli.dev # Questions? see you next time要支持这种src:与cover:语法创建./setup/preparser.tsimport { definePreparserSetup } from slidev/types export default definePreparserSetup(() { return [ { transformRawLines(lines) { let i 0 while (i lines.length) { const l lines[i] if (/^cover:/i.test(l)) { lines.splice( i, 1, ---, layout: cover, background: ${l.replace(/^cover: */i, )}, ---, ) continue } if (/^src:/i.test(l)) { lines.splice( i, 1, ---, src: ${l.replace(/^src: */i, )}, ---, ) continue } i } } }, ] })原理拆解cover: xxx被splice原地展开为一整段标准 frontmatterlayout: coverbackground: xxx加空行——相当于把一行“宏”翻译成 Slidev 原生分页语法之后无需任何额外代码第 1 步的切分逻辑就能正常把它识别为独立的 cover 页src: xxx.md则展开为src: xxx.md的 frontmatter 页复用第 3 步既有的src引入机制含循环引用与根目录逃逸检查所以引入文件的行为与原生写法完全一致。这正是transformRawLines的典型用法在分页发生前把自定义标记重写为原生结构。仓库的测试 parser.test.ts 中parse with-extension eg-easy-cover用例验证了同类逻辑把cov 1.jpg展开成 cover frontmatter 后断言各页frontmatter为{ layout: cover, background: 1.jpg }等分页结果正确。用例 2用自定义 frontmatter 包裹幻灯片当你经常想对某些幻灯片做缩放但又不想为此新建布局想继续复用现有布局库时可以用自定义 frontmatter。注意命名上用下划线前缀_scale来避免与既有 frontmatter 属性冲突不带下划线的scale可能产生冲突。slides.md写法--- layout: quote _scale: 0.75 --- # Welcome great! --- _scale: 4 --- # Break --- # Ok --- layout: center _scale: 2.5 --- # Questions? see you next time处理_scale: ...的./setup/preparser.tsimport { definePreparserSetup } from slidev/types export default definePreparserSetup(() { return [ { async transformSlide(content, frontmatter) { if (_scale in frontmatter) { return [ Transform :scale${frontmatter._scale}, , content, , /Transform ].join(\n) } }, } ] })要点这里用的是transformSlide按页处理而非transformRawLines。函数收到已经过切分与 frontmatter 解析后的页内容判断 frontmatter 中是否存在_scale后返回用 Transform 内置组件包裹后的新内容字符串没有命中时隐式返回undefined该页保持原样——与类型定义中「possiblyundefinedif no modifications have been done」完全一致。包裹后的模板会在第 2 步被 markdown 插件渲染为真实的 Vue 组件调用因此缩放是运行时生效的且对任意layout:通用。由于_scale属性本身仍保留在 frontmatter 中若不希望它泄漏到运行时页面数据可以在扩展里delete frontmatter._scale——frontmatter 对象是可变mutable的。用例 3用自定义 frontmatter 替换演讲者备注设想你想把某一页的默认备注替换为外部文件中的备注。slides.md写法同样使用下划线避免属性冲突--- layout: quote _note: notes/note.md --- # Welcome great! !-- Default slide notes --处理_note: ...的./setup/preparser.tsimport fs, { promises as fsp } from node:fs import { definePreparserSetup } from slidev/types export default definePreparserSetup(() { return [ { async transformNote(note, frontmatter) { if (_note in frontmatter fs.existsSync(frontmatter._note)) { try { const newNote await fsp.readFile(frontmatter._note, utf8) return newNote } catch (err) { } } return note }, } ] })说明transformNote在每页切分后、拿到页备注来自页尾 HTML 注释见 parseSlide后被调用。命中_note且文件存在时返回从文件读入的新备注覆盖原备注任何未命中或读取失败的路径都应return note保持原值。该函数支持async因此可以在预处理阶段做文件 IO这也是三个回调中唯一在官方示例里显式使用async的一个。扩展是如何被加载和执行的源码视角把官方文档的抽象描述落到当前仓库的实现上整条调用链如下入口注入packages/slidev/node/setups/preparser.ts 中的setupPreparser()调用injectPreparserExtensionLoader把「加载扩展」的职责挂到slidev/parser上加载 setup 文件loader 内部调用 loadSetupsroots, preparser.ts, [{ filepath, headmatter, mode }]。它遍历每个 root项目与主题的根目录检查setup/preparser.ts是否存在存在则用loadModule加载并以其默认导出调用参数即文档中说的{ filepath, headmatter, mode }上下文所有 root 的返回值经returns.flat()合并成扩展数组。这也解释了多扩展的执行顺序数组按加载顺序展开测试 parser.test.ts 的 sequence 用例 验证了多个transformRawLines扩展会按声明顺序依次作用于同一行headmatter 先行识别在 load 函数 中slidev/parser会先用与解析代码一致的严格规则识别文件头部的 frontmatter 块并 YAML 解析再把结果传给 loader——这就是 setup 函数里headmatter参数的来源也意味着你可以按 headmatter 的内容决定启用哪些扩展例如按演示文件开启特定扩展扩展到所有 md 文件loadMarkdown在 加载每个 md 文件时 都调用parse(raw, path, extensions)即扩展不仅作用于 entry 文件也作用于src:引入的子文件扩展生效点transformRawLines在切分前执行transformSlide/transformNote在每页切分后执行详见前文 core.ts 的分析。slidev/parser的公共 API 快照fs.snapshot.d.ts确认了injectPreparserExtensionLoader、parse等入口测试目录 test/parser.test.ts 则系统性地覆盖了transformRawLines替换、自定义分隔符SEPARATOR → ---、紧凑封面与transformSlide包裹含前插/后插内容与注入 frontmatter 的组合断言等行为可作为扩展行为边界的参考。使用限制与最佳实践汇总文档与源码给出的约束实际使用 preparser 扩展时建议遵守改完必须完全重启修改setup/preparser.ts后需停止再启动 Slidev热更新可能不生效优先 Transformers如文档 info 框所述常规自定义语法请先考虑 Transformers只有需要改分页结构、src引入或按 headmatter/mode 条件启用的场景才用 preparser警惕编辑器一致性preparser 改动是「隐式语法变更」Side Editor 等编辑器看到的原文与运行时结构会不一致属于文档明确提示的破坏风险点side-editor 文档善用mode参数v0.48.0可在导出 PDF 与开发预览之间启用不同扩展避免调试语法污染正式导出命名加下划线前缀自定义 frontmatter 属性如_scale、_note避免与内置属性冲突保持扩展幂等且最小transformRawLines拿到的是全文件行数组任意splice/push都合法但需谨慎未命中规则时不要产生副作用。按上述方式配置后cover:/src:这类紧凑语法、_scale缩放包裹、_note外部备注均可直接在你的slides.md中使用——这正是官方文档以 “And thats it.” 收尾的原因一次性的 setup 文件写好后后续写作即可全程使用自定义语法。【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →