Slidev 的 Features 特性索引是如何实现的:从 VitePress Content Loader 到可分享的 URL 过滤【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev本文以 Slidev 官方文档中的 Features 特性总览页(docs/features/index.md)为主体,讲解这个页面背后的完整实现:如何用一个 VitePresscreateContentLoader把docs/features/目录下 40 余个特性文档自动聚合为结构化数据,如何通过 URL hash 参数实现可分享的全文搜索与多标签过滤,以及特性卡片、标签色块等渲染组件的细节。读完后你可以掌握文档站内容索引页的一种标准构建方式,并知道如何向 Slidev 文档体系新增一个特性页面。Features 页面要解决的问题Slidev 提供的大量功能(代码块语法、Monaco 编辑器、绘图、录制、主题系统等)在文档中是逐篇独立、可选启用的。Features 总览页正是为这些文档建立的一级入口:This is a list of all the individual features that Slidev provides. Each feature can be used independently and is optional. (原文,见 docs/features/index.md)它解决三个具体问题:发现:用户不需要记住每个功能的文档路径,打开一个页面即可看到全部特性的标题、简介与标签;检索:支持按关键词搜索(匹配特性名、标题、描述)与按标签多条件过滤,过滤状态写入 URL hash,链接可直接分享;导航闭环:特性卡片上的标签点击后会带#tagstag回到本页完成过滤,卡片则跳转到对应的特性详情文档(构建后形如/features/name.html)。需要说明的是,该页面本身在 frontmatter 中关闭了编辑链接、页脚、侧边栏与大纲(editLink: false等),并强制内容最大宽度为72vw,这是一个纯展示型的数据驱动页面。数据契约:每个特性文档的 frontmatter索引的数据源就是 docs/features/ 目录下除index.md之外的所有.md文件,例如 import-snippet、side-editor、recording。每个文档用 frontmatter 声明一组结构化字段,构成索引页面的数据契约:字段含义示例(来自 import-snippet.md)description特性一句话简介,展示在卡片上Import code snippets from existing files into your slides.tags标签数组,用于过滤与展示[codeblock, syntax]depends该特性依赖的其他特性(相对路径)见其他含depends的文档relates相关特性,用于延伸阅读- features/monaco-editorsince特性引入的版本v0.47.0derives由哪些特性依赖它(通常自动推导,可手写补充)—frontmatter 之外,文档正文的 H1 标题(# Import Code Snippets)会被解析出来作为卡片的title。正文则承载完整的实操内容——以 Import Code Snippets 为例,它给出了 /snippets/snippet.js、VS Code region 片段(#region-name)、显式语言声明(ts)以及组合行高亮/Monaco 选项的完整语法,这些正文细节不属于索引页职责,本文不再展开。用 createContentLoader 聚合全目录文档核心聚合逻辑位于 docs/features/index.data.ts。它借助 VitePress 的createContentLoader声明式地加载整个特性目录:export interface Feature { name: string title: string link: string description: string depends: string[] relates: string[] derives: string[] tags: string[] since?: string } export default createContentLoader(features/*.md, { includeSrc: true, transform(data) { /* 见下文 */ }, })要点有三个:includeSrc: true让每个文档的源码进入md.src,从而可以用正则/^# (.*)$/m提取 H1 作为title(提取失败时回退为文件名);name与link由文件名决定:name basename(url, .md),link /features/${name}.html。index与features两个名字会被显式跳过,避免索引页自我引用;frontmatter 字段按缺省值兜底:所有数组字段都用?? []保证缺失时是空数组,since保持可选。derives 的反向推导最有意思的部分是derives的自动计算。depends是我依赖谁,而derives是谁依赖我。transform 中先遍历一遍所有文档,用正则RE_FEATURE_NAME /\/([\w-])($|#)/从依赖路径(如features/import-snippet#region)中解析出特性名,建立依赖目标 - 依赖者列表的倒排表derivesMap,再在第二个循环里合并进每个特性的derives数组(手写声明优先,倒推结果去重后追加)。这样只要任何文档声明了depends,被依赖方无需修改即可在索引数据中体现出反向关系——这是典型的正向声明、反向索引设计,从源码结构看,它也让某个基础特性被哪些上层特性构建这类问题可以直接从数据回答。页面状态:URL hash 参数驱动的可分享过滤docs/features/index.md 的script setup中并没有用 ref 存搜索词,而是把状态直接映射到 URL 上:const query useUrlSearchParams(hash-params, { removeFalsyValues: true }) const search toRef(query, search) as Refstring | null const tags toRef(query, tags) as Refstring | null const tagsArr computed({ get: () tags.value?.split(,).map(t t.trim()).filter(Boolean) ?? [], set: (val: string[]) query.tags val.join(,), })这里有两处值得注意的实现选择:hash-params策略(来自vueuse/core的useUrlSearchParams):过滤状态写在#searchxxxtagsa,b形式的 hash 里而不是 query string。hash 不会发送到服务器,因此该页面可以部署在任意静态托管上而不需要任何后端或路由配置,同时浏览器历史、前进/后退、复制链接分享全部天然可用;双向 computed 封装:对外暴露数组语义的tagsArr,内部序列化为逗号分隔字符串;removeFalsyValues: true则保证清空过滤条件时 URL 保持干净。过滤算法filteredFeatures的过滤规则是搜索 OR 标签 AND的组合:const filteredFeatures computed(() { const s search.value?.toLowerCase().trim() const t tagsArr.value return Object.values(features).filter(feature { const matchSearch !s || feature.name.toLowerCase().includes(s) || feature.title.toLowerCase().includes(s) || feature.description.toLowerCase().includes(s) const matchTags !t?.length || t.every(tag feature.tags?.some(featureTag featureTag.toLowerCase() tag.toLowerCase()) ) return matchSearch matchTags }) })关键词只需命中name、title、description 三者之一(全部小写后做子串匹配);多个已选标签必须全部命中才算匹配(t.every对feature.tags做双向小写相等比较);两个条件同时满足才保留结果。空结果时页面展示 No results found 并提供Clear Filters按钮,其resetFilters()将query.search与query.tags置空,URL 同步复位。整个过滤是纯客户端计算,数据在构建期已由 content loader 生成,页面本身不含任何异步请求。渲染层:特性卡片网格与标签色块FeaturesOverview:响应式卡片网格卡片网格由主题组件 docs/.vitepress/theme/components/FeaturesOverview.vue 渲染。它接收过滤后的Feature[],每张卡片是一个锚点,href经过withBase(feature.link)处理以兼容站点子路径部署,内容依次为加粗标题、半透明描述、弹性占位与标签行:div classfeatures-grid mt-4 a v-forfeature in features :keyfeature.name flex flex-col h-full p4 gap-3 rounded-md :hrefwithBase(feature.link) div font-bold text-wrap leading-5 {{ feature.title }} /div div text-wrap leading-5 op-75 overflow-hidden text-sm {{ feature.description }} /div div flex-grow / div flex gap-1 pointer-events-auto FeatureTag v-fortag in feature.tags :keytag :tag / /div /a /div网格布局采用grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)),即每格最小 200px、行数自动的经典响应式写法,宽屏下自动排多列,移动端单列,不需要任何媒体查询。hover 时切换为品牌色文字与深色背景。FeatureTag:哈希定色的标签与过滤闭环标签组件 docs/.vitepress/theme/components/FeatureTag.vue 做了两件有复用价值的事:由字符串哈希稳定生成颜色。getHashColorFromString对tag salt做经典哈希后对 360 取模得到色相,再配合明暗模式参数(暗色 50%/60%、亮色 65%/40% 的饱和度/亮度)生成 5 档透明度的 HSLA 颜色,分别用于背景、边框、文字与 hover 态。同一标签在任何位置、任何时刻都呈现同一颜色,且无需维护颜色配置表;标签文案的展示格式化。formattedTag会把连字符换成空格、首字母大写,并保留API、CLI这类单词的全大写形态(全大写输入则原样保留)。组件通过removable、noclick两个 prop 支持三种形态:可删除的过滤回显(索引页搜索区使用,点击close图标emit(remove))、纯展示 span、以及默认可点击链接——默认形态指向withBase(/features/#tags${tag}),即回到 Features 页并带上该标签的 hash 参数,由前文的tagsArrcomputed 接住,完成卡片标签 → 过滤视图的闭环。页面模板中还有一个细节:搜索区与网格都包在ClientOnly内,避免 SSR 阶段渲染依赖 URL hash 的内容;正文区之外还内联了覆盖 VitePress 内容宽度的样式(.all-features-page .VPDoc .container .content { max-width: 72vw !important; })。当前收录的特性清单createContentLoader(features/*.md)是通配加载,目录中新增文档即自动进入索引,无需改动任何页面代码。截至当前仓库,该目录收录了 40 余个特性文档,按主题大致可分为(分组依据文档文件名归纳):代码块与语法:line-highlighting、code-block-line-numbers、code-block-max-height、code-groups、shiki-magic-move(since v0.48.0)、twoslash(since v0.46.0)、import-snippet(since v0.47.0)、comark(since v0.43.0);Markdown 结构:block-frontmatter、frontmatter-merging、slot-sugar、importing-slides、slide-scope-style;编辑器与交互工具:monaco-editor、monaco-run(since v0.48.0)、monaco-write(since v0.49.5)、side-editor、drawing、click-marker(since v0.48.0)、rough-marker(since v0.48.0)、prettier-plugin、vscode-extension;图表与公式:latex、mermaid、plantuml;布局与样式:canvas-size、zoom-slide、draggable、direction-variant(since v0.48.0)、global-layers、transform-component、icons、eject-theme;演讲者与发布:recording、timer、notes-auto-ruby、remote-access、mcp(since v52.17.0)、pwa(since v52.17.0)、og-image、seo-meta、bundle-remote-assets、slide-hook。since字段在索引数据中是可选的,但相当一部分文档维护了它,便于在总览中判断某个特性的可用前提。如何新增一个特性文档由于索引完全由目录内容驱动,新增特性的成本极低,流程如下:在 docs/features/ 下创建feature-name.md,正文写 H1 标题(会被用作卡片标题);在 frontmatter 中按需声明description(建议,卡片展示依赖它)、tags、relates,若存在前置依赖则写depends(相对路径即可,如features/monaco-editor);正文按现有文档的写法给出完整语法示例、参数说明与限制条件(可参照 import-snippet 的篇幅与结构);构建文档站后,createContentLoader会自动把它纳入总览网格,derives倒排也会自动反映新的依赖关系,无需修改 index.md 或 index.data.ts。小结Slidev 的 Features 页是一个内容即数据的文档站索引范例:frontmatter 承担结构化契约,docs/features/index.data.ts 的createContentLoader负责加载与反向推导,页面用useUrlSearchParams(hash-params)把搜索与标签状态落到可分享的 URL 上,再由 FeaturesOverview.vue 与 FeatureTag.vue 完成响应式渲染与过滤闭环。对希望在自己的 VitePress 站点中构建全量文档特性目录 关键词/标签检索的读者,这套content loader hash 参数 卡片网格的组合可以直接借鉴。【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
