我正在参加「掘金·启航计划」
前言
Vitepress 出来后,已经是我们博客、文档的不二之选。但是其功能还不是很完善,其中的一个痛点就是不能根据目录自动生成侧边栏,每次我们想增加一篇文章时都需要手动的将文章路径同步到 sidebar config。这个问题在 issues 里已经挂了很久了,但是优先级不是很高,于是有了个人开发的想法。
介绍
- 自动将目录转换成 sidebar,取文件的标题作为侧边栏名称。
# 目录结构
├── dir1
│ ├── dir1-1
│ │ ├── 1.md
│ │ ├── 2.md
│ │ └── dir1-1-1
│ │ └── 1.md
│ └── dir1-2
│ └── 1.md
- 当文件删除、标题修改时,自动同步到 sidebar
- 支持对产生的 sidebar 配置做自定义修改(改名称、排序...)
怎么使用
- 安装
# pnpm
pnpm i @iminu/vitepress-plugin-auto-sidebar
# yarn
yarn add @iminu/vitepress-plugin-auto-sidebar
# npm
npm install @iminu/vitepress-plugin-auto-sidebar
- 配置
// .vitepress/config.ts
import AutoSidebar from "@iminu/vitepress-plugin-auto-sidebar";
export default defineConfig({
vite: {
plugins: [
AutoSidebar(),
],
},
});
自定义配置
// .vitepress/config.ts
import AutoSidebar from "@iminu/vitepress-plugin-auto-sidebar";
export default defineConfig({
vite: {
plugins: [
AutoSidebar({
/**
* 当插件将目录结构转换为 sidebar 配置后触发,
* 方便我们去操作 sidebar,比如将目录排序、修改目录名称等
*/
sidebarResolved(value) {
// do sort
value["/dir2/"][0].items?.sort((a, b) => a.text - b.text);
// rename
value["/dir2/"][0].text = "sorted";
},
// 忽略一些文件
ignores: ["index.md"],
// 指定我们要自动构建的文档目录,默认是 .vitepress 目录
docs: path.resolve(process.cwd(), "docs/demo"),
/**
* 指定 .vitepress 目录,默认会通过 glob 匹配到,
* 如果页面有多个 .vitepress 需要手动配置
*/
root: path.resolve(process.cwd(), "docs"),,
}),
],
},
});
实现
vitepress 是基于 vite,因此我们的操作可以通过编写 Vite Plugin 实现。
1. 自动生成 sidebar 结构
思路是重写我们的配置文件。通过插件构建出 sidebar config 的数据结构,自动填充到 vitepress config 中,这里我们借助 config() 这个钩子。
export default function VitePluginAutoSidebar() {
return {
name: "VitePluginAutoSidebar",
// 新增
config(config) {
config.vitepress.site.themeConfig.sidebar = getSidebarConfig(opts);
return config;
},
};
}
getSidebarConfig()就是将目录结构转换成 sidebar config,那么我们怎么实现这个方法呢。
- 通过
glob,找到所有的.md文件。
const paths = glob.sync("**/*.md", {
cwd: docsPath,
ignore: opts.ignores,
});
- 拿到所有的文件 path 后,对 path 做分割,然后遍历组合成树,构建出符合 sidebar 的结构,具体见 源码。
paths.forEach((fullPath) => {
const segments = fullPath.split("/");
const absolutePath = path.resolve(docsPath, fullPath);
if (segments.length === 0) return;
// { "/demo/dir1/":[]}
const topLevel = basePath
? `/${basePath}/${segments.shift()}/`
: `/${segments.shift()}/`;
// 如果第一级是文件
if (topLevel.endsWith(".md")) return;
if (!sidebar[topLevel]) {
sidebar[topLevel] = [];
}
let currentLevel = sidebar[topLevel];
segments.forEach((segment) => {
let curConfig = currentLevel.find((item) => item.text === segment);
if (!curConfig) {
const itemConfig: DefaultTheme.SidebarItem = {};
// is file
if (segment.endsWith(".md")) {
const route = getRoute(opts.root, absolutePath);
itemConfig.text = matchTitle(absolutePath);
itemConfig.link = route;
// cache title
titleCache[route] = itemConfig.text;
} else {
itemConfig.text = segment;
itemConfig.collapsed = false;
itemConfig.items = [];
}
currentLevel.push(itemConfig);
curConfig = itemConfig;
}
currentLevel = curConfig.items as DefaultTheme.SidebarItem[];
});
});
2. 文件变动时刷新 sidebar
- 利用
configureServer钩子拿到ViteDevServer对象。 - 通过
server.watcher监听所有的.md文件变化 - 触发删除文件时重启 server
- 触发文件修改时,查看一级标题是否有变化,如有变化重启 server
export default function VitePluginAutoSidebar() {
return {
name: "VitePluginAutoSidebar",
configureServer: ({ watcher, restart }: ViteDevServer) => {
const fsWatcher = watcher.add("*.md");
fsWatcher.on("all", (event, filePath) => {
if (event === "addDir") return;
if (event === "unlinkDir") return;
if (event == "add") return;
if (event === "unlink") {
restart();
return;
}
if (event === "change") {
const title = matchTitle(filePath);
const route = getRoute(opts.root, filePath);
if (!route || !title) return;
// 未更新 title
if (title === titleCache[route]) return;
restart();
return;
}
});
},
};
}
最后
以上就是文章的全部内容了
如果你想给博客增加全文搜索,见 快来给你的博客添加全文搜索
如果对你有帮助,欢迎 Star 🌟 支持一下!